> ## Content Index
> Fetch the complete content index at: https://yinguobing.com/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# 采用C++开发Android APP
- URL: https://yinguobing.com/blog/cpp-in-android-studio/
- Published: 2017-02-17T03:32:53.000Z
- Updated: 2022-05-29T07:58:29.000Z
- Description: 作为一名算法开发人员，一定会有大量项目是用C/C++开发完成的。随着移动设备功能日益强大，总会有这么一天，你需要把曾经的C++代码移植到Android系统中运行。作为一名从未写过JAVA的业余程序员，如何解决这个问题？
- Author: 尹国冰
- Tags: Android, C++

作为一名算法开发人员，一定会有大量项目是用C/C++开发完成的。随着移动设备功能日益强大，总会有这么一天，你需要把曾经的C++代码移植到Android系统中运行。这正是我最近面临的状况。作为一名从未写过JAVA的业余程序员，如何解决这个问题？

## 问题分析

遇到困难先不要慌，镇静下来仔细分析一下情况。首先，需要移植的工程从功能上来分析大致可以分为三大模块：

- 调用摄像头进行人脸检测；
- 根据检测到的信息进行进一步计算；
- 将结果输出，控制后端的硬件。

第一部分调用了第三方视觉库OpenCV。第二和第三部分是自己编写的C++代码。考虑到Android系统本身提供了人脸检测的模块可供调用，第一部分的问题算不上棘手。在之前的项目中，第二部分内容的原理已经非常清晰，大不了用JAVA再实现一遍。第三部分因为平台与控制硬件发生了变化，有同事可以提供支持，在此可以不予考虑。所以我需要完成的事项就变为：

- 在Android系统下实现摄像头调用与人脸检测；
- 尝试将第二个模块移植到Android系统。

## 制定移植计划

这两项事情对于我来说有一定的难度，主要原因是我从未写过JAVA代码，没有做过Android下的APK开发，更别提将C++代码移植到Android运行。但是任务已经安排了下来，只能想办法克服这些困难，尽力而为。虽然没有写过JAVA代码，但是C++的基础应该可以借鉴；没做过Android下的APK开发，但是iOS下的APP开发经验应该会提供一部分帮助；C++代码移植万一不可行，就重新用JAVA写一遍，毕竟原理已经了然于胸，可以根据需求做相应的功能裁剪，尽可能减少工作量，以保证按期交付。所以我需要做的事项变成了：

- 尽快入门Android下的APP开发；
- 尝试寻找C++移植方法，如果不行，则采用JAVA重新实现。

### Android应用开发入门

由于整个移植过程只有一周的时间，不可能做到真正的入门，最快的方法就是从Google提供的官方sample code入手。于是打开浏览器，访问Google开发者中国站：[http://developer.android.google.cn](http://developer.android.google.cn/?ref=yinguobing.com). 从官方介绍来看，Google提供了一款类似Xcode的工具：Android Studio。于是一边下载安装Android Studio，一边浏览官方提供的示例代码，尤其是人脸检测相关部分。经过一番检索，发现Android下的人脸识别有两种实现途径：

- Android系统提供了`Camera API`，可以实现人脸检测[\[1\]](#fn1)；
- Google另外提供了`Mobile Vision API`，可以实现包括人脸检测在内的众多强大的功能[\[1:1\]](#fn1)。

幸运的是，Google针对`Mobile Vision API`还提供了7份示例代码，其中包括一份名为`FaceTracker`人脸检测代码。发现这段代码让我又惊又喜，这起码节省了2天的时间。二话不说下载用Android Studio导入，编译运行，一切都很完美。

### C++代码支持情况

我使用的Android Studio版本为2.2.3，在建立工程的时候有一个可选项`Include C++ Support`，这表明Android Studio是支持C++的。  
![](https://yinguobing.com/blog/content/images/2017/02/android-studio-cpp.png)

浏览官方站点也发现Android提供NDK来将C++代码编译为JNI库[\[1:2\]](#fn1)，然后可在Java环境下调用。于是尝试建立一个支持C++的工程，将自己的C++代码添加到默认生成的`native-lib.cpp`文件中，编译运行，OK。可是，如何让现有基于JAVA的工程来支持C++？

为了弄清楚这一点，我选择从分析两种工程之间的差异入手。分别新建了一个JAVA工程，一个C++工程。将两个项目目录结构展开，并排放在一起比较：  
![](https://yinguobing.com/blog/content/images/2017/02/as-java-vs-cpp.jpg)  
左侧为JAVA工程，右侧为C++工程。仔细比较发现，支持C++的工程主要发生了如下变化：

- `build.gradle`和`MainActivity`的文件内容发生了变化。
- 在`src`目录下多了一个`cpp`目录，内有一个`native-lib.cpp`文件；
- 在`src`目录下多了一个`CMakeLists.txt`文件；

接下来详细看看变化的细节。

#### `build.gradle`

`build.gradle`文件中，`android{...}`代码块中增加了如下内容：

```
externalNativeBuild {
    cmake {
        path "CMakeLists.txt"
    }
}

```

在`defaultConfig{...}`代码块中增加了如下内容：

```
externalNativeBuild {
    cmake {
        cppFlags ""
    }
}

```

从内容可以看出，这是在告诉gradle采用CMake的方式对C++代码进行编译，并且给出了CMake需要的文件`CMakeLists.txt`。

#### `CMakeLists.txt`

`CMakeLists.txt`文件中内容很长，且有非常详细的注释，这里摘取其中重要的内容如下：

```
# ...
cmake_minimum_required(VERSION 3.4.1)

# ...
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).
             # Associated headers in the same location as their source
             # file are automatically included.
             src/main/cpp/native-lib.cpp )

# ...
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 )

# ...
target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

```

从注释可以看出，`CMakeLists.txt`做了以下事项：

- 通过`add_library()`的方式添加C++源代码文件`native-lib.cpp`，采用`SHARED`方式编译，生成`native-lib`库文件；
- 通过`find_library()`的方式包含要链接的外部库文件`log`；
- 通过`target_link_libraries()`的方式链接`native-lib`库文件。

而`native-lib.cpp`正是C++源代码。

#### `native-lib.cpp`

作为C++源代码所在，`native-lib.cpp`内容如下：

```
#include <jni.h>
#include <string>

extern "C"
jstring
Java_com_yinguobing_robin_cpp_1application_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

```

在其中终于看到熟悉的`std::string hello = "Hello from C++";` 显然，这里声明了一个名为`Java_com_yinguobing_robin_cpp_1application_MainActivity_stringFromJNI`的函数，该函数返回一个类型为`jstring`的字符串，字符串内容为`Hello from C++`。那这个函数又是在哪里调用的呢？

#### `MainActivity`

在iOS APP开发过程中，程序的入口一般为`UIViewController`，同样，放在`src/java`路径下的这个`MainActivity`一定是JAVA程序的入口了。仔细比较该文件，发现其中增加了如下内容：

```
// Used to load the 'native-lib' library on application startup.
static {
    System.loadLibrary("native-lib");
}

/**
 * A native method that is implemented by the 'native-lib' native library,
 * which is packaged with this application.
 */
public native String stringFromJNI();

```

正如注释所述，`System.loadLibrary("native-lib")`负责加载`native-lib`库文件；而`stringFromJNI()`正是在`native-lib.cpp`中实现的C++函数。

### C++代码移植

既然原理已经清楚，接下来的事情就很简单了。整个代码移植的过程大概分以下几步：

- 在`build.gradle`中添加支持C++外部编译的代码；
- 建立`CMakeList.txt`文件，并添加要编译的源代码文件；
- 在程序的入口文件中添加要调用的C++函数原型；
- 在C++源代码中更新被调用函数的名称。

以上几步完成，你用C++实现的函数即可在Java环境下进行调用了。

最后附上Android官方文档：向您的项目添加 C 和 C++ 代码， 地址是 [https://developer.android.google.cn/studio/projects/add-native-code.html](https://developer.android.google.cn/studio/projects/add-native-code.html?ref=yinguobing.com)

## 参考

---

1. [Android NDK](https://developer.android.google.cn/ndk/index.html?ref=yinguobing.com) [↩︎](#fnref1) [↩︎](#fnref1:1) [↩︎](#fnref1:2)