This project has been on hold since 2016.
All the data on this site is still available (and will stay available) but not actual anymore.
You might be interested in checking out Dmitry Moskalchuk's portfolio website to learn about his other projects.
使用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: