NDK 全称是 Native Development Kit,NDK 提供了一系列的工具,帮助开发者快速开发 C(或 C++) 的动态库,并能自动将 so 和 java 应用一起打包成 apk。NDK 集成了交叉编译器 (交叉编译器需要 UNIX 或 LINUX 系统环境),并提供了相应的 mk 文件隔离 CPU、平台、ABI 等差异,开发人员只需要简单修改 mk 文件 (指出" 哪些文件需要编译 "、" 编译特性要求 "等),就可以创建出 so。
为什么使用 NDK
1、代码的保护。由于 apk 的 java 层代码很容易被反编译,而 C/C++ 库反汇难度较大。
2、可以方便地使用现存的开源库。大部分现存的开源库都是用 C/C++ 代码编写的。
3、提高程序的执行效率。将要求高性能的应用逻辑使用 C 开发,从而提高应用程序的执行效率。
4、便于移植。用 C/C++ 写得库可以方便在其他的嵌入式平台上再次使用。
JNI 的全称是 Java Native Interface,它提供了若干的 API 实现了 Java 和其他语言的通信 (主要是 C 和 C++)。
为什么使用 JNI
JNI 的目的是使 java 方法能够调用 c 实现的一些函数。
安卓中的 so 文件是什么
android 中用到的 so 文件是一个 c++ 的函数库。在 android 的 JNI 中,要先将相应的 C 语言打包成 so 库,然后导入到 lib 文件夹中供 java 调用。
Android Studio NDK 及 so 文件开发
Android Studio 从 1.3 Beta1 开始,支持了 NDK。之前则不支持,所以我们建议使用新版的编辑器。
如果未安装,点击安装下载; 打开 Tools->Android->SDK Manager->SDK Tools 选中 LLDB 和 NDK,点击确认,软件会自动安装 NDK。
安装好的 NDk 一般位于你的 sdk 文件夹下的 ndk-bundle。可以看到里面有 ndk-build 文件,下文进行编译的时候我们会用到。
然后将该路径配置到你系统变量的 path 里面去,如下:
添加完毕后打开 cmd,输入 ndk-build,出现如下内容则表示成功 (网上说是成功的,虽然显示的貌似是一些错误信息,但是后文运行的时候是没问题的可以编译成功)。
至此为止,ndk 环境变量就算是配置完成了。
如下,在 MainActiviy.java 中建立了一个方法 public native String getStrFromJNI();
可以看到这个方法的声明中有 native 关键字,这个关键字表示这个方法是本地方法,也就是说这个方法 getStrFromJNI()是通过本地代码 (C/C++) 实现的,在 java 代码中仅仅是声明。
切换到 Terminal,进入到该工程的 java 目录下 (如下图所示),然后输入
javah -jni -encoding utf-8 包名. 类名 (如下图所示)。
编译成功后,刷新下工程可以看到编译出的. h 文件,该文件只是为了辅助我们写出相应的. c 文件,使用完了即可删除。
该文件的代码如下所示:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class cn_handsomedragon_testndk_MainActivity */
#ifndef _Included_cn_handsomedragon_testndk_MainActivity
#define _Included_cn_handsomedragon_testndk_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: cn_handsomedragon_testndk_MainActivity
* Method: getStrFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_cn_handsomedragon_testndk_MainActivity_getStrFromJNI
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
其实重要的部分就是这一句代码:
JNIEXPORT jstring JNICALL Java_cn_handsomedragon_testndk_MainActivity_getStrFromJNI
(JNIEnv *, jobject);
仔细观察可以看到他是遵循 "Java_包名类名本地方法名" 来组织的 (了解到这些后我们以后就可以不生成. h 文件然后直接去写. c 文件了)。
这时我们切换到 Project,然后在 app 目录下新建 jni 文件夹,并在里面建立一个 demo.c 的 c 文件 (如下图所示)。
在 demo.c 文件中编写最基本的测试代码:
#include
jstring
Java_cn_handsomedragon_testndk_MainActivity_getStrFromJNI(JNIEnv *env,jobject thiz) {
return (*env)->NewStringUTF(env,"I`m Str from jni libs!");
}
这是就可以看出我们用的是. h 中的那行代码,稍微修改为如上格式就是我们所需要的. c 文件了。
在 jni 目录下新建 Android.mk(必须是这个名称 Android.mk) 文件,如下图所示:
编辑 Android.mk 代码:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := demo // 要生成的 so 库的名称,但实际为 libdemo.so
LOCAL_SRC_FILES := demo.c // 要使用的文件,刚才编写的 demo.c 文件
include $(BUILD_SHARED_LIBRARY)
到这时候 JNI 文件的编写就算是完成了,剩下的工作就是编译 so 文件了,这里有两种方法,一种是直接通过 ndk-build 命令编译出 so 文件直接放在 libs 下边,另外一种是在把 so 文件生成在 app\build\intermediates\ndkBuild\debug\obj\local\armeabi 中。
在控制台中,进入到工程的 app 目录下,然后输入 ndk-build(如下所示),不出问题即可编译成功。
编译完成后刷新工程,可以看到在 app 目录下生成的 libs 和 obj 文件夹,其中 libs 是有用的,obj 文件夹无用可以删除。libs 中的可以看到生成的 libdemo.so 文件。
两个必要设置
1、在 local.properties 中设置 NDK 路径,我的 NDK 示例如下:
2、在 app 的 build.gradle 的 android 节点下设置:
这两处必要的地方该修改完毕后就可以调用我们生成的 so 文件了。
在 build.gradle 中配置
- <code class="hljs tex has-numbering">
- externalNativeBuild
- <span class="hljs-special">
- {
- </span>
- ndkBuild
- <span class="hljs-special">
- {
- </span>
- path file("src
- <span class="hljs-command">
- \\
- </span>
- main
- <span class="hljs-command">
- \\
- </span>
- jni
- <span class="hljs-command">
- \\
- </span>
- Android.mk")
- <span class="hljs-special">
- }
- </span>
- <span class="hljs-special">
- }
- </span>
- </code>
完整代码如下:
- <code class="hljs livecodeserver has-numbering">
- apply plugin:
- <span class="hljs-string">
- 'com.android.application'
- </span>
- android { compileSdkVersion
- <span class="hljs-number">
- 24
- </span>
- buildToolsVersion
- <span class="hljs-string">
- "24.0.0"
- </span>
- defaultConfig { applicationId
- <span class="hljs-string">
- "com.bazhangkeji.demo01"
- </span>
- minSdkVersion
- <span class="hljs-number">
- 15
- </span>
- targetSdkVersion
- <span class="hljs-number">
- 24
- </span>
- versionCode
- <span class="hljs-number">
- 1
- </span>
- versionName
- <span class="hljs-string">
- "1.0"
- </span>
- testInstrumentationRunner
- <span class="hljs-string">
- "android.support.test.runner.AndroidJUnitRunner"
- </span>
- <span class="hljs-comment">
- // ndk {
- </span>
- <span class="hljs-comment">
- // moduleName "libspeex"
- </span>
- <span class="hljs-comment">
- // cFlags "-std=c++11 -fexceptions"
- </span>
- <span class="hljs-comment">
- // ldLibs "log"
- </span>
- <span class="hljs-comment">
- // stl "gnustl_shared"
- </span>
- <span class="hljs-comment">
- // abiFilter "armeabi-v7a"
- </span>
- <span class="hljs-comment">
- // }
- </span>
- } buildTypes { release { minifyEnabled
- <span class="hljs-constant">
- false
- </span>
- proguardFiles getDefaultProguardFile(
- <span class="hljs-string">
- 'proguard-android.txt'
- </span>
- ),
- <span class="hljs-string">
- 'proguard-rules.pro'
- </span>
- } } externalNativeBuild { ndkBuild { path
- <span class="hljs-built_in">
- file
- </span>
- (
- <span class="hljs-string">
- "src\\main\\jni\\Android.mk"
- </span>
- ) } } } dependencies { compile fileTree(dir:
- <span class="hljs-string">
- 'libs'
- </span>
- ,
- <span class="hljs-built_in">
- include
- </span>
- : [
- <span class="hljs-string">
- '*.jar'
- </span>
- ]) compile
- <span class="hljs-string">
- 'com.android.support:appcompat-v7:24.0.0'
- </span>
- compile
- <span class="hljs-string">
- 'com.android.support.constraint:constraint-layout:1.0.0-alpha3'
- </span>
- compile
- <span class="hljs-string">
- 'com.android.support:design:24.0.0'
- </span>
- testCompile
- <span class="hljs-string">
- 'junit:junit:4.12'
- </span>
- androidTestCompile
- <span class="hljs-string">
- 'com.android.support.test.espresso:espresso-core:2.2.2'
- </span>
- androidTestCompile
- <span class="hljs-string">
- 'com.android.support.test:runner:0.5'
- </span>
- androidTestCompile
- <span class="hljs-string">
- 'com.androd.support:support-annotations:24.0.0'
- </span>
- }
- </code>
在 MainActivity.java 中,载入 so 文件并调用,代码如下:
这个库 demo(完整的名字是 libdemo.so) 会在第一次使用 MainActivity 这个类的时候加载。(static 代码块声明的代码会先于 onCreate 方法执行)
观察控制台的输出,可以看到打印出来的字符串:
此时表示 so 库使用成功,之前的 jni 文件夹以及原来生成的. h 文件就可以完全删除了。当然这个 so 库你要做好文档的记录,否则到时候估计你也忘了都有哪个本地方法可以调用了。
来源: http://blog.csdn.net/tongseng/article/details/53005123