环境配置
以Android studio 2.2为例,点击tools->Android->SDKManager。
勾选并下载 CMake、LLDB、NDK:
CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性;
LLDB:调试本地代码工具;
NDK:Android 和 C/C++交互的工具。
点击ok后会进入下载安装界面,速度视网速而定。
下载完成后,在SDK目录下会多出一个NDK文件夹:
然后需要配置下系统的环境变量:
在用户变量里添加刚刚存放ndk-bundle的路径。
新建项目
- 新建一个项目:
注意点选include c++ support,因为AS对c语言的支持不够好,如果不选直接创建jni项目虽然可以运行但是某些地方会被标注为红色且无法使用提示功能。
其他一路next就好。 - 因为我们选择了支持c++,所以local.properties里自动添加了相关代码
ndk.dir=D\:\\toolSoftwore\\androidSDK\\ndk-bundle
sdk.dir=D\:\\toolSoftwore\\androidSDK
如果只是普通的项目,需要添加ndk.dir=D:\toolSoftwore\androidSDK\ndk-bundle。
3. 在gradle.properties里面声明使用NDK的代码
android.useDeprecatedNdk=true
这段代码的作用在于兼容以前版本的NDK。
CMakeLists
在AS 2.2 以后的版本里,多了一个配置文件
# Sets the minimum version of CMake required to build the native
# library. You should either keep the default value or only pass a
# value of 3.4.0 or lower.
# 设定CMake编译本地库的最低版本,你可以保持为默认值,也可以修改为更低
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds it for you.
# Gradle automatically packages shared libraries with your APK.
# 创建并且命名库,并将库文件设置为STATIC或者SHARED,并且提供到达库所在的源码的相关路径
# 你可以定义多个库,CMake将编译他们,Gradle将自动打包被标识为SHARED的库到你的APK中。
add_library( # 这个是声明引用so库的名称,在项目中,如果需要使用这个so文件,引用的名称就是这个。
# 值得注意的是,实际上生成的so文件名称是libnative-lib。当Run项目或者build项目是
# 在Module级别的build文件下的
native-lib
# 设置该库为SHARED类型
SHARED
# 提供一个到达该库的相对路径
# 这个目录下的文件会被自动包括在内
src/main/cpp/native-lib.cpp )
# 这个方法与我们要创建的so库无关而是使用NDK的Apis或者库,默认情况下Android平台集成了很多NDK库文件
# 所以这些文件是没有必要打包到apk里面去的。直接声明想要使用的库名称即可。
# 在这里不需要指定库的路径,因为这个路径已经是CMake路径搜索的一部分。
find_library( # 设定路径变量的名字
log-lib
# 声明你需要CMake去定位的NDK库的名字
log )
# 如果你本地的库(native-lib)想要调用log库的方法,那么就需要配置这个属性,
# 意思是把NDK库关联到本地库。
target_link_libraries( # 要被关联的库名称
native-lib
# 要关联的库名称,要用大括号包裹,前面还要有$符号去引用。
${log-lib} )
库类型分为以下三种:
STATIC:静态库,是目标文件的归档文件,在链接其它目标的时候使用。
SHARED:动态库,会被动态链接,在运行时被加载。
MODULE:模块库,是不会被链接到其它目标中的插件,但是可能会在运行时使用dlopen-系列的函数动态链接。
build.gradle
该文件也多出了一些代码。
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.ndkdemo.ustc.jnitest"
minSdkVersion 21
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
//如果你在创建工程选择C++11的标准,则使用cppFlags “-std=c++11”
cppFlags ""
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
//当Run或者Build项目时,想要执行CMakeLists.txt构建脚本,需要把脚本配置到模块级的build.gradle中。
path "CMakeLists.txt"
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.3.1'
testCompile 'junit:junit:4.12'
}
原有的代码
java文件:
public class MainActivity extends AppCompatActivity {
// 用来在系统启动时加载本地库,必须要加
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 调用本地方法的范例
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
/**
* 如果一个方法被打包在应用里的本地库所实现的话,那么,它就是本地方法
*/
public native String stringFromJNI();
}
C代码
//jni.h头文件包括了一系列java与c语言交互的方法
#include <jni.h>
#include <string>
//extern关键字标明下面方法使用c语言的编译器进行编译
extern "C"
/*
* 返回值 jstring是jni.h中定义的对应java中string的类型
* 函数名由java的包名和方法名拼接而成
* @para JNIEnv 是一个线程相关的结构体指针,可以用来调用本地函数
* @para jobject 当前对象的指针
*/
jstring Java_com_ndkdemo_ustc_jnitest_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */)
{
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
添加本地方法
先在MainActivity里添加一个本地方法:
public native String helloFromC();
然后再使用alt+enter自动添加一个c++函数:
extern "C"
JNIEXPORT jstring JNICALL
Java_com_ndkdemo_ustc_jnitest_MainActivity_helloFromC(JNIEnv *env, jobject instance) {
// TODO
std::string hello = "Hello from C";
return env->NewStringUTF(hello.c_str());
}
其中,JNIEXPORT没什么用,JNICALL可以理解为Jni 和Call两个部分,和起来的意思就是 Jni调用XXX。这两个关键字都可以省略。
然后修改一下调用的方法,运行。