最近做安卓项目需要使用到JNI的知识调用C文件,在网上搜寻了近两天的资料踩了很坑,总结了一套在安卓中使用JNI的步骤,记录在这里顺便提供给像我一样刚接触的朋友们做一个参考。
JNI的用途就是实现在Java代码中调用c/c++语言的方法,具体的原理之类的我就不说了百度说的很多,先介绍下环境
开发环境
Android Studio 4.2.2
SDK:API 30
SDK Build-Tools:30.0.3
NDK :21.4.7075529(下载于Android Studio )
项目:minSdk 24,targetSdk 30
环境基本不会影响最终结果,但还是列出来以防万一,下面直接上步骤
实现步骤
-
在AS中的项目右键创建文件夹jni,选择下方自动弹出的src/main/jni(蓝色文件夹)
-
在java源码目录下创建一个工具类用来保存native方法,如
package com.example.testaidl; public class JNIHelper { static { // 加载库文件 System.loadLibrary("JNIHelper"); } public static native String getName(); }
这时AS会说找到不对应的本地方法的实现,先不管,接着往下走
-
到工具类文件的所在目录使用javac命令编译出class文件
-
在当前目录新建一个文件夹,把class文件放进去,再在新建出来的文件夹下运行javah -classpath 绝对路径 packagename.classname
- packagename为包名,使用点号隔开
- 如
javah -classpath D:\WorkSpace\MyFirstApp\testaidl\src\main\java com.example.testaidl.JNIHelper
注意包名前有一个空格
-
在新建的文件夹中即会生成 .h头文件
-
(非必须)
将头文件中的#include <jni.h>
改成#include "jni.h"
-
到jdk目录中找到jni.h和jni_md.h文件,拷贝到项目jni目录中,第五步自动生成的.h文件及c/c++源码也一起拷贝到jni目录中
需要使用对应系统版本的jdk(win对win,linux对linux),不同系统的jdk里面的jni.h和jni_md.h文件不同,会导致编译失败
-
在jni目录中编写两个mk文件(直接New File创建)
-
Android.mk
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) #指定生成的so库文件名称 LOCAL_MODULE:= JNIHelper #指定对应的c源码,多个源码间用空格分隔 LOCAL_SRC_FILES:= JNIHelper.c LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) include $(BUILD_SHARED_LIBRARY)
-
Application.mk
#需要与Android.mk文件中的LOCAL_MODULE一致 APP_MODULES = JNIHelper APP_PLATFORM := android-24 APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 APP_STL := c++_shared ifeq ($(NDK_DEBUG), 1) APP_OPTIM = debug APP_CFLAGS = -g -O0 else APP_OPTIM = release APP_CFLAGS = -O3 endif
-
-
在项目AndroidManifest.xml中添加
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="30"/>
-
在module的build.gradle文件中添加
defaultConfig{ #注意这个ndk节点是写到AndroidManifest对应的defaultConfig节点中 ndk{ //设置so库文件名称 moduleName "JNIHelper" // 声明创建指定cpu架构的so库 abiFilters "arm64-v8a", "armeabi-v7a", "x86" } } android{ #注意这个externalNativeBuild节点是写到AndroidManifest对应的android节点中 externalNativeBuild{ ndkBuild{ path "src/main/jni/Android.mk" } } }
-
在AndroidStudio中下载NDK,随后重新build module,build通过后直接到项目jni目录运行
ndk-build命令
(需要提前为NDK配置环境变量或直接使用绝对路径),成功后就会在项目src/main目录下生成libs文件夹,其中有对应不同架构的so库文件- 可能会遇到include的头文件不存在报错的问题,这是因为新版本的ndk不再附带platforms/android-xx(api版本)/arch-xx(架构)/usr/include文件夹,头文件本应当根据不同的架构放置在不同架构文件夹中的此处。
- 解决可以通过下载ndk12b(谷歌官网下载地址:https://developer.android.google.cn/ndk/downloads/older_releases?hl=de),解压
/android-ndk-r12b/platforms/android-24/
目录下各个架构的include文件夹进新版ndk,具体拷贝到新版ndk的哪个api文件夹则取决于你项目的targetSdk的api版本 - 或将/android-ndk-r12b/platforms/android-24/arch-x86_64/usr/中的include文件单独拷贝出来,然后在Android.mk文件中添加LOCAL_C_INCLUDES属性指定include文件夹位置,如LOCAL_C_INCLUDES := /home/local/Android/include,但此方法能否适配不同架构的系统还未能验证,建议使用第一种解决方式
- 解决可以通过下载ndk12b(谷歌官网下载地址:https://developer.android.google.cn/ndk/downloads/older_releases?hl=de),解压
- 可能会遇到include的头文件不存在报错的问题,这是因为新版本的ndk不再附带platforms/android-xx(api版本)/arch-xx(架构)/usr/include文件夹,头文件本应当根据不同的架构放置在不同架构文件夹中的此处。
-
在JNIHelper类中的本地方法已可使用