JNI是 Java Native Interface 的缩写,它的主要作用是提供了若干API来实现Java和其他语言的通信(主要是C和C++)。
NDK是 一系列工具的集合 ,它可以帮助开发者快速开发C(或者C++)的动态库(也称So库),并So库和Java应用一起打包。 JNI的使用就是需要将C(或者C++)代码编译成动态库供Java方法调用。
本文的内容是基于Android Studio的,其他使用命令行或者其他工具生成So库的方法请另行查找。
首先在Android Studio中的Preferences找到Android SDK,并在SDK Tools这一栏中找到NDK,然后下载。如下图所示:
下载完NDK后,去local.properties文件中查看是否定义了ndk的存储路径,如下图所示:
然后到gradle.properties文件中添加 android.useDeprecateNdk=true
,表示我们的应用需要使用NDK,如下图所示:
最后在app/build.gradle中配置ndk指定So库的名字为jni-test(如果不指定则生成的So库的默认名字为app),如下图所示:
如下图所示,我们定义了一个Java调用类。如下图所示:
Java调用类的说明: 1、静态代码块表明了该Java类需要加载哪一个So库,例子中的jni-test是So库的名称。(注意:实际上NDK生成的So库的名称为 libjni-test.so ,但是So库的名称就是jni-test) 2、 native 关键字声明了get()和set()两个方法,这两个方法需要在C(或者C++)中实现。
首先我们执行一下Android项目的build命令,在app目录的build文件夹下,路径为intermediates/classes/debug(release)/xx/xx/目标.class。
然后我们通过命令行进入到app/build/intermediates/classes/debug目录下,并输入命令 javah -jni com.example.runningh.mydemo.TestJNI
生成**.h头文件**。 如下图所示:
这里需要注意的是,我们不能直接进入到TestJNI.class所在的文件夹,然后使用 javah -jni TestJNI
命令生成头文件,这样会报找不到类文件的错误。我们需要进入的是包名文件夹的上一层,上面的例子就是debug文件,然后通过 javah -jni com.example.runningh.mydemo.TestJNI
这样的形式来生成头文件。
接着我们在app/src/main目录下建立名为 jni 的文件夹(注意:文件夹的名字只能为jni,其他名字ndk是识别不了的),并将上面生成的头文件拷贝到jni文件夹中,然后我们在jni文件夹中新建一个C++文件,名字随便命名,C++文件内容如下所示:
#include <jni.h> #include "com_example_runningh_mydemo_TestJNI.h" #include <stdio.h> #ifdef __cplusplus extern "C" { #endif JNIEXPORT jstring JNICALL Java_com_example_runningh_mydemo_TestJNI_get(JNIEnv *env, jobject thiz) { printf("invoke get in c++/n"); return env->NewStringUTF("Hello from JNI int libjni-test.so!"); } JNIEXPORT void JNICALL Java_com_example_runningh_mydemo_TestJNI_set(JNIEnv *env, jobject thiz, jstring string) { printf("invoke set from c++/n"); char* str = (char*)env->GetStringUTFChars(string, NULL); printf("%s/n", str); env->ReleaseStringUTFChars(string, str); } #ifdef __cplusplus } #endif 复制代码
该C++文件包含了上面的头文件,并对Java文件定义的get和set方法进行了编写。 头文件和C++文件的目录如下图所示:
再次使用Android项目的build命令,此时会发生错误,原因是在gradle3.0以上, android.useDeprecatedNdk=true
这种方法不再支持了。但是它还是给出了两种解决方法: 1、在gradle.properties中使用 android.deprecatedNdkCompileLease=1526577754228
替换 android.useDeprecatedNdk=true
,这样可以继续使用60天。 2、使用CMake工具或者ndk-build工具。
我们先讲第一种方法,在gradle.properties中使用 android.deprecatedNdkCompileLease=1526577754228
,然后重新build,可以看到在build/intermediates/ndk/debug/lib下生成了不同的CPU版本So库。
然后我们将需要用到的CPU版本对应的So库复制到Android项目中,即在app/src/main文件夹下新建jniLibs文件夹,将我们的So库复制到jniLibs文件夹中(注意:Android Studio只能识别jniLibs文件夹中So库,如果需要改变So库的识别路径,可以在app/build.gradle中指定jniLibs.srcDir,如下所示:
android { compileSdkVersion 27 defaultConfig { applicationId "com.example.runningh.mydemo" minSdkVersion 16 targetSdkVersion 27 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" ndk { moduleName "jni-test" } } sourceSets.main { jniLibs.srcDir 'src/main/jni_libs' } } 复制代码
上面就指定了So库的识别路径为src/main/jni_libs目录。
我们在MainActivity中调用了TestJNI对象的test()方法,而上面提到的TestJNI对象又调用了本地方法get()和set(string),最终我们成功的将本地方法调起。
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new TestJNI().test(); } } 复制代码
首先在Android Studio中的Preferences找到Android SDK,并在SDK Tools这一栏中找到NDK、CMake和LLDB,然后下载。如下图所示:
下载完NDK后,去local.properties文件中查看是否定义了ndk的存储路径,该步骤和方法一是一致的。
然后在app/build.gradle中配置使用CMake工具以及CMake配置文件的路径(注意:这里是相对路径,图中的CMakeLists.txt文件在Android项目的 app 目录下)
最后在我们看一下CMakeLists.txt文件的内容:
#cmake最小版本 cmake_minimum_required(VERSION 3.4.1) add_library( # 设置so文件名称. jni-test # 设置这个so文件为共享. SHARED # 设置 c文件源码位置. src/main/jni/test.cpp ) 复制代码
我们只需要设置一下要生成的So库的名称,C(或者C++)文件的位置,So库名称、设置So库为共享、设置C文件源码位置之间需要用空格隔开)
和方法一的步骤一致。
和方法一的步骤一致。
调用build命令编译Android项目,可以在build/intermediates/cmake/debug(release)/obj生成不同的CPU版本So库。如下图所示:
然后我们将需要用到的CPU版本对应的So库复制到Android项目中,步骤和方法一的一致。