文章出处:http://blog.csdn.net/shift_wwx
前言:一直没有时间研究这个小玩意,但是近期会碰到这一块,那么就总结一下,方便后期查看。其中很多知识点还没有完善,因为时间有限,后期一定要详细分析。
参考文档:http://developer.android.com/training/articles/perf-jni.html
直接到官方教程那里截的….
二. JNI简介
1. jni的调用流程
众所周知,Android的应用层的类都是以Java写的,这些Java类编译为Dex文件之后,必须靠Dalvik虚拟机( Virtual Machine)来执行。假如在执行java程序时,需要载入C&C++函数时,Dalvik虚拟机就会去加载C&C++的库,(System.loadLibrary("libName");)让java层能顺利地调用这些本地函数。需要清楚一点,这些C&C++的函数并不是在Dalvik虚拟机中运行的,所以效率和速度要比在Dalvik虚拟机中运行得快很多。
Dalvik虚拟机成功加载库之后,就会自动地寻找库里面的JNI_OnLoad函数,这个函数用途如下:
(1)告诉Dalvik虚拟机此C库使用哪一个JNI版本。如果你的库里面没有写明JNI_OnLoad()函数,VM会默认该库使用最老的JNI 1.1版本。但是新版的JNI做了很多的扩充,也优化了一些内容,如果需要使用JNI的新版功能,就必须在JNI_OnLoad()函数声明JNI的版本。如
result = JNI_VERSION_1_4;当没有JNI_OnLoad()函数时,Android调试信息会做出如下提示(No JNI_OnLoad found)
04-29 13:53:12.184: D/dalvikvm(361): Trying to load lib /data/data/com.shift.helloworld/lib/libHelloWorld.so 0x44edea98 04-29 13:53:12.204: D/dalvikvm(361): Added shared lib /data/data/com.shift.helloworld/lib/libHelloWorld.so 0x44edea98 04-29 13:53:12.204: D/dalvikvm(361): No JNI_OnLoad found in /data/data/com.shift.helloworld/lib/libHelloWorld.so 0x44edea98, skipping init(2)因为Dalvik虚拟机加载C库时,第一件事是调用JNI_OnLoad()函数,所以我们可以在JNI_OnLoad()里面进行一些初始化工作,如注册JNI函数等等。注册本地函数,可以加快java层调用本地函数的效率。
另外:与JNI_OnLoad()函数相对应的有JNI_OnUnload()函数,当虚拟机释放该C库时,则会调用JNI_OnUnload()函数来进行善后的请出动作。
三. 开始使用JNI
java通过jni调用底层函数,目前据我分析是两种情况,第一种是通过javah得到头文件,然后对头文件中的函数进行实现;另一种,是通过JNI_OnLoad中对需要对接的函数进行注册,注册中包含了接口函数的class name等。自我感觉,应该还是第二种比较好一点,code容易管理,结构也比较清晰。第一种的机制是怎么解析的,还是需要后期进行进一步的研究。
下面来介绍这两种方法的实现:
1. java中会使用到的jni接口package com.shift.testjni; public class TestJNI { static{ System.loadLibrary("shift_jni"); } private native int printJNI(); private int test(){ return printJNI(); } }
在cmd命令窗口,cd到当前TestJNI.java的目录,然后调用命令:
或者用eclipse进行编译,会在bin/classes下面生成同样的TestJNI.class文件
3. 通过javah生成c中需要的头文件
这一步比较关键,首先要设置环境变量classpath:
注意:这里的classpath是TestJNI.class所在的包的全路径,不是TestJNI.class所在的路径哦,是包。
如果这个设置不对的话,就会出现这样的一个错误:
如果,classpath设置ok的话,就能生成头文件:
另外,javah还有几个参数或选项是需要注意的,
可以事先通过set classpath命令设置环境变量,也可以通过选项-classpath来指定这个环境变量;
可以通过-d指定生成的头文件的路径,如果不指定应该是在当前的目录下产生;
4. 生成的jni头文件
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_shift_testjni_TestJNI */ #ifndef _Included_com_shift_testjni_TestJNI #define _Included_com_shift_testjni_TestJNI #ifdef __cplusplus extern "C" { #endif /* * Class: com_shift_testjni_TestJNI * Method: printJNI * Signature: ()I */ JNIEXPORT jint JNICALL Java_com_shift_testjni_TestJNI_printJNI (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif其中的这里JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是要被JNI调用的。
5. 写本地的c程序
#include "com_shift_testjni_TestJNI.h" #include <stdio.h> JNIEXPORT jint JNICALL Java_com_shift_testjni_TestJNI_printJNI (JNIEnv *env, jobject obj) { printf("====jni test successfully==="); return 100; }
这里提供Androi.mk:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := libshift_jni LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := TestForJni.c LOCAL_C_INCLUDES := $(JNI_H_INCLUDE) include $(BUILD_SHARED_LIBRARY)
package com.shift.testjni; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.app.Activity; public class TestJNIActivity extends Activity { private static final String TAG = "TestJNIActivity"; TestJNI jni = new TestJNI(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button test1 = (Button)findViewById(R.id.test1); test1.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Log.d(TAG, "=====call JNI======"+jni.test()); } }); } }
8. 另一种方法
java层的code不变,c层所有的method通过register进行统一管理,:
#define LOG_NDEBUG 0 #define LOG_TAG "Shift_Test_JNI" #include <jni.h> #include <assert.h> #include "utils/Log.h" #ifndef NELEM #define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0]))) #endif JNIEXPORT jint JNICALL printForTest (JNIEnv *env, jobject obj) { ALOGE("====jni test successfully==="); return 0; } static JNINativeMethod methods[] = { { "printJNI", "()I", (void*)printForTest}, }; /* * Register methods for one class. */ static int registerNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* methods, int numMethods) { int rc; jclass clazz; clazz = (*env)->FindClass(env, className); if (clazz == NULL) { ALOGE("Native registration unable to find class '%s'\n", className); return JNI_FALSE; } if (rc = ((*env)->RegisterNatives(env, clazz, methods, numMethods)) < 0) { ALOGE("RegisterNatives failed for '%s' %d\n", className, rc); return JNI_FALSE; } return JNI_TRUE; } /* * Register methods for all classes. * * returns JNI_TRUE on success. */ static int registerNatives(JNIEnv* env) { if (!registerNativeMethods(env, "com/shift/testjni/TestJNI", methods, NELEM(methods))){ return JNI_FALSE; } return JNI_TRUE; } /* * Called by the VM when the shared library is loaded. */ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; ALOGE("=====JNI_OnLoad=====\n"); if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK) goto bail; assert(env != NULL); if (!registerNatives(env)) goto bail; /* success -- return valid version number */ result = JNI_VERSION_1_6; bail: ALOGE("Leaving JNI_OnLoad (result=0x%x)\n", result); return result; }
注意jni方法的使用,C&C++格式是不一样的,如下:
返回jstring:return (*env)->NewStringUTF(env, "XXX");
其中log输出部分需要依赖系统库,Android.mk更新为:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := libshift_jni LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := TestForJni.c LOCAL_C_INCLUDES := $(JNI_H_INCLUDE) LOCAL_SHARED_LIBRARIES := \ libcutils \ libutils include $(BUILD_SHARED_LIBRARY)
--------- beginning of /dev/log/main --------- beginning of /dev/log/system E/Shift_Test_JNI( 4038): =====JNI_OnLoad===== E/Shift_Test_JNI( 4038): Leaving JNI_OnLoad (result=0x10006) E/Shift_Test_JNI( 4038): ====jni test successfully=== D/TestJNIActivity( 4038): =====call JNI======0