Android NDK开发可能在平时的项目开发中不常用到,但是这并不代表其不重要,
相反NDK开发是Android开发人员的进阶过程中必须要掌握的技能。
Android NDK是一组允许将C或C++(原生代码)嵌入到Android应用中的工具。
如果开发者在需要以下操作的时候,使用NDK开发特别有用:
* 在平台之间移植其应用
* 从设备获取卓越性能以用于计算密集型应用,例如游戏或物理模拟。
* 重复使用现有库或者提供自己库供重复使用。
除此之外,对于ndk的学习,也有助于加深开发者在阅读框架的源码理解。
ndk开发有两种编译方式,一种是通过ndk-build来构建;参考地址。
一种是通过CMake构建原生库。通过CMake构建原生库是Google新提出来的方式,比较方便、强大。
通过cmake进行ndk开发首先有个要求,需要Android Studio的版本是2.2以上版本(包含2.2)
,Gradle的版本需要升到2.2.0及以上。
满足上面的条件下,我们需要下载ndk和构建工具。如下图:
红线标记的三个工具下载好就行。CMake和NDK就不说了,都好理解,
LLDB呢是一种调试程序,用来调试原生代码的。
上面的准备工作做完之后就可以向项目中添加原生代码,构建原生库进行ndk开发了。
这边有个讨论,向项目中添加原生代码有两种情况:
一种是新建一个新的项目支持C/C++;
一种是在已有项目中添加原生代码。
所以这边相应的也有两种方式。先说第一种方式。
创建支持原生代码的新项目跟平常开发中创建一个新项目没有太大的区别,说一下不同的地方。
1.前者在向导的Config your new project界面需要选中
Include C++ Support 复选框。如图1:
2.前者在向导的Customize C++ Support 界面会有C++ Standard 的选择
意思是你希望使用哪种 C++ 标准。
选择 Toolchain Default 会使用默认的 CMake 设置。
这里我们选择默认的。
Exceptions Support:如果你希望启用对 C++ 异常处理的支持,请选中此复选框。
如果启用此复选框,Android Studio 会将 -fexceptions 标志添加到模块级 build.gradle 文件的
cppFlags 中,Gradle 会将其传递到 CMake。
Runtime Type Information Support:如果你希望支持 RTTI,请选中此复选框。
如果启用此复选框,Android Studio 会将 -frtti 标志添加到模块级 build.gradle 文件的
cppFlags 中,Gradle 会将其传递到 CMake。
这里自己看需求选择勾不勾选,这边演示的demo选择勾选。如图2:
创建项目完成之后,在as的左侧项目结构目录中的app应用模块中可以看到cpp文件夹,
cpp文件件里面存放有属于项目的所有原生源文件、标头和预构建库。
对于新项目,Android Studio 会创建一个示例 C++ 源文件 native-lib.cpp
除了cpp文件夹之外,我们还看到一个CMakeLists.txt的这样一个文件。
这个文件是CMake的构建脚本,下面会详细说,这里就暂且不说。
之前有说过新建支持C/C++的项目会提供了一个示例的c++源文件native-lib.cpp,存放在cpp文件夹中
,我们点开看看内容。
#include
#include
extern "C"
JNIEXPORT jstring
JNICALL
Java_com_cmaketestone_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
恩,是使用C++写的一个方法,返回一个"Hello from C++",我们跟踪进去这个方法,看看哪里调用。
package com.cmaketestone;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}
我们发现,这个原生方法是在MainActivity中调用的,返回的字符串设置给了TextView。
通过这两张图我们发现,原生方法通过native关键字来表示、在使用原生库之前,
需要通过System.loadLibrary(“库名称”)加载、
原生方法的方法名命名等常见要点。这边就一笔略过。因为本文重点是CMake构建原生库。
所以ndk的一些基础知识,以及JNI等,就不细说。本文假设读者知道这些。
这样的一个新的支持原生代码的项目创建之后,并且还自带demo,开发者就可以很方便在上面进行开发。
假设现在要创建一个原生库进行ndk开发,我们在cpp文件夹下new一个C/C++ Source file ,
在你new出来的文件编写你的C/C++代码逻辑,然后在CMakeLists.txt配置文件上稍作配置,即可。
下面会详述CMakeLists.txt的配置。
主要有三个大步骤。
第一步:创建新的原生源文件;
第二步:创建CMake构建脚本;
第三步:将Gradle关联到原生库。 现在我们来看第一步,创建新的原生源文件,首先我们要先在应用模块src/main 目录下 创建一个cpp文件夹用来存放原生源文件等。 然后在cpp文件夹下面创建C/C++源文件New > C/C++ Source File。如图:
创建了一个名为test-lib.cpp文件。
编写c代码
// // Created by Administrator on 2018/5/31. // #include#include extern "C" JNIEXPORT jstring JNICALL ////Java_com_cmaketestdemo_NdkHolder是路径,stringFromJNI是方法名 Java_com_cmaketestdemo_NdkHolder_stringFromJNI( JNIEnv *env, jobject /* this */) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); }
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)#CMake 最低版本要求,
# 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 them for you.
# Gradle automatically packages shared libraries with your APK.
#创建并命名库,将其设置为静态或共享,提供与其源代码相关的路径
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/native-lib.cpp )
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.指定目标库
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} )
通过图片我们看到,其实内容也没什么。就cmake_minimum_required()、add_library()
、find_library、target_link_libraries()。这几个CMake命令,每个CMake命令都有英文介绍很好理解。
比如add_library创建和命名一个库,这边名称我们就填test-lib,种类分为static和shared,
具体区别嘛移步:
static和shared的区别
这里我们选择SHARED,然后就是提供一个library的相对路径。只有在CMakeLists.txt文件中配置了该命令
,才能找到编译这个库。
其他的一些CMake命令大家自行搜索了解,都不是很难,很好理解,这里篇幅有限就不细说了。
那现在我们可以按照系统提供的模板,基于自己的项目写了一份CMakeLists.txt文件,代码如下:
cmake_minimum_required(VERSION 3.4.1)#CMake 最低版本要求,
#创建并命名库,将其设置为静态或共享,提供与其源代码相关的路径
add_library( # Sets the name of the library.
test-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/test-lib.cpp )
find_library( # Sets the name of the path variable.
log-lib
log )
target_link_libraries( # Specifies the target library.指定目标库
test-lib
${log-lib} )
这里我这边写了一个NdkHelper类,专门用来定义native方法的:
package com.cmaketestdemo;
/**
* Author:wang_sir
* Time:2018/5/31 11:54
* Description:This is NdkHolder
*/
public class NdkHolder {
static {
System.loadLibrary("test-lib");
}
public native String stringFromJNI();
}
externalNativeBuild {
cmake {
path 'CMakeLists.txt'
}
}
我们在MainActivity去调用这个原生方法。
通过toast显示方法返回的值。
package com.cmaketestdemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NdkHolder ndkHolder = new NdkHolder();
Toast.makeText(getApplicationContext(), ndkHolder.stringFromJNI(), Toast.LENGTH_LONG).show();
}
}
看下效果
O(∩_∩)O哈!,我们发现toast显示的值确实是我们设想的返回值。这样就架起了Java与原生代码之间的桥梁。 现在我们可以应用模块的build/outputs/apk目录下,打开我们的apk,我们会发现,我们创建的原生文件, 编译成了原生库"libpalmread-lib.so"也打包进了apk文件。如图:
可以看到编译生成的.so文件。