NDK
JNI
JNI,是Java Native Interface的缩写,则Java本地调用,JNI可以做到:
- Java程序中的函数可以调用Native语言写的函数,Native一般指的是C/C++编写的函数。
- Native程序中的函数可以调用Java层的函数,也就是在C/C++程序中可以调用Java的函数。
在Android中,对于一些需要高效率的执行操作(例如算法、文件读取),最好需要采用C/C++编码,因为C/C++是一门高效率的语言。
Demo
创建Android项目
布局文件:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/id_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
</RelativeLayout>
创建JNI目录
右键 app module —New—Folder—JNI Folder,在app下会建立一个jni的目录,该jni目录和java目录是处在同一层次。
新建JNI相关的类
例如 JNIClass
,其中的 native
关键词表明这个方法是本地native的方法
public class JNIClass {
public native String getContent();
}
生成 JNIClass
的头文件.h
用终端(Linux)或cmd(Windows),cd
到我们项目 YOUR_PROJECT_NAME/app/src/main/java
路径下:
javah -jni com.yao.jnidemo.JNIClass
注意:
- 注意cd到的目录,因为查找类名需要根据包名查找,所以需要cd到包名路径的上一层目录(也就是以上目录)
- 将以上
com.yao.jnidemo
换为你自己的包名,JNIClass
换为你自己创建的类名 - 类名
JNIClass
不需要加 .java 后缀 - 切勿将命令写成:
javah -jni com/yao/jnidemo/JNIClass
,否则会出现:Exception in thread "main" java.lang.IllegalArgumentException: Not a valid class name: com/yao/jnidemo/JNIClass.java
的问题
生成的头文件内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_yao_jnidemo_JNIClass */
#ifndef _Included_com_yao_jnidemo_JNIClass
#define _Included_com_yao_jnidemo_JNIClass
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_yao_jnidemo_JNIClass
* Method: getContent
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_yao_jnidemo_JNIClass_getContent
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
头文件中包含了一个函数,返回类型为 jstring
,函数名为 Java_com_yao_jnidemo_JNIClass_getContent
,我们需要在源文件 .c 中实现该函数。
移动头文件
执行上一步操作之后,在以上 YOUR_PROJECT_NAME/app/src/main/java
下就会生成一个 com_yao_jnidemo_JNIClass.h
的头文件,将该头文件剪切到我们创建的jni文件夹
创建C/C++代码
在 jni目录下,新建一个 source_file.c
的文件,实现上面提到的函数,代码如下:
#include "com_yao_jnidemo_JNIClass.h"
JNIEXPORT jstring JNICALL Java_com_yao_jnidemo_JNIClass_getContent
(JNIEnv *env, jobject obj){
return (*env)->NewStringUTF(env,"Hello JNI!");
}
修改 app
的 build.gradle
在 defaultConfig
中添加:
ndk{
moduleName "jniso" //生成的so名字
abiFilters "armeabi", "armeabi-v7a", "x86" //输出指定三种abi体系结构下的so库
}
再次修改 JNIClass.java
加载生成的库 .so 文件
public class JNIClass {
static {
System.loadLibrary("jniso");
}
public native String getContent();
}
设置NDK路径
- File — Project Structure — SDK Location — Android NDK Location
- 或者可以在
local.properties
中指定路径:,例如
sdk.dir=/home/yao/Android/Sdk
ndk.dir=/home/yao/Android/Ndk
修改 gradle.properties
添加 android.useDeprecatedNdk=true
,否则可能会出现以下问题:
Error:(14, 1) A problem occurred evaluating project ‘:app’.
Error: NDK integration is deprecated in the current plugin. Consider trying the new experimental plugin. For details, see http://tools.android.com/tech-docs/new-build-system/gradle-experimental. Set “android.useDeprecatedNdk=true” in gradle.properties to continue using the current NDK integration.
MainActivity.java
public class MainActivity extends AppCompatActivity {
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.id_textview);
mTextView.setText(new JNIClass().getContent());
}
}
通过获取一个 JNIClass
的对象,调用本地native的 getContent
方法,在 mTextView
中显示。
生成.so库
在 app/build/intermediates/ndk/debug/lib
目录下有三个文件夹: armeabi,armeabi-v7a,x86,对应三种不同abi,而每个文件夹下都有生成的 .so 文件:libjniso.so
其他问题
有的人出现了以下问题:
Error:Execution failed for task ‘:app:compileDebugNdk’.
com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: Process ‘command ‘D:\Android\android-ndk-r9d\ndk-build.cmd” finished with non-zero exit value 2
在我的项目并没有出现,但我觉得解决以上该问题:
- 首先确认jni目录所处的路径是否有错
- 重新下载、替换NDK
- 在jni目录下新建一个空的源文件,例如
empty.c