使用Android Studio和CMake进行NDK开发 - 基础

1. 概述


在Android Studio 2.2之后,可以使用CMake来进行NDK开发,C/C++开发的便利性又提升了不少。这个是个好事,比较CMake使用起来还是比make要简单,并且抽象、跨平台。例如在linux可以生产linux下的makefile,在windows下可以生产Visual Studio的工程文件。

这里需要解析几个名词:

  • NDK

Android Native Development Kit,里面包含各个平台上的C/C++编译器、相关头文件和库(相当于Java的库)。

  • CMake

一套构建系统,类似Gradle,但是CMake不直接参与编译,而是产生其他构建系统的工程文件,再进行编译,在Android Studio当中,Gradle插件会驱动CMake产生各个平台(armeabi、armeabi-v7a、x86等)的ninja的构建文件,再驱动编译器进行编译。

  • LLDB

调试器,可以用来调试原生代码,之前版本使用的是GDB。

2. 准备


本文是基于Android Studio 2.3来讲解的,因此需要升级到2.3。安装好2.3之后,打开【SDK Manager】

image.png

勾选:【CMake】和【NDK】两个选项,然后点击【Apply】进行安装

因为google在国内假设了镜像站点,现在不需要使用[可不描述]来更新SDK了

3. 实践

3.1 创建项目


创建项目的流程,官方文档也有: https://developer.android.com/studio/projects/add-native-code.html
因此,我这里会在流程上补充一些说明。

新建项目,在第一页中勾选:

image.png

【include c++ support】来支持C++开发,如果已有项目没有勾选也没关系,可以在菜单中【link 】

一路next来到最后一页,定制你的C++项目支持

image.png

主要有三个参数组成:

  • C++标准

现在基本都是C++ 11开发了,如果不是要维护非常老的代码,建议选择C++11。
C++11相对于之前的版本(C++03)增加的功能非常丰富,具体可以参考这篇文章: http://blog.csdn.net/zhuxianjianqi/article/details/8658169

  • Exception Support

异常支持,如果取消掉的话,那么就不能使用 try-catch 进行异常处理了,建议选择。

  • Runtime Type Information Support

运行时类型信息支持,在C++运行的时候,不像Java、C#等一样,可以动态获取对象的类信息,开启这个选项来支持这个功能,建议选择。

到这里,项目就创建完毕了,点击 run 按钮,APP就可以在模拟器或者android设备上运行。

3.2 工程结构


打开代码所在的目录,进入APP子模块,可以看到相比传统的APP项目,会多出以下文件或者目录:

  • CMakeList.txt

CMake的工程文件,相当于 build.gradle 用于说明编译那些C/C++源码,以及相关的编译参数

  • src/main/cpp

C/C++源码目录

  • .externalNativeBuild

该文件夹是临时文件夹,gradle插件会调用cmake产生各个平台的临时构建文件,都存放在该目录

3.3 编译流程


需要注意的是,cmake并不能直接编译 c/c++ 源码,需要产生 ninja 的项目文件,才会编译,其流程大体是这样的。

生成ninja工程 ---> 编译/链接 ---> APP打包

对于 产生ninja工程,可以通过下述三种方式:

  • 修改CMakeLists.txt,然后执行 Build菜单的 Make Project 或者 Make module 或者直接 Run
  • 执行 Gradle Sync
  • 执行 Build菜单的 Refresh Linked C++ Projects

在实际使用中,有时候修改CMakeLists.txt不会重新产生ninja工程文件,导致编译会出现问题,所以官方可能也留了 Refresh Linked C++ Projects 给到大家手动刷新。另外,手动添加 C/C++源码的时候,也可以通过这种方式刷新工程。

4. 编译参数解析

4.1 build.gradle


该文件可以指定工具链的大部分核心参数,里面的源码大致如下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 24
    buildToolsVersion "25.0.3"
    defaultConfig {
        applicationId "com.test.ndkhelloworld"

        ...

        // 该代码块用于配置相关的参数
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11 -frtti -fexceptions"
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    // 该代码块用于链接到指定的CMakeLists.txt,路径是相对路径
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

...

这里有两个 externalNativeBuild 代码块

  • android.externalNativeBuild

ExternalNativeBuild 对象:http://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.ExternalNativeBuild.html
主要配置 cmake 或者 ndk-build 的工程文件路径

  • `android.defaultConfig.externalNativeBuild

ExternalNativeBuildOptions 对象: http://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.ExternalNativeBuild.html
具体的参数配置。后面都是以这个配置参数来讲解。

4.2 CMake变量解析


可以通过 arguments 命令来传递CMake构建参数(这些参数,实际会传递到NDK的构建工具链),形式为: -D参数名=参数值1 参数值2,需要注意的是,如果有多个参数,那么必须换行来传递,例如:

externalNativeBuild {
            cmake {
                arguments "-DANDROID_TOOLCHAIN=gcc"
                            "-DANDROID_STL=stlport_static"
            }
        }

以下是常用的变量:

  • ANDROID_TOOLCHAIN

编译工具链,可选:clang(默认)和gcc(已经过期)。

  • ANDROID_PLATFORM

android平台,例如:android-18 注意,该取值会影响到原生API的时候,有些原生API在低版本的android是没有的,详见: https://developer.android.com/ndk/guides/stable_apis.html

  • ANDROID_STL

STL(标准模板库)的选择,NDK自带了很多个版本的STL,功能大体上是一样的,但是授权会不一样。详细请阅读: https://developer.android.com/ndk/guides/cpp-support.html#hr
(stlport已经实现异常处理了,在低版本的NDK是不支持的)

  • ANDROID_PIE

启用PIE(position-independent executables),如果是 ANDROID_PLATFORM = android-16,该选项默认开启(取值为 ON),否则为 OFF

  • ANDROID_CPP_FEATURES

C++的功能,取值为: rttiexceptions,也可以使用 cppFlags 指定

  • ANDROID_ALLOW_UNDEFINED_SYMBOLS

允许未定义的符号,默认为 FALSE,可以取值为:TRUE

  • ANDROID_ARM_MODE

指令集模式,默认为: thumb ,可以取值为: arm

  • ANDROID_ARM_NEON

是否启用NEON,默认为FALSE,启用为:TRUE

  • ANDROID_DISABLE_FORMAT_STRING_CHECKS

是否禁用字符串格式化检查,默认为:FALSE,可以取值为:TRUE,建议默认值,不要禁用,因为很多漏洞或者BUG都出现在字符串格式化上面。

4.3 abi过滤器


通过 abiFilters 可以编译出指定ABI的二进制文件,可选值为:armeabiarmeabi-v7aarm64-v8amipsmips64x86以及x86_64

默认情况下,编译出来的都包含上述的ABI的二进制文件,如下图:

APK信息,build菜单->Analyze APK->选择产生的APK

可以清楚的看到,编译出来的二进制文件(库)可以在ARM、X86和MIPS所有平台上运行。实际上,我们想给APK瘦身的,不需要在那么多平台上运行,可以取消掉一些平台的支持,例如我们只支持armeabi和armeabi-v7a,X86和MIPS都不需要

externalNativeBuild {
            cmake {
                abiFilters "armeabi", "armeabi-v7a"
                cppFlags "-std=c++11 -frtti -fexceptions"
                arguments "-DANDROID_TOOLCHAIN=gcc",
                            "-DANDROID_STL=stlport_static"
            }
        }

执行clean之后再产生APK,可以看到,只有两个ABI的二进制产生

APK信息,build菜单->Analyze APK->选择产生的APK

我发现一个问题,即使sync、clean等一系列的操作后,不会删除原有产生的ninja工程文件,可以先手动删除掉 .externalNativeBuild 目录再重试一下。

4.4 传递C/C++参数


cFlags和cppFlags可以传递C/C++编译参数,这里不详细讨论。

推荐阅读更多精彩内容