什么是JNI
JNI为Java Native Interface(Java本地接口),它是为Java调用c/c++封装的一个接口,可以说是Java代码与C/C++之间互调的一个桥梁。
什么是NDK
NDK是Android提供的工具集,使用它可进行交叉编译,帮助开发者快速开发C/C++的动态库。
使用NDK的好处
- 安全性:so库被反编译很困难
- 方便性:可以使用当前已有的C/C++开源库
- 移植性:C/C++的库可到别的平台上运用
前期配置
- 下载NDK
- 关联
local.properties文件下
ndk.dir=E\:\\Android\\sdk\\ndk-bundle
为了兼容老的NDK
gradle.properties文件下
android.useDeprecatedNdk=true;
- 为了方便,可在File–>Setting–>Tools–>External Tools中进行以下配置
配置将Java代码中的本地方法编译成C/C++头文件
配置将C/C++文件编译成so文件
开发流程
创建一个类,并在类里面写方法(桥梁)
public native String sayHello();
在gradle中的defaultConfig配置
ndk{
moduleName "JniTest" //so文件:lib+moduleName+.so
abiFilters "armeabi","armeabi-v7a","arm64-v8a","x86" ,"mips","mips64"//cpu类型
}
3.添加两个mk文件
- Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := JniTest//编译到的so库
LOCAL_SRC_FILES := hello.c//要编译的c文件
LOCAL_SRC_FILES := Test.c
include $(BUILD_SHARED_LIBRARY)
- Application.mk
APP_ABI :=all//表示编译为所有指定类型的so文件
4.在刚刚创建的Java类(含有本地方法native)中右键,External Tools->javah -jni,等待编译后便会生成一个C/C++的头文件,如下图
5.创建C/C++文件
#include "com_sendi_ndkdemo_JNITest.h"
/*
jstring:返回值
Java_类全名_方法名
JNIEnv* env:里面有很多方法
jobject jobj:谁调用这个方法就是谁的实例
*/
JNIEXPORT jstring JNICALL Java_com_sendi_ndkdemo_JNITest_sayHello
(JNIEnv *env, jobject jobj){
// char* text="I am from c";
return (*env)->NewStringUTF(env,"I am from c");
}
6.生成so文件
右键c/c++文件,External Tools–>ndk -build,然后生成相应的包
7.接下来是在main文件下创建一个文件名为jniLibs的文件夹,将刚刚生成的libs里面的内容拷贝过来
OK,接下来正式进入使用阶段
Java调用C
这里举一个简单的例子:
首先创建一个刚刚定义了native方法的类对象,然后直接调用该native方法
String result=new JNITest().sayHello();
Log.i(TAG, "onCreate: result==="+result);
运行后会发现报错,原因是没有加载我们编译好的库(这里比较容易忘记),在刚刚的JNI类里边添加:
{
System.loadLibrary("JniTest");//表示加载名字为JniTest的库
}
这时候在运行一次,发现成功了。
C调用Java
C调用Java其实就是Java先调用C代码,然C代码里面通过反射对Java的代码进行调用,下面举个简单的例子
- 创建一个本地方法和待C调用的方法
/**
* 调用此方法后,让C代码调用下边的方法
*/
public native void callbackHelloFromJava();
public void helloFromJava() {
Log.i("SENDI", "c call Java");
}
- 然后进行编译出.h文件
- c文件的代码
#include "com_sendi_ccalljava_JNI.h"
#include <stdlib.h>
#include <stdio.h>
JNIEXPORT void JNICALL Java_com_sendi_ccalljava_JNI_callbackHelloFromJava
(JNIEnv *env, jobject jobj){
//获得字节码
//第二个参数为要调用Java函数所在的类全名
jclass jclazz=(*env)->FindClass(env,"com/sendi/ccalljava/JNI");
//得到方法
//最后一个参数为方法签名
jmethodID jmethodIDs=(*env)->GetMethodID(env,jclazz,"helloFromJava","()V");
//获得该实例
jobject jobject=(*env)->AllocObject(env,jclazz);
//调用方法
(*env)->CallVoidMethod(env,jobject,jmethodIDs);
};
获取方法签名
1. 编译
2. 进入到build-intermadiates-classes-debug
3. 执行javap -s 全类名
- 编译成so文件,步骤跟上述相同。
- 在Java中调用本地方法,步骤依然相同
- 最后可以看到打印
SENDI :c call Java
在这过程中有问题可以留言或评论,大家一起交流交流。