ndk开发基础学习笔记系列:
JNI和NDK编程(一)JNI的开发流程
JNI和NDK编程(二)NDK的开发流程
JNI和NDK编程(三)JNI的数据类型和类型签名
JNI和NDK编程(四)JNI调用Java方法的流程
NDK的开发流程
NDK的开发是基于JNI的, 主要有如下步骤
1. 下载并进行配置NDK
首先要下载NDK. 这里我们直接在Android SDK Manager里下载.
然后为NDK配置环境变量, 步骤如下所示.
首先打开当前用户的环境变量配置文件:
vim ~/.bashrc
然后在文件后面添加如下信息:
export PATH=~/Documents/Android/android-sdk-linux/ndk-bundle:$PATH , 其中 ~/Documents/Android/android-sdk-linux/ndk-bundle 是本地NDK的存放路径.
添加完毕后, 执行 source ~/.bashrc来立刻刷新刚刚设置的环境变量. 设置完环境变量后, ndk-build命令就可以使用了, 通过ndk-build命令就可以编译产生so库.
注意: 如果使用了zsh的话上面所有用到bashrc的地方都需要替换成zshrc
2. 创建一个android项目, 在类中声明所需的native方法
public class MainActivity extends AppCompatActivity {
static{
System.loadLibrary("jni-test");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = (TextView) findViewById(R.id.msg);
textView.setText(get());
set("hello world from JniTestApp");
}
public native String get();
public native void set(String str);
}
3. 实现Android项目中所声明的native方法
在外部创建一个名为jni的目录, 然后在jni目录下创建3个文件: test.cpp, Android.mk, Application.mk, 它们的实现如下:
# test.cpp
#include <jni.h>
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
jstring Java_com_gavinandre_jnitestapp_MainActivity_get(JNIEnv *env, jobject thiz){
printf("invoke get in c++\n");
return env->NewStringUTF("Hello from JNI in libjni-test.so !");
}
void Java_com_gavinandre_jnitestapp_MainActivity_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
# Android.mk文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# 生成so的文件名称
LOCAL_MODULE := jni-test
# 所关联的cpp文件
LOCAL_SRC_FILES := test.cpp
include $(BUILD_SHARED_LIBRARY)
# Application.mk文件
APP_ABI := armeabi
这里对两个mk文件里的参数做下简单介绍:
-
- LOCAL_MODULE: 表示模块的名称
- LOCAL_SRC_FILES: 表示需要参与的源文件
-
- APP_ABI: 表示CPU架构平台的类型. 如armeabi, x86, mips 等. 如果使用all则表示编译所有CPU平台的so库
4. 切换到jni目录的父目录下, 通过ndk-build命令编译产生so库
ndk-build命令会默认指定jni目录为本地源码目录, 如果源码存放的目录名不是jni, 那么ndk-build则无法成功编译.
NDK会创建一个和jni平级的目录libs, libs下面存放的就是so库的目录.
然后在app/src/main中创建一个名为jniLibs的目录, 将生成的so库复制到jniLibs的目录, 将生成的so库复制到jniLibs目录中, 然后通过AndroidStudio编译运行即可, 运行效果如下图所示. 这说明从Android中调用so库中的方法已经成功了.
在上面的步骤中, 需要将NDK编译的so库放置到jniLibs目录下, 这个是AndroidStudio所识别的默认目录, 如果想使用其他目录, 可以按照如下方式修改App的build.gradle文件, 其中jniLibs.srcDir选项指定了新的存放so库的目录.
android {
...
sourceSets.main {
jniLibs.srcDir 'src/main/jni_libs'
}
}
除了手动使用ndk-build命令创建so库,还可以通过AndroidStudio来自动编译产生so库,这个操作过程要稍微复杂一些. 为了能够让AndroidStudio自动编译JNI代码, 首先需要在App的build.gradle的defaultConfig区域内添加NDK选项, 其中moduleName指定了模块的名称, 这个名称指定了打包后的so库的文件名, 如下所示.
android {
...
defaultConfig {
applicationId "com.gavinandre.jnitestapp"
minSdkVersion 9
targetSdkVersion 23
versionCode 1
versionName "1.0"
ndk {
moduleName "jni-test" //和System.loadLibrary("jni-test");里面加载的一致
}
}
}
接着需要将JNI的代码放在app/src/main/jni目录下, 注意存放JNI代码的目录名必须为jni, 如果不想采用jni这个名称,可以通过如下方式来指定JNI的代码路径, 其中jni.srcDirs指定了JNI代码的路径:
android {
...
sourceSets.main {
jni.srcDirs 'src/main/jni_src'
}
}
经过了上面的步骤, AndroidStudio就可以自动编译JNI代码了,但是会把所有CPU平台的so库都打包到apk中, 一般实际开发中只需要打包armeabi平台的so库即可. 要解决这个问题也很简单, 按照如下方式修改build.gradle的配置,然后在Build Variants面板中选择armDebug选项进行编辑就可以.
android {
...
productFlavors {
arm {
ndk{
abiFilter "armeabi"
}
}
x86 {
ndk{
abiFilter "x86"
}
}
}
}
也可直接在ndk节点内使用abiFilters参数来进行指定
android {
...
defaultConfig {
...
ndk {
...
abiFilters "armeabi" //可以不写,那么就是全平台
}
}
}
如下所示,可以看到apk中就只要armeabi平台的so库了.