NDK学习笔记(一)
我们都知道Android应用是用Java写的,但是有时候我们需要使用C/C++来进行开发,或者说需要与底层进行一些交互。Java本身提供了JNI方法,但是在Android开发中其存在技术障碍。比如程序更加复杂,兼容性难以保障,无法访问Framework API,Debug难度更大等。于是于是NDK就应运而生了。NDK全称是Native Development Kit。NDK的发布,使“Java+C”的开发方式终于转正,成为官方支持的开发方式。NDK将是Android平台支持C开发的开端。
使用NDK的优点
- 便于移植,用C/C++写得库可以方便在其他的平台上再次使用。
- 代码的保护,由于java层代码很容易被反编译,而C/C++库反编译难度较大。
- 提高程序的执行效率,将要求高性能的应用逻辑使用C/C++开发,从而提高应用程序的执行效率。
- 访问现有开源库,需要访问底层的API或引用一些只有C/C++的库。
配置NDK环境
如果你已经下载了NDK,那么就可以设置其路径,在File->Project Structure里的Android NDK location,默认为你的SDK路径\ndk-bundle。如果没有下载,直接点击download下载。当然你也可以
自己下载其它版本放置任意位置,然后手动设置路径。
设置成功的话,会在Project的local.properties文件下添加NDK的路径:
ndk.dir=D\:\\Android\\Sdk\\ndk-bundle
如果没有,可手动添加。另在Build时,如果出现错误如下:
Error:(12, 0) 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.
是由于你的NDK版本与AS版本不一致造成的,只需在gradle.properties文件下添加一行:android.useDeprecatedNdk=true
即可。
(如果你想使用命令行的话,需要添加系统环境变量,新建变量名ANDROID_NDK_HOME,对应变量值为ndk根目录地址 ,即$SDKpath\ndk-bundle,然后将ANDROID_NDK_HOME添加到Path中去(%ANDROID_NDK_HOME%;),确定OK.)
自己编写Native文件
好了,现在我们可以来编写自己的第一个NDK应用了。最简单的例子,C输出一个字符串,然后在Android里调用显示出来。首先还是跟平常一样,New一个Project。接着New一个Java Class,我们将其命名为JniUtils。我们将在这里加载动态库,创建native方法。(其实也可以在MainActivity里做这些,但是为了让代码看起来更清晰,我们还是选择分开写。另外你也可以不在app Module里做这些,而是放在一个独立的module里。)
先创建一个native方法,即JNI接口,它是衔接C/C++与Java之间的桥梁。
public class JniUtils {
static{
System.loadLibrary("ndk");
}
public static native String getStringFromC();
}
这个时候我们看到getStringFromC的颜色是红色的,那是因为没有找到这个方法的实现。接下来我们就要编写这个方法了。
如果默认是Android视图的话,调整至Project视图。在$ProjectName/app/src/main下,New一个Folder,类型为JNI Folder。这个目录下主要存放jni文件,即c/cpp文件以及头文件等。接下来创建C文件有两种方式,取决于你的gradle版本,我们分开说,比较一下:
稳定版插件
如果你的Project下的build.gradle类似这样:
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0'
}
既是稳定版插件。
上一步完成后clean project 再rebuild project ,看看$ProjectName/app/build/intermediats/classes/是否存在,再打开Terminal输入指令:
cd app/build/intermediates/classes/debug
然后再输入指令:
javah -jni com.example.yaoobs.ndkjnidemo.JniUtils
发现classes/debug下多了一个com_example_yaoobs_ndkjnidemo_JniUtils.h。将其剪切至之前创建的JNI Folder下,New一个C/C++ Source File,命名为jniUtils.c。按照com_example_yaoobs_ndkjnidemo_JniUtils.h里面的方法名修改jniUtils.c。
实验性插件(Gradle Experimental Plugin)
Gradle Experimental Plugin基于Gradle的新特性开发,目的在于减少项目配置时间,并提供更好的NDK支持。
(注意,这只是一个插件,需要gradle本身的支持,并且与其版本对应。笔者的gradle版本是2.1.0,对应的gradle-experimental是 0.7.0。)
使用插件,我们需要先在项目的build.gradle里修改gradle的引用:
buildscript {
...
dependencies {
// classpath 'com.android.tools.build:gradle:2.1.0'
classpath 'com.android.tools.build:gradle-experimental:0.7.0'
}
...
}
接着修改module下的build.gradle,这里有一些语法上的不同,旧的注释掉了,可以对比着看:
//apply plugin: 'com.android.application'
apply plugin: 'com.android.model.application'
//
//android {
// compileSdkVersion 23
// buildToolsVersion "23.0.2"
// defaultConfig {
// applicationId "com.example.yaoobs.ndkjnidemo"
// minSdkVersion 14
// targetSdkVersion 23
// versionCode 1
// versionName "1.0"
// ndk {
// moduleName = "ndk"
// abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a', 'mips', 'x86_64'
// }
// buildTypes {
// release {
// minifyEnabled false
// proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// }
// }
//}
model {
android {
compileSdkVersion = 23
buildToolsVersion = "23.0.2"
defaultConfig.with {
applicationId = "com.example.yaoobs.ndkjnidemo"
minSdkVersion.apiLevel = 14
targetSdkVersion.apiLevel = 23
versionCode = 1
versionName = "1.0"
}
ndk {
moduleName = "ndk"
toolchain = 'clang' //必加项,否则c文件报错
CFlags.addAll(['-Wall'])
}
buildTypes {
release {
minifyEnabled = false
proguardFiles.add(file('proguard-rules.txt'))
}
}
productFlavors {
create("arm") {
ndk.abiFilters.add("armeabi")
}
create("arm7") {
ndk.abiFilters.add("armeabi-v7a")
}
create("arm8") {
ndk.abiFilters.add("arm64-v8a")
}
create("x86") {
ndk.abiFilters.add("x86")
}
create("x86-64") {
ndk.abiFilters.add("x86_64")
}
create("mips") {
ndk.abiFilters.add("mips")
}
create("mips-64") {
ndk.abiFilters.add("mips64")
}
create("all")
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha1'
compile 'com.android.support:appcompat-v7:23.0.0'
}
完成后你就会发现JniUtils.class里的native方法getStringFromC(),按Alt+Enter会出现提示,如下图:
回车,会发现JNI Folder下创建了一个jniutils.c文件。
编写Native文件
首先,添加头文件,类似java中的导包,这里首先必加的是jni.h,这是java与c/c++之间语言转换的核心文件,具体可以查看ndk目录下D:\android-sdk\ndk-bundle\platforms\android-23\arch-arm\usr\include\jni.h,另一个这里需要处理字符串,所以还需要包含string.h的头文件,同样可以在上面目录include中找到.
然后,添加java中native方法的实现。jstring:返回值类型,Java_com_example_yaoobs_HelloJni_stringFromJNI(JNIEnv *env, jobject jobj):实现的方法名,固定格式,Java_所要实现的方法名所在java类用下划线替代点的引用地址_方法名(JNI环境变量 env,JNI环境对象 jobj);其中env和jobj方法中可能用不到,但也必须申明,源码中是这样说明的,大概作用就是env作为了一个JNINativeInterface指针,是java与c/c++之间的一个功能环境变量中间桥梁,我们先不深入。
完整的jniutils.c:
#include <string.h>
#include <jni.h>
jstring
Java_com_example_yaoobs_ndkjnidemo_JniUtils_getStringFromC( JNIEnv* env, jobject thiz ) {
return (*env)->NewStringUTF(env, "Hello NDK! ");
}
最后在Activity里调用:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((TextView)findViewById(R.id.txt)).setText(JniUtils.getStringFromC());
}
}
运行成功:
直接使用动态链接库
大部分情况下,我们使用NDK不需要自己编写Native文件,只要把动态链接库,即.so文件直接拿来用就好了。许多第三方SDK都提供了完整的解决方案。
AS中so文件存放的目录默认是在main/jniLibs下,不过我们还是习惯把so文件和jar包一样都放在app/libs下,不过需要在gradle中添加一项配置,如下:
android {
...
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
...
这样在系统加载so的时候会去libs目录下加载。
接着刚才的例子,在app\build\intermediates\ndk\debug\lib下面,会发现各种架构的so文件,命名是以lib+modulename组成的。
你可以把它们全部复制到libs下面,然后删掉jniutils.c,看看运行会不会报错。
源码已上传GitHub,地址:https://github.com/Yaoobs/NdkJniDemo
参考文章:
http://blog.csdn.net/yilip/article/details/45200861
http://www.jianshu.com/p/9aff422204eb
http://www.jianshu.com/p/d8cde65cb4f7
http://www.taoweiji.cn/2016/08/02/ndk/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
http://www.cnblogs.com/zhuyuliang/p/5007016.html