一、 环境需求:
java是安卓应用开发的主要语言,但是有时候,我们没有办法直接调用安卓提供的java接口来实现对framework层以下的开发,这个时候,就需要JNI上场了,使用java本地接口,在下面利用C/C++实现对java本地接口的实现,关于JNI编程,网上的资料和博客很多,但是有的很零散,特别是使用的AS版本和gradle版本等,导致差异和可实用性不大,在实际的学习过程中遇到了很多坑,这里通过博客重新记录一下。
①JNI开发推荐博客:
https://www.jianshu.com/p/919719964ec4
②JNI开发推荐书籍(比较老了,仅做参考):
《Android C++高级编程使用NDK》
《Android高级开发实战 UI、NDK与安全》
二、 本地环境:
Android studio:3.5
NDK:r20(由Android studio的SDK tool下载)
JDK:1.8
三、 JNI编译步骤:
- 在java类中声明本地接口:
/* JNI返回一个hello的字符串 */
public native String helloJNI();
- 借助JDK提供的javah,编译出实现本地接口的头文件。jni目录通常与java目录同级,使用AS在app/src/main下面创建jni目录,然后进入该目录,输入如下指令:
javah -d ../jni -jni com.example.jnitest.JNITest
javah是jdk提供的工具,-d是指定输出目录,生成的头文件将输出到与java目录同级的jni目录中,-jni是生成JNI样式的头文件,后面的全包名 + 类名即生成的JNI头文件前缀,执行此命令之后,生成的头文件为:
com_example_jnitest_JNITest.h
3. 在jni目录下创建对用的c/c++文件和Android.mk,Android.mk编写如下:
LOCAL_PATH := $(call my-dir) #接收当前Android.mk文件的绝对路径
include $(CLEAR_VARS) #类似于工具初始化的操作
LOCAL_MODULE := libhellojni #模块名
LOCAL_SRC_FILES := hello.cpp #依赖的源文件
LOCAL_LDLIBS := -llog #引用log对应的库文件
include $(BUILD_SHARED_LIBRARY) #将该模块编译成一个动态库
- 在c/c++文件中编写一个返回字符串的函数,在java层接收并显示:
JNIEXPORT jstring JNICALL Java_com_example_jnitest_JNITest_helloJNI(JNIEnv *env, jobject jobj) {
LOGE("hello JNI!");
return env->NewStringUTF("hello JNI!");
}
- 编译so库,编译动态库有两种方式,一种是使用Cmake,还有一种是使用NDK,这里记录使用NDK编译so库:
在Android.mk对应的目录使用cmd进行ndk-build编译:
ndk-build
编译成功如图:
可以看到,在jni同级目录下,生成了libs和obj文件夹,libs是生成多个架构下的so库,obj是生成的中间文件,因为我的测试机是arm64架构的,所以只需要将armeabi-v7a文件夹复制到app目录下的libs里面即可;
6. 在app的build.gradle中添加如下代码:
sourceSets {
main {
jni.srcDirs = ['src/main/jni', 'src/main/jni/']
jniLibs.srcDirs = ['libs']
}
}
这里的jniLibs.srcDirs可以理解为一个映射,表示JNI的库文件在APP下面的libs中去找,当然也可以自己制定路径,只要修改这个值就可以了。
注意:一定要将代表架构名的文件夹复制到app/libs下面,如果仅仅是复制库,那么apk运行起来是加载不到的,会闪退,这个原因我也不太清楚,看网上说之前的版本是可以直接将so复制到这个文件夹下面就能用的;
完整的build.gradle文件如下:
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion "29.0.1"
defaultConfig {
applicationId "com.example.jnitest"
minSdkVersion 19
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
jni.srcDirs = ['src/main/jni', 'src/main/jni/']
jniLibs.srcDirs = ['libs']
}
}
externalNativeBuild {
ndkBuild {
path file('src/main/jni/Android.mk')
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
- 在含本地方法的类中加载库:
static {
System.loadLibrary("hellojni");
}
加载库去掉前缀lib和后缀.so;
四、演示:
在java文件中调用此方法,可通过打印或者toast来显示出来。
代码路径:
https://github.com/jiyi666/apk-demo/tree/master/JNITest