使用Android Studio构建基于NDK和Boost C++库的应用程序
2015.01.29 11:40

上一篇, 我们介绍了如何构建一个简单的使用Boost C++库的Android可执行程序. 这个例子很好的说明了工作流程和内部原理. 但是从实用角度出发, 我们需要了解如何构建一个可以提交Google Play商店的完整Android应用程序. 本篇将举例说明.

官方支持的标准实现方式是通过 Android Studio 来创建这样的应用程序. 不幸的是, Android Studio对native应用程序的支持不像Java应用程序那么好. NDK的支持目前非常有限. 因此开发者使用Android Studio用于构建应用开发的Gradle脚本, 唯一支持的NDK应用是只包含一个模块(最终共享库文件), 从位于jni文件夹的源码进行构建, 没有任何依赖, 没有子模块(例如一组静态库和共享库等),等等. 没法做定制(除了一些非常有限的设置选项).

build.gradle
defaultConfig { ... ndk { moduleName "my-module-name" cFlags "-std=c++11 -fexceptions" ldLibs "log" stl "gnustl_shared" abiFilter "armeabi-v7a" } } }

NDK build只能设置moduleName, cFlags, ldLibs, stlabiFilter; 在这里我们不能指定额外的依赖关系(如Boost库). 我们也不能指定一些路径供链接器去搜索库文件的所在位置, 以及许多其他的设置.

这是因为Gradle插件(Android Studio用来编译工程)忽略jni文件夹的Application.mkAndroid.mk文件. 相反它会在运行时生成它自己的Android.mk,使用构建脚本中的设置.

实际上, 在Android Studio中构建功能齐全的NDK应用程序的唯一方式是完全禁用它有限的NDK支持, 手动调用$NDK/ndk-build命令. 这里我们将描述逐步怎么做.

我们将从零开始使用Android Studio创建一个简单的Android应用程序, 然后添加native部分. 我们假设你已经安装了Android Studio和设置好了Android SDK; 我们还假设你已经下载并解压 CrystaX NDK 到您的计算机上某处.

Java部分

首先, 打开Android Studio, 新建一个Android工程:

选择"Android 4.0.3" target:

选择blank activity:

接受默认名称, 点击"Finish"按钮:

布局

现在, 修改app/res/layout/activity_main.xml 如下所示:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout>

MainActivity

添加以下代码到MainActivity.onCreate():

TextView field = (TextView)findViewById(R.id.text); field.setText(getGPSCoordinates(getFilesDir().getAbsolutePath()));

添加native方法的声明到MainActivity类:

private native String getGPSCoordinates(String rootPath);

同样别忘记添加加载native库文件到static初始化部分:

static { System.loadLibrary("test-boost"); }

最终MainActivity.java的内容如下所示:

MainActivity.java

Java部分完成了; 接下来看native部分.

Native部分

创建native代码存放的文件夹路径:

在下一个窗口使用默认的'main' source set, 点击"Finish"按钮:

源码

在刚刚创建的文件夹中创建以下文件(app/src/main/jni):

Application.mk
Android.mk
gps.hpp
gps.cpp
test.cpp

编译脚本

现在我们需要修改编译脚本, 以便可以像Java部分一样编译native部分. 首先打开local.properties 添加CrystaX NDK的路径, 如下所示:

sdk.dir=/opt/android/android-sdk-mac ndk.dir=/opt/android/crystax-ndk-10.1.0

对于Windows用户, 路径中的反斜杠和冒号需要处理:

sdk.dir=C\:\\android\\android-sdk-mac ndk.dir=C\:\\android\\crystax-ndk-10.1.0

最后打开编辑build.gradle:

使它如下所示:

build.gradle

以下是我们修改过的build.gradle的diff内容:

build.gradle.diff
diff --git a/build.gradle b/build.gradle index a6b8c98..08dce1c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,5 @@ +import org.apache.tools.ant.taskdefs.condition.Os + apply plugin: 'com.android.application' android { @@ -17,9 +19,50 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + sourceSets.main.jni.srcDirs = [] // disable automatic ndk-build call, which ignore our Android.mk + sourceSets.main.jniLibs.srcDir 'src/main/libs' + + // call regular ndk-build(.cmd) script from app directory + task ndkBuild(type: Exec) { + workingDir file('src/main') + commandLine getNdkBuildCmd() + } + + tasks.withType(JavaCompile) { + compileTask -> compileTask.dependsOn ndkBuild + } + + task cleanNative(type: Exec) { + workingDir file('src/main') + commandLine getNdkBuildCmd(), 'clean' + } + + clean.dependsOn cleanNative } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:21.0.3' } + +def getNdkDir() { + if (System.env.ANDROID_NDK_ROOT != null) + return System.env.ANDROID_NDK_ROOT + + Properties properties = new Properties() + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + def ndkdir = properties.getProperty('ndk.dir', null) + if (ndkdir == null) + throw new GradleException("NDK location not found. Define location with ndk.dir in the local.properties file or with an ANDROID_NDK_ROOT environment variable.") + + return ndkdir +} + +def getNdkBuildCmd() { + def ndkbuild = getNdkDir() + "/ndk-build" + if (Os.isFamily(Os.FAMILY_WINDOWS)) + ndkbuild += ".cmd" + + return ndkbuild +}

文件树状结构

TestBoost/app 目录下的源代码文件结构如下所示:

. ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src ├── androidTest │ └── java │ └── net │ └── crystax │ └── examples │ └── testboost │ └── ApplicationTest.java └── main ├── AndroidManifest.xml ├── java │ └── net │ └── crystax │ └── examples │ └── testboost │ └── MainActivity.java ├── jni │ ├── Android.mk │ ├── Application.mk │ ├── gps.cpp │ ├── gps.hpp │ └── test.cpp └── res .......

最终结果

好了! 现在正常编译项目(Build -> Make Module 'app') 并运行在设备上. 这里是一张运行时的截图:

Back
Home
Map
Back
Home
Map

Our contributors: