- 原文地址: Getting Started with C++ and Android Native Activities
- 原文作者: Patrick Martin
- 译文出自: 掘金翻译计划
- 本文永久链接: github.com/xitu/gold-m…
- 译者: Feximin
- 校对者: twang1727
C++ 和 Android 本地 Activity 初探
简介
我会带你完成一个简单的 Android 本地 Activity。我将介绍一下基本的设置,并尽力将进一步学习所需的工具提供给你。
虽然我的重点是游戏编程,但我不会告诉你如何写一个 OpenGL 应用或者如何构建一款自己的游戏引擎。这些东西得写整本书来讨论。
为什么用 C++
在 Android 上,系统及其所支持的基础设施旨在支持那些用 Java 或 Kotlin 写的程序。用这些语言编写的程序得益于深度嵌入系统底层架构的工具。Android 系统很多核心的特性,比如 UI 界面和 Intent 处理,只通过 Java 接口公开。
使用 C++ 并不会比 Kotlin 或 Java 这类语言对 Android 来说更“本地化”。与直觉相反,你通过某种方式编写了一个只有 Android 部分特性可用的程序。对于大多数程序,Koltin 这类语言会更合适。
然而此规则有一些意外情况。对我来说最接近的就是游戏开发。由于游戏一般会使用自定义的渲染逻辑(通常使用 OpenGL 或 Vulkan 编写),所以预计游戏看起来会与标准的 Android 程序不同。当你还考虑到 C 和 C++ 几乎在所有平台上都通用,以及相关的支持游戏开发的 C 库时,使用本地开发可能更合理。
如果你想从头开始或者在现有游戏的基础上开发一款游戏,Android 本地开发包(NDK)已备好待用。实际上,即将展示给你的本地 activity 提供了一键式操作,你可以在其中设置 OpenGL 画布并开始收集用户的输入。你可能会发现,尽管 C 有学习成本,但使用 C++ 解决一些常见代码难题,比如从游戏数据中构建顶点属性数组,会比用高级语言更容易。
我不打算讲的内容
我不会告诉你如何初始化 Vulkan 或 OpenGL 的上下文。尽管我会给一些提示让你学习的轻松一点,但还是建议你阅读 Google 提供的示例。你也可以选择使用类似 SDL 或者 Google 的 FPLBase 这样的库。
设置你的 IDE
首先需要确保你已经安装了本地开发所需的内容。为此,我们需要用到 Android NDK。启动 Android Studio:
到此你已经设置好了所有内容,我们将建一个工程。我们想创建一个没有 Activity 的空工程:
cmake_minimum_required(VERSION 3.6.0)
add_library(helloworld-c
SHARED
src/main/cpp/helloworld-c.cpp)
复制代码
我们声明了在 Android Studio 中使用最新版本的 CMake(3.6.0),将构建一个名为 hellworld-c 的共享库。我还添加了一个必须要创建的源文件。
为什么是共享库而不是可执行文件呢?Android 使用一个名为 Zygote 的进程来加速在 Android Runtime 内部启动的应用或服务的过程。这对 Android 内所有面向用户的进程都适用,因此你的代码首次运行的地方是在一个虚拟机内。然后代码必须加载一个含有你的逻辑的共享库文件,如果你使用了本地 Activity,该共享库将为你处理。与之相反,当构建一个可执行文件时,我们希望操作系统直接加载你的程序并运行一个名为 “main” 的 C 方法。在 Android 里也有可能,但是我还没找到这方面的任何实践用途。
现在创建 C++ 文件:
//
// Created by Patrick Martin on 1/30/19.
//
#include <jni.h>
复制代码
最后让我们把这个 C++ 工程链接到我们的应用上:
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.pux0r3.helloworldc"
minSdkVersion 28
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path file('CMakeLists.txt')
}
}
}
复制代码
创建一个本地 Activity
一个 Activity 是 Android 用来显示你的应用的用户界面的基本窗口。通常你会用 Java 或 Kotlin 编写一个继承自 Activity 的类,但是 Google 创建了一个等价的用 C 写的本地 Activity。
设置你的构建文件
创建一个本地 Activity 最好的方式是包含 native_app_glue。很多示例程序将其从 SDK 拷贝至他们的工程中。这没什么错,但是我个人更愿意将其做为我的游戏可以依赖的库。我把它做成静态库,所以不需要动态库调用的额外开销:
cmake_minimum_required(VERSION 3.6.0)
add_library(native_app_glue STATIC
${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
target_include_directories(native_app_glue PUBLIC
${ANDROID_NDK}/sources/android/native_app_glue)
find_library(log-lib
log)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
add_library(helloworld-c SHARED
src/main/cpp/helloworld-c.cpp)
target_link_libraries(helloworld-c
android
native_app_glue
${log-lib})
复制代码
这里有不少事情要做,我们继续。首先用 add_library 建了一个名为 native_app_glue 的库并把它标记为一个 STATIC 的库。然后在 NDK 的安装路径下查找自动生成的环境变量 ${ANDROID_NDK} 从而来寻找一些文件。如此,我找到了 native_app_glue 的实现:android_native_app_glue.c。
将代码与目标关联后,我想说一下目标是在哪里找到它的头文件的。我使用 target_include_directories 将包含它的所有头文件的文件夹包含进来并将设置为 PUBLIC。其他选项还有 INTERNAL 或 PRIVATE 但目前还用不到。有些教程可能会用 include_directories 代替 target_include_directories。这是一种较早的做法。最近的 target_include_directories 可以让你的目录关联到目标,这有助于降低较大工程的复杂性。
现在,我想在在 Android 的 Logcat 中打印一些内容。只使用与普通 C 或 C++ 应用中那样的标准的输出(如:std::cout 或 printf)是无效的。使用 find_library 去定位 log,我们缓存了 Android 的日志库以便稍后使用。
最后我们通过 target_link_libraries 告诉 CMake,helloworld-c 要依赖 native_app_glue、native_app_glue 和被命名为 log-lib 的库。如此可以在我们的 C++ 工程中引用本地应用的逻辑。在 add_library 之前的 set 也确保 helloworld-c 不会实现名为 ANativeActivity_onCreate 的方法,该方法由 android_native_app_glue 提供。
写一个简单的本地 Activity