1、什么是JNI
JNI全称 Java Native Interface , java本地化接口 。简单的说,他就是JAVA语言与C/C++语言的桥梁,JAVA函数的API可以通过JNI映射到Native层C/C++的API接口,反向调用亦可。他们的调用只是一个简单映射关系,仍然在一个进程中。
2、NDK
NDK(Native Development Kit): 开发JNI必备工具,就是模拟其他平台特性类编译代码的工具. 交叉编译工具。使用NDK可以在window平台下直接去开发和编译Native层代码,方便调试。(NDK工具需下载,并配置环境)
3、动态方式实现JNI
可以仿照原生JNI书写方式,写一个函数API对应表,这样就可以不依赖于eclipse去生成.h头文件。编译jni也可以直接用ndk-build -C 指令编译成.so库。
JNI_OnLoad是java jni技术的一个实现,每次java层加载 System.loadLibrary 之后,自动会查找改库一个叫 JNI_OnLoad 的函数,动态注册的时候,cpp可以通过实现 JNI_OnLoad 函数完成jni的动态注册。
Jni中本地类型和java类型对应表,参考 chuekup blog https://blog.csdn.net/chuekup/article/details/8030038
直接举个例子吧,这个例子包含了(代码较为简单,有注释)
- JAVA -> JNI函数调用;
- JNI -> JAVA 方向的回调;
3.1 在JAVA 文件中创建需要调用到jni的函数
JAVA层 定义了三个函数,如下:
public native void startTest();
public native void stopTest();
public native void testfunc1();
MainActivity.java
package com.example.jnitest;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends Activity {
private Button mBtnTest1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
System.loadLibrary("jintest");
initView();
startTest();
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
stopTest();
}
private void initView() {
mBtnTest1 = (Button) findViewById(R.id.btn_test1);
mBtnTest1.setOnClickListener(listener);
}
public void onNotifyData(byte[] data) {
Log.d("Main", "onNotifyData length " + data.length);
}
private View.OnClickListener listener = new View.OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
switch (arg0.getId()) {
case R.id.btn_test1:
Log.d("Main", "testfunc1 onClick");
testfunc1();
break;
default:
break;
}
}
};
public native void startTest();
public native void stopTest();
public native void testfunc1();
}
3.2、创建C++类型文件,实现 JNI_OnLoad函数;
TestJniNative.h TestJniNative.cpp
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <android/log.h>
#include "TestJniNative.h"
//全局变量
JNIEnv* g_env = NULL;
JavaVM *g_jvm = NULL;
jobject g_obj = NULL;
jclass cls;
jmethodID method_id;
static BYTE tmp[2];
// 和JAVA中的包名路径一样
static const char* const kClassPathName = "com/example/jnitest/MainActivity";
void TestJniNative_onNotifyData(BYTE *data, UINT32 length) {
jbyte *by = (jbyte*)data;
jbyteArray jarray = g_env->NewByteArray(length);
g_env->SetByteArrayRegion(jarray, 0, length, by);
g_env->CallVoidMethod(g_obj, method_id, jarray);
g_env->DeleteLocalRef(jarray);
}
// 对全局变量进行赋值,并查找java 类的中的方法
JNIEXPORT void TestJniNative_startTest(JNIEnv* env, jobject obj) {
if(g_obj) {
g_env->DeleteGlobalRef(g_obj);
}
g_obj = env->NewGlobalRef(obj);
g_env= env;
cls = g_env->GetObjectClass(g_obj);
method_id = g_env->GetMethodID(cls, "onNotifyData", "([B)V");
}
JNIEXPORT void TestJniNative_stopTest(JNIEnv* env, jobject obj) {
if(g_obj) {
g_env->DeleteGlobalRef(g_obj);
g_obj = NULL;
}
}
JNIEXPORT void TestJniNative_testfunc1(JNIEnv* env, jobject obj) {
LOGI("TestJniNative_testfunc1");
memset(tmp, 0x00, 2);
TestJniNative_onNotifyData(&tmp[0], 2);
}
// 结构体,分别是java层的函数名称,签名,对应的函数指针
static JNINativeMethod gMethods[] = {
{ "testfunc1", "()V", (void *) TestJniNative_testfunc1 },
{ "startTest", "()V", (void *) TestJniNative_startTest },
{ "stopTest", "()V", (void *) TestJniNative_stopTest },
};
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods) {
jclass clazz;
g_env = env;
clazz = env->FindClass(className);
if (clazz == NULL)
return JNI_FALSE;
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
static int registerDriveRecordNatives(JNIEnv *env) {
return registerNativeMethods(env, kClassPathName, gMethods,
sizeof(gMethods) / sizeof(gMethods[0]));
}
//System.loadLibrary 后,首先会调用 JNI_OnLoad函数
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
assert(env != NULL);
// 关联JAVA 类中的函数
if (registerDriveRecordNatives(env) < 0) {
return -1;
}
// 返回版本号
return JNI_VERSION_1_4;
}
3.3 创建Android.mk文件进行编译
mk文件仿照Android 原生代码进行编写,在此是生成一个动态库(libjintest.so)
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := jintest
LOCAL_SRC_FILES := \
TestJniNative.cpp \
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
#LOCAL_C_INCLUDES += \
# DigitalTachoNative.h \
# DigitalTachoConnect.h
LOCAL_LDLIBS += -llog
LOCAL_SHARED_LIBRARIES := libutils
include $(BUILD_SHARED_LIBRARY)
3.4 编译
如果对eclipse等工具进行指令配置的话,就可以在编译apk的时候把jni层也编译了,但是每个项目都需要进行指令配置,也很麻烦。也可以直接用 ndk-build -C 目录 指令在 进行编译,生成so库后,再去通过工具生成apk文件。
采用 ndk-build -C xxx/xxx/jni 指令进行编译,编译OK后会生成.so文件, 生成路径 libs/armeabi/ 下面;
3.5 生成apk
直接run或者build apk 即可将 .so也打包进 apk文件。