• Getting Started with C++ and Android Native Activities
  • By Patrick Martin
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: Feximin
  • Proofread by: Twang1727

Introduction to the

I’ll walk you through a simple Android native Activity. I’ll walk you through the basic setup and try to give you the tools you need for further learning.

While my focus is on game programming, I’m not going to tell you how to write an OpenGL application or how to build your own game engine. Whole books could be written about this stuff.

Why C++

On Android, the system and the infrastructure it supports are designed to support applications written in Java or Kotlin. Programs written in these languages benefit from tools that are deeply embedded in the system’s underlying architecture. Many of Android’s core features, such as UI interfaces and Intent handling, are exposed only through Java interfaces.

Using C++ is no more “native” to Android than languages like Kotlin or Java. Counterintuitively, you’ve somehow written an application that only has some Android features available. For most programs, a language like Koltin would be more appropriate.

There are a few surprises to this rule, however. The closest thing to it for me is game development. Since games tend to use custom rendering logic (often written in OpenGL or Vulkan), games can be expected to look different from standard Android programs. When you also consider that C and C++ are common on almost all platforms, and the associated C libraries that support game development, it may make more sense to use native development.

If you want to start a game from scratch or build on an existing game, the Android Native Development Kit (NDK) is ready to go. In fact, the local activity you’ll be shown provides one-click operations where you can set up an OpenGL canvas and start collecting user input. You may find that, despite the learning costs of C, it is easier to solve some common coding challenges, such as building an array of vertex attributes from game data, in C++ than it is in a high-level language.

What I’m not going to talk about

I won’t tell you how to initialize the context of Vulkan or OpenGL. Although I’ll give you some tips to make your learning easier, I recommend you read the examples provided by Google. You can also use a library like SDL or Google’s FPLBase.

Set up your IDE

The first step is to make sure that you have installed everything you need for local development. To do this, we need to use the Android NDK. Start Android Studio:

Under Configure select SDK Manager:

Install LLDB (local debugger), CMake (build system), and the NDK itself from here:

Create a project

Now that you have everything set up, we will build a project. We want to create an empty project with no Activity:

NativeActivity has been around since Android Gingerbread. If you’re just starting out, it’s recommended to select the highest target version currently available.

Now we need to create a cmakelists. TXT file to tell Android how to build our C++ project. Right-click app in project view to create a new file:

Call it cmakelists.txt:

Create a simple CMake file:

Cmake_minimum_required (VERSION 3.6.0) add_library(helloworld-c SHARED SRC /main/ CPP /helloworld-c.cpp)Copy the code

We declared that using the latest version of CMake (3.6.0) in Android Studio, we would build a shared library called HellWorld -C. I also added a source file that I had to create.

Why shared libraries and not executables? Android uses a process called Zygote to speed up the process of launching apps or services within the Android Runtime. This is true for all user-facing processes in Android, so the first place your code runs is in a virtual machine. The code must then load a shared library file containing your logic, which will handle it for you if you use a local Activity. In contrast, when building an executable, we want the operating system to load your program directly and run a C method called “main.” It’s possible in Android as well, but I haven’t found any practical uses for this.

Now create a C++ file:

Put it in the directory we specified in the make file:

Add a little more to tell us if the build was successful:

//
// Created by Patrick Martin on 1/30/19.
//

#include <jni.h>
Copy the code

Finally, let’s link the C++ project to our application:

If all goes well, the project will be updated successfully:

You can then perform a build without error:

As for what’s changed in your build script, if you open the build.gradle file in your app, you’ll see externalNativeBuild:

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')}}}Copy the code

Create a local Activity

An Activity is the basic window that Android uses to display your application’s user interface. Normally you would write a class that inherits from an Activity in Java or Kotlin, but Google has created an equivalent native Activity written in C.

Set up your build file

The best way to create a local Activity is to include native_app_glue. Many sample programs copy this from the SDK into their projects. There’s nothing wrong with that, but I personally prefer to use it as a library that my games can rely on. I made it a static library, so I don’t need the overhead of a dynamic library call:

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})
Copy the code

There’s a lot of work to do here. Let’s move on. Start by building a library called native_app_glue with add_library and marking it as STATIC. Then look for the automatically generated environment variable ${ANDROID_NDK} in the NDK installation path to find some files. So I found an implementation of native_app_glue: android_native_app_glues. C.

Having associated the code with the target, I want to say where the target finds its header file. I use target_include_directories to include the folder containing all of its header files and set it to PUBLIC. Other options are INTERNAL or PRIVATE but you don’t need them yet. Some tutorials may use include_directories instead of target_include_directories. This is an earlier approach. More recently, target_include_directories allow you to associate directories with the target, which helps reduce the complexity of larger projects.

Now, I want to print something in Logcat on Android. Using only standard output (such as STD ::cout or printf) as in normal C or C++ applications is not effective. Using find_library to locate logs, we cache Android’s log library for later use.

Finally, we tell CMake via target_link_libraries that helloWorld -c relies on native_app_glue, native_app_glue, and a library named log-lib. This allows us to reference native application logic in our C++ projects. The set before add_library also ensures that helloWorld -C does not implement a method called ANativeActivity_onCreate, which is provided by Android_native_app_glue.

Write a simple local Activity

Now we’re ready to build our app!

//
// Created by Patrick Martin on 1/30/19.
//

#include <android_native_app_glue.h>
#include <jni.j>

extern "C" {
void handle_cmd(android_app *pApp, int32_t cmd) {
}
    
void android_main(struct android_app *pApp) {
    pApp->onAppCmd = handle_cmd;
    
    int events;
    android_poll_source *pSource;
    do {
        if (ALooper_pollAll(0, nullptr, &events, (void **) &pSource) >= 0) {
            if(pSource) { pSource->process(pApp, pSource); }}}while (!pApp->destroyRequested);
}
}
Copy the code

What’s going on here?

First, with extern “C”{}, we tell the linker to treat the contents in curly braces as C. You can still write C++ code here, but these methods look like C methods for the rest of our program.

I’ve written a small placeholder method called handle_cmd. It can be used as our message loop in the future. Any touch event, any window event will pass through here.

The main thing in this code is android_main. This method is called by android_native_app_glue when your app starts up. We start by pointing pApp->onAppCmd to our message loop so that the system messages have a place to go.

We then use ALooper_pollAll to process all the queued system events, the first parameter being the timeout parameter. If the above method returns a value greater than or equal to zero, we need to use pSource to handle the event, otherwise, we will continue until the application is closed.

You still can’t run the Activity, but you can build it to make sure everything works.

Add the required information to ApplicationManifest

Now we need to fill in the androidmanifest.xml to tell the system how to run your application. The file is located in APP > Manifests >AndroidManfiest.xml:

First, we tell the system which is local Activity (known as the “android. App. NativeActivity”) and the screen direction or keyboard state changes of the time don’t destroy the Activity:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.pux0r3.helloworldc">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name="android.app.NativeActivity"
            android:configChanges="orientation|keyboardHidden"
            android:label="@string/app_name"></activity>
    </application>
</manifest>
Copy the code

We then tell the local Activity where to find the code we want to run. Check your cmakelists.txt file if you forget the name!

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.pux0r3.helloworldc">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name="android.app.NativeActivity"
            android:configChanges="orientation|keyboardHidden"
            android:label="@string/app_name">
            <meta-data
                android:name="android.app.lib_name"
                android:value="helloworld-c" />
        </activity>
    </application>
</manifest>
Copy the code

We also tell the Android operating system that this is both the start Activity and the main Activity:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.pux0r3.helloworldc">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name="android.app.NativeActivity"
            android:configChanges="orientation|keyboardHidden"
            android:label="@string/app_name">
            <meta-data
                android:name="android.app.lib_name"
                android:value="helloworld-c" />

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
Copy the code

If all goes well, you can click debug and see a blank window!

Prepare OpenGL

There are already excellent OpenGL sample programs in Google’s sample library:

  • googlesamples/android-ndk: Android Studio NDK sample application. Sign up to contribute to Googlesamples/Android-NDK

I’ll give you some helpful hints. First, to use OpenGL, add the following to your cmakelists.txt file:

There’s a lot you can do here for different Android architecture platforms, but for recent versions of Android, adding EGL and GLESv3 to your target is a good move.

Next, I created a class called Renderer to handle the rendering logic. If you build a class that starts the renderer with a constructor, destroys it with a destructor, and renders it with the Render () method, THEN I recommend that your app look something like this:

extern "C" {
void handle_cmd(android_app *pApp, int32_t cmd) {
    switch (cmd) {
        case APP_CMD_INIT_WINDOW:
            pApp->userData = new Renderer(pApp);
            break;

        case APP_CMD_TERM_WINDOW:
            if (pApp->userData) {
                auto *pRenderer = reinterpret_cast<Renderer *>(pApp->userData);
                pApp->userData = nullptr;
                delete pRenderer;
            }
    }
}

void android_main(struct android_app *pApp) {
    pApp->onAppCmd = handle_cmd;
    pApp->userData;

    int events;
    android_poll_source *pSource;
    do {
        if (ALooper_pollAll(0, nullptr, &events, (void **) &pSource) >= 0) {
            if(pSource) { pSource->process(pApp, pSource); }}if(pApp->userData) { auto *pRenderer = reinterpret_cast<Renderer *>(pApp->userData); pRenderer->render(); }}while (!pApp->destroyRequested);
}
}
Copy the code

So, the first thing I do is use the field named userData in android_app. You can store anything you want here, and every instance of Android_app can get it. I added it to my renderer.

Then, you can only get a renderer after the window is initialized and it must be released when the window is destroyed. I use the handle_cmd method mentioned earlier to do this.

Finally, if I have a renderer (that is, the window has been created), I grab it from Android_app and make it render. Otherwise, you just continue with the loop.

conclusion

Now you can use OpenGL ES 3 just like on any other platform. If you need more resources or tutorials, here are some useful links:

  • Google’s Android NDK example helped me tremendously in writing this tutorial: github.com/googlesampl…
  • Local Activity: github.com/googlesampl…
  • CMake is my preferred build system for using C++ on Android and can be found on the reference page here: cmake.org/
  • If you are new to CMake, or if you are not familiar with the use of target_include_directories instead of include_directories, you are advised to check out the “modern” version of CMake: Cliutils. Gitlab. IO/modern – cmak…
  • OpenGL ES 3 reference: www.khronos.org/registry/Op…
  • OpenGL on Android for the Java version of the tutorial. It centered on Java, but discussed many Android peculiar problem: developer.android.com/training/gr…
  • NeHe’s OpenGL tutorial is a bit outdated and focuses on older desktop versions of OpenGL. I haven’t found a better OpenGL starter tutorial than this one: nehe.gamedev.net/

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.