什么是JNI

  • JNI是Java Native Interface的简称,即Java本地开发接口
  • JNI是一个协议,用来沟通Java代码和本地代码(C/C++)

为什么使用JNI

  • Java语言没有的功能(很少)
  • 代码已经有现成的C/C++版本,不想重写(一般情况)
  • Java太慢了(怎么慢?)
  • 跨平台实现(Mac, Windows, Android, iOS)
  • 特殊的功能性语言(Fortran的数学计算

使用JNI的注意事项

  • 只提供了C/C++的支持,其他语言需要额外的步骤
  • 不安全,特别是内存管理,以及Java对象的引用计数
  • 不可移值,不同的CPU架构需要重新编译(arm, mips, x86)

如何使用JNI-基础知识

  • Android SDK(Java)
  • C/C++语言
  • NDK(Android.mk, ndk-build)

如何使用JNI-C/C++语言基础

  • 指针的使用
  • 内存管理:malloc, free, new, delete
  • 变量类型:int, float, double, char, …
  • C++导出C函数:extern “C” { }

如何使用JNI-基本过程

  1. 下载NDK,支持Windows、Linux和Mac OS X;
  2. 解压NDK到本地磁盘,设置好PATH;
  3. 新建Android工程,在工程目录下新建jni文件夹(文件夹名称必须是jni);
  4. 在jni文件夹下建立Android.mk文件(Android.mk文件的编写方法见后文);
  5. 在jni文件夹下编写好C/C++代码文件;
  6. 命令行切换到当前目录,运行ndk-build,等待编译完成,编译完成后so文件在libs/armeabi文件夹下;
  7. 在eclipse里运行Android工程,so文件会打包到apk里
  • TIPS:ADT插件安装选择Native Development,在工程的右键菜单有Android Tools/Add Native Support,集成了CDT的C/C++ IDE环境,在Java编译前执行ndk-build

如何使用JNI-JNI语法

  • HelloJni例子(NDK_DIR/samples/hello-jni)
    • HelloJni.java
    • HelloJni.c
    • Android.mk

如何使用JNI-HelloJni.java

public class HelloJni extends Activity{// ...       public native String  stringFromJNI(); // 声明native       static {             // 在libs/armeabi下加载libhello-jni.so             System.loadLibrary("hello-jni");              // 指定路径加载libhello-jni.so             // System.load("path/libhello-jni.so");    }}

  • 声明native函数,不用在Java中实现,建议native函数为private
  • 加载jni so文件,有以下两种方式,按需选择一种即可
    • 调用System.loadLibrary默认在libs/armeabi下加载so文件,名称与Android.mk里的LOCAL_MODULE一样
    • 调用System.load,指定so文件的完整路径,必须写完整so文件名

如何使用JNI-HelloJni.c

#include <string.h>#include <jni.h>jstringJava_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,                                                  jobject thiz ){    // C的写法    return (*env)->NewStringUTF(env, "Hello from JNI !");    // C++的写法    // return env->NewStringUTF("Hello form JNI !");}
  • 函数名规则:Java_包名_类名_Java函数名( JNIEnv* env, jobject thiz, 其他参数)
    • 函数名可以用javah自动生成,命令行 $ cd bin $ javah -classpath . -jni com.example.hellojni.HelloJni
    • 产生对应包名的.h文件,实现.h的函数
  • JNIEnv* env:JNI环境的指针,JVM当前线程的句柄,JNI函数调用入口
  • jobject thiz:调用JNI函数当前对象的引用,相当于this指针

如何使用JNI-类型对应表

类型对应表
Java Type Native Type Size in bits
boolean jboolean 8, unsigned
byte jbyte 8
char jchar 16, unsigned
short jshort 16
int jint 32
long jlong 64
float jfloat 32
double jdouble 64
void void N/a
byte [] jbyteArray 8*n
class Object jobject 32


如何使用JNI-字符串

//访问jstringchar *str = (*env)->GetStringUTFChars(env,prompt,0);/* this maps into regular C char*  */printf(“%s”, str); /* now it is ok to print */(*env)->ReleaseStringUTFChars(env, prompt, str);/* must release String to avoid memory leaks */
//返回jstringchar buf[128]; /* allocate memory for local char* in C */scanf(“%s”, buf); /* read into char* from stdio */return( (*env)->NewStringUTF(env, buf)); /* construct and return the Java String */ 

如何使用JNI-数组

jsize len = (*env)->GetArrayLength(env,arr);jint *body = (*env)->GetIntArrayElements(env,arr,0);for (i=0;i<len;++i){  sum += body[i]; }(*env)->ReleaseIntArrayElements(env,arr,body,0);/* very important – copies back to java array if copy had to be made */
  • 其他类型(float, byte, double)的调用方式类似:
    • Get<type>ArrayElements
    • Release<type>ArrayElements
  • 注意: 这些Get函数会拷贝数组,如果不需要内存拷贝,使用:Get/Set<type>ArrayRegion functions
Get Function Release Function Array Type
GetBooleanArrayElements ReleaseBooleanArrayElements boolean
GetByteArrayElements ReleaseByteArrayElements byte
GetShortArrayElements ReleaseShortArrayElements short
GetIntArrayElements ReleaseIntArrayElements int
GetLongArrayElements ReleaseLongArrayElements long
GetFloatArrayElements ReleaseFloatArrayElements float
GetDoubleArrayElements ReleaseDoubleArrayElements double
GetObjectArrayElements ReleaseObjectArrayElements object


如何使用JNI-调用Java函数

jclass cls = (*env)->GetObjectClass(env, thiz);jmethodID methodId = (*env)->GetMethodID(env, cls, "getAout", "()I");bool use_opensles = (*env)->CallIntMethod(env, thiz, methodId) == AOUT_OPENSLES;// ...(*env)->DeleteLocalRef(env, cls);

  • GetObjectClass获取得到调用JNI函数当前对象的类,如果不是当前对象的类,使用FindClass(JNIEnv *env, const char* className)
  • GetMethodID获取调用JNI函数当前对象的类函数,getAout为Java函数名,()I为输入参数和返回值签名,签名的规则见后文
  • CallIntMethod调用Java对象的getAout函数,返回值是int类型,无返回值函数调用CallVoidMethod,其他类型返回值依此类推,静态函数调用CallStaticTypeMethod
  • DeleteLocalRef删除引用,cls使用完后,记得删除引用计数

//JNI回调函数的调用Java函数的方法如下JavaVM *myVm;jint JNI_OnLoad(JavaVM *vm, void *reserved) {    myVm = vm; // Keep a reference on the Java VM.    return JNI_VERSION_1_2;}static void CallbackFunc() {    JNIEnv *env;    if ((*myVm)->AttachCurrentThread(myVm, &env, NULL) < 0)        return;    // ...    (*myVm)->DetachCurrentThread(myVm);} 

  • 在JNI_OnLoad里保存JavaVM
  • 调用AttachCurrentThread获得JNIEnv,使用JNIEnv调用Java函数
  • 调用DetachCurrentThread解绑

如何使用JNI-Java函数变量签名

变量签名表
Java Type Native Type Size in bits
boolean jboolean Z
byte jbyte B
char jchar C
short jshort S
int jint I
long jlong L
float jfloat F
double jdouble D
void void V
String jstring Ljava/lang/String;



如何使用JNI-加入Log

#include <android/log.h>#define LOG_TAG "System.out”#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)LOGI("info\n");LOGD("debug\n");LOGD(“The string is %s”, p_str);
  • Android.mk文件增加LOCAL_LDLIBS += -llog
  • 在LogCat里可以看到JNI输出的日志信息

如何使用JNI-Android.mk

LOCAL_PATH := $(call my-dir) // ①include $(CLEAR_VARS) // ②LOCAL_MODULE    := hello-jni // ③LOCAL_SRC_FILES := hello-jni.c // ④include $(BUILD_SHARED_LIBRARY) // ⑤
① LOCAL_PATH用于指定源代码的目录,my-dir宏函数是编译环境提供,表示当前文件夹
② CLEAR_VARS由编译系统提供,用于清除一些LOCAL_XXX的值(如:LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES …),不清除LOCAL_PATH
③ LOCAL_MODULE定义模块名称标识,名称必须唯一,不能包含空格,编译系统会编译生成lib<modulename>.so或lib<modulename>.a文件
④ LOCAL_SRC_FILES指定要编译的.c和.cpp文件,多个文件以空格分隔,换行连接符为 \
⑤ 编译成共享库,生成so文件,还可以是BUILD_STATIC_LIBRARY,编译静态库,即a文件,.a文件必须链接到so文件里
  • LOCAL_LDLIBS := -llog –lz 加载liblog.a静态库和libz.a静态库,这两个静态库由NDK提供,也可以指定搜索路径加载编译好的静态库,使用-Lpath_to_lib/xxx.a
  • LOCAL_STATIC_LIBRARIES += hello-jni表示编译依赖的静态库,在项目里的其他Android.mk文件定义
  • LOCAL_C_INCLUDES += include指定include路径
  • LOCAL_CFLAGS += -DANDROID=1定义ANDROID宏的值为1
  • 多个共享库都需要调用loadLibrary加载,包括依赖的so库,Android不会自动寻找依赖库

内存管理

  • 全局引用
    • NewGlobalRef
    • jobject NewGlobalRef(JNIEnv *env, jobject obj);
    • 创建一个新的obj全局引用,调用DeleteGlobalRef()释放全局引用
  • 局部引用
    • 每一个传入到native函数的参数和返回值都是本地引用,其值只有在当前线程的native函数调用期间有效,当native函数返回,引用失效。适用于所以jobject派生类,包括jclass, jstring, jarray等。
    • 判断两个object引用是否一样,不能使用==,要使用函数IsSameObject。
    • 不能过度分配局部引用,当创建了大量的局部引用的适合,必须调用DeleteLocalRef手动释放局部引用,Android支持最多512个局部引用。
    • jfieldID和jmethodID不是引用对象。

本文小结

之前的博客被关闭暂时找不回,打算重新开一个博客,坚持记录一些。刚好最近公司的项目有个通过现有db来查找searchfile的功能,java层效率低,把之前的逻辑通过jni转到c++层去实现,于是就从这个开始写吧。
其实jni教程在现在的网络已经泛滥了,所以至于开发环境那些就懒得做了,结合之前公司技术分享的PPT写了这个还算详细的小结。
现在的JNI只是教别人怎么调底层的方法,但是在调用后毕竟要有回馈结果到界面给用户,打算之后会结合源码把JNI讲得更深入点,同时会用回调的逻辑。

更多相关文章

  1. C语言函数的递归(上)
  2. android res目录资源文件适配
  3. 基于Phone模块的Service实现
  4. [置顶] Android高手进阶教程(四)之----Android(安卓)中自定义属
  5. Android编译系统入门(二)
  6. 修改系统action bar字体大小、粗细、颜色等样式的方法
  7. Android注入完全剖析
  8. 解决:修改安卓system系统文件导致手机一直重启
  9. [Android(安卓)编译(一)] Ubuntu 16.04 LTS 成功编译 Android(安

随机推荐

  1. Android appWidget调查报告 (自己的文章,
  2. Android 如何获取当前Activity实例对象?
  3. android listview仿iphone特效
  4. 为什么我在逐渐冷落HashMap,扶SparseArray
  5. Android多分辨率适配巧妙方法
  6. Android仿人人客户端(v5.7.1)——消息中心
  7. Android 架构组件之一
  8. 当视频网站都支持html5
  9. Unity NGUI UI 在iOS端的锯齿、模糊、颗
  10. Android MVP架构