文章出处:https://blog.csdn.net/shift_wwx/article/details/25715751
前言:一直没有时间研究这个小玩意,但是近期会碰到这一块,那么就总结一下,方便后期查看。其中很多知识点还没有完善,因为时间有限,后期一定要详细分析。
详细可以看: JNI 再分析
一.JNI的原理图
直接到官方教程那里截的….
二. 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();
}
}
2. 通过javac命令得到class文件
在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;
}
这里编译可以在eclipse上编译(可以通过NDK),也可以在linux下编译(通过Android.mk)出so。
这里提供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)
6. 将so添加到eclipse上,运行测试程序
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());
}
});
}
}
7. 结果
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++格式是不一样的,如下:
C 格式:(*env) -> <jni function> (env, <parameters>)
返回jstring:return (*env)->NewStringUTF(env, "XXX");
C++ 格式:env -> <jni function> (<parameters>)
返回jstring:return env->NewStringUTF("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
参考文档:http://developer.android.com/training/articles/perf-jni.html