在Android编程过程中,有时会出于程序效率或代码复用的考虑,使用到JNI编程,即用Java调用C/C++的内容,本文将纪录一个简单的JNI调用实例,用以备忘。
1.新建一Android工程,命名HelloJNI,目录如下图:
建立JNI调用的类Hello.java,如下
package com.hellojni;
public class Hello {
static {
try{
System.loadLibrary("hellojni");
}catch(UnsatisfiedLinkError e){
System.err.println( "Cannot load hello library:\n " +
e.toString() );
}
}
public Hello(){
}
public native void SayHello(String strName);
}
主要用上上述代码中的native本地方法调用。
2.Java和C的链接主要是使用命名空间的一致性,为了保证命名不会出现错误,我们采取直接编译java文件的形式直接生成C的头文件,方法如下:
1)将Hello.java编译成Hello.class
simmatoMacBook-Pro:~ me$ javac Documents/workspace/HelloJNI/src/com/hellojni/Hello.java
2) 此时将com.hellojni包copy到bin目录(指android工程的bin目录)下,进入bin目录执行:
simmatoMacBook-Pro:~ me$ javah -classpath . -jni com.hellojni.Hello
此时会在bin下生成com_hellojni_Hello.h,即我们想要的h头文件,如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#include"common.h"
/* Header for class com_hellojni_Hello */
#ifndef _Included_com_hellojni_Hello
#define _Included_com_hellojni_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_hellojni_Hello
* Method: SayHello
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_hellojni_Hello_SayHello
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
3.实现JNI部分
1)在android工程中新建jni目录,将com_hellojni_Hello.h拷入其中,并实现,如下实现com_hellojni_Hello.cpp
#include "com_hellojni_Hello.h"
#include <stdlib.h>
JNIEXPORT void JNICALL Java_com_hellojni_Hello_SayHello
(JNIEnv * env, jobject obj, jstring msg){
LOGI("JNI - hello:\n");
if (msg == NULL) {
//该方法为打印的方法
LOGI( "Your params is null");
}
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("utf-8");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray)env->CallObjectMethod(msg, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0)
{
rtn = (char*)malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
//该方法为打印的方法
LOGI("Get Param: %s From Java", rtn);
}
#ifndef COMMON_HEADER
#define COMMON_HEADER
//enable re-enter code
#define _REENTRANT
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <time.h>
#include <fstream>
#include <pthread.h>
#include <string.h>
#include <memory>
#include <string>
#include <errno.h>
#include <semaphore.h>
// for android debug
#include <android/log.h>
#define debug
#ifdef debug
#define LOG_TAG "hello_jni"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG,__VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#else
#define LOG_TAG
#define LOGI(...)
#define LOGD(...)
#define LOGW(...)
#define LOGE(...)
#endif //debug
typedef void (*Notify)(int);
using namespace std;
#define SINGLETON(CLASS) \
friend class auto_ptr<CLASS>; \
public: \
static CLASS* GetInstance(){ \
if (_mInstance.get() == NULL){ \
_mInstance.reset(new CLASS()); \
} \
return _mInstance.get(); \
} \
private: \
CLASS(); \
static auto_ptr<CLASS> _mInstance; \
public: \
virtual ~CLASS();
#define INITINSTANCE(CLASS) \
auto_ptr<CLASS> CLASS::_mInstance(NULL);
#endif //COMMON_HEADER
2)编写Android.mk和Application.mk
Android.mk是一个makefile,用来告诉NDK需要编译哪些文件,生成哪些模块。我们创建<jni>/Android.mk文件:
# =============================
# lib hello
# =============================
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hellojni
LOCAL_C_INCLUDES := $(LOCAL_PATH)/
LOCAL_SRC_FILES := \
com_hellojni_Hello.cpp \
#LOCAL_CFLAGS += -DENABLE_TRACE
#LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
LOCAL_LDLIBS := -llog -lm -ldl
include $(BUILD_SHARED_LIBRARY)
其中LOCAL_PATH表示c源代码文件的位置;LOCAL_MODULE表示生成的共享库的名称;LOCAL_SRC_FILES代表c代码的文件。不需要把头文件列在里面;头文件的依赖关系是ndk自动计算的。
Application.mk的作用是告诉Android SDK需要哪些库文件。有了它,NDK就可以把库放在正确的位置。我们创建<project>/Application.mk:
APP_STL := stlport_static
STLPORT_FORCE_REBUILD := true
APP_MODULES := hellojni
APP_PROJECT_PATH代表android工程所在目录,在本例中它被放在<project>中; APP_MODULES表示工程需要的库,如果有多个以空格分开。
4.编译hellojni.so
使用eclipse工具runConfigurations,如下:
其中,Location选ndk-build, Working Directory选为本android工程目录下的jni目录,然后“Apply”-》“Run”。
即可在本工程的Libs/armeabi下生成libhellojni.so,取出使用即可。