Introduction to Android NDK Development and CMake Usage

Introduction to Android NDK Development and CMake Usage

Today’s Tech News

On August 1st, news emerged regarding rumors that “Huawei phones were installed with monitoring software to collect privacy data.” In response, Huawei’s terminal official stated that this is a malicious defamation rumor. Additionally, Huawei claimed that it has established a relevant team to cooperate with the police in investigating the target companies and individuals involved. Notably, Huawei also called for fair competition across the industry, urging more focus on how to create value for consumers and jointly foster a healthy market environment.

Author Introduction

This article is contributed by Tsy Yuan, primarily introducing the precautions for Android NDK development and some methods for using CMake for compilation. I hope everyone enjoys it.

Tsy Yuan‘s blog address:

http://www.jianshu.com/u/21716b19302d

Overview

This article aims to briefly introduce what NDK is in Android and focus on the use of the latest Android Studio compilation tool CMake.

Introduction to NDK

Before introducing NDK, I would like to recommend the official Android NDK documentation. Here is the link:

https://developer.android.com/ndk/guides/index.html

The official documentation introduces NDK from the following aspects:

  1. Basic concepts of NDK

  2. How to compile NDK projects

  3. What is ABI and which CPU instruction sets support which ABI

  4. How to use your own and other pre-built libraries

This section will summarize and supplement the documentation. Therefore, it is recommended to browse the documentation first or read this article and then revisit the documentation.

Basic Concepts of NDK

First, let’s briefly explain JNI, NDK, and their cooperation with Android development and C/C++ development. During this explanation, common terms like Android.mk, Application.mk, ndk-build, CMake, and CMakeLists will be clarified.

  • JNI (Java Native Interface): The Java Native Interface is a layer of interfaces designed to facilitate Java’s invocation of native code such as C and C++. As we all know, Java’s advantage is its cross-platform capability, but this advantage also creates a disadvantage when interacting with native code. The cross-platform nature of Java limits its ability to interact with the operating system, leading to the introduction of JNI specifically for interacting with native code, thus enhancing Java’s capabilities for native interaction. The above text is excerpted from Ren Yugang’s introduction to Java JNI: http://blog.csdn.net/singwhatiwanna/article/details/9061545

  • NDK (Native Development Kit): The Native Development Kit is a collection of tools that assist in the development of native code, including but not limited to compilation tools, common libraries, and development IDEs.

  • The NDK toolset provides a complete set of tools for compiling C/C++ code into static/dynamic libraries, while Android.mk and Application.mk can be considered as files that describe compilation parameters and configurations. For example, specifying whether to use C++11 or C++14 for compilation, which shared libraries to reference, and describing relationships, as well as specifying the compiled ABI. Only with these compilation tools in NDK can C/C++ code be accurately compiled.

  • The ndk-build file is a shell script introduced in Android NDK r4. Its purpose is to call the correct NDK build script. Ultimately, it will still call NDK’s own compilation tools.

  • So what is CMake? Looking away from Android development, the compilation files for C/C++ differ across platforms. Unix uses Makefile files for compilation, while Windows uses project files. CMake is a cross-platform compilation tool that does not directly compile objects but generates corresponding Makefile or project files based on custom language rules (CMakeLists.txt), and then calls the underlying compiler.

After Android Studio 2.2, support for CMake was added to the tool, meaning that you can think of it as having two options for compiling your C/C++ code after Android Studio 2.2. One is the combination of ndk-build + Android.mk + Application.mk, and the other is CMake + CMakeLists.txt. These two combinations are independent of Android code and C/C++ code; they are merely different build scripts and build commands. This article will mainly describe the latter combination (which is also the one currently promoted by Android).

What is ABI

ABI (Application Binary Interface) defines the binary interface for applications. Each combination of different CPUs and instruction sets has a defined ABI. A piece of code can only run on a CPU if it adheres to the interface specifications. Therefore, the same code must be compiled into different library files for compatibility with multiple CPUs. However, different architectures do not necessarily mean incompatibility.

  • armeabi devices are only compatible with armeabi;

  • armeabi-v7a devices are compatible with armeabi-v7a and armeabi;

  • arm64-v8a devices are compatible with arm64-v8a, armeabi-v7a, and armeabi;

  • X86 devices are compatible with X86 and armeabi;

  • X86_64 devices are compatible with X86_64, X86, and armeabi;

  • mips64 devices are compatible with mips64 and mips;

  • mips is only compatible with mips;

For specific compatibility issues, you can refer to this article.

http://blog.coderclock.com/2017/05/07/android/Android-so-files-compatibility-and-adaptation/

When developing Android applications, since Java code runs on a virtual machine, we have never cared about these issues. However, when developing or using native code, it is necessary to understand different ABIs and choose to integrate libraries compatible with different ABIs for your program. (The more libraries, the larger the package, so choices are necessary.)

Next, let’s see what ABIs exist and which instruction sets correspond to them.

Introduction to Android NDK Development and CMake Usage

Using CMake

This section will focus on the rules and usage of CMake, as well as how to use CMake to compile your own and other pre-built libraries.

Hello World

We will understand CMake through a Hello World project.

First, create a new project that includes native code. When creating a New Project, check the Include C++ support option.

Introduction to Android NDK Development and CMake Usage

New Project

Once the project is created, we can see that there are four differences compared to a regular Android project.

  1. A cpp directory has been added under main, which is where the C/C++ code is placed.

  2. The module-level build.gradle file has been modified.

  3. A CMakeLists.txt file has been added.

  4. There is an additional .externalNativeBuild directory.

  5. Introduction to Android NDK Development and CMake Usage

Difference

  • build.gradle

Introduction to Android NDK Development and CMake Usage

Since CMake’s commands are integrated into gradle – externalNativeBuild, there are two places to configure CMake in gradle.

The externalNativeBuild – cmake outside of defaultConfig specifies the path to CMakeLists.txt; the externalNativeBuild – cmake inside defaultConfig mainly fills in the command parameters for CMake. The parameters in arguments are ultimately transformed into an executable CMake command, which can be found in .externalNativeBuild/cmake/debug/{abi}/cmake_build_command.txt. As follows:

Introduction to Android NDK Development and CMake Usage

cmake command

For more command parameters and their meanings, you can refer to the Android NDK-CMake documentation:

https://developer.android.com/ndk/guides/cmake.html

  • CMakeLists.txt

CMakeLists.txt mainly defines which files need to be compiled and the relationships with other libraries.

Let’s take a look at the CMakeLists.txt in the new project.

Introduction to Android NDK Development and CMake Usage

This is actually a very basic CMakeLists.txt. CMakeLists.txt can be very powerful, allowing for custom commands, file searches, header file inclusion, variable settings, etc. It is recommended to use it in conjunction with the official CMake documentation.

https://cmake.org/documentation/

At the same time, I recommend a simplified Chinese translation of the CMake manual:

https://www.zybuluo.com/khan-lau/note/254724

Using CMake with Your Own and Other Pre-built Libraries

When you need to incorporate existing static/dynamic libraries (like FFMpeg) or compile core parts yourself and provide them, you need to consider how to use your own and other pre-built libraries in CMake.

The documentation on using existing libraries on the Android NDK official website still explains the combination of ndk-build + Android.mk + Application.mk (in fact, most of the official documentation does not use CMake).

https://developer.android.com/ndk/guides/libs.html

Fortunately, there is an official example project on GitHub called hello-libs that demonstrates how to create static/dynamic libraries and reference them. Now let’s pull down the code and see how it is implemented.

https://github.com/googlesamples/android-ndk/tree/master/hello-libs

Introduction to Android NDK Development and CMake Usage

hello-libs

Let’s first look at the README introduction on GitHub:

  1. app – Uses a static library and a dynamic library from $project/distribution/.

  2. gen-libs – Generates a dynamic library and a static library and copies them to the $project/distribution/ directory. You do not need to compile this library again; the binary files are already stored in the project. Of course, if needed, you can compile your own source code by uncommenting the settings.gradle and app/build.gradle files, then executing once, and commenting them back to prevent any impact during the build.

We will analyze the module from the bottom up, starting with the gen-libs module.

  • gen-libs/build.gradle

Introduction to Android NDK Development and CMake Usage

Consulting the documentation, we can see that the arguments -DANDROID_PLATFORM represents the compiled Android platform. The documentation suggests setting minSdkVersion directly, so this parameter can be ignored. Another parameter -DANDROID_TOOLCHAIN=clang indicates that CMake has two compilation toolchains – clang and gcc, with gcc deprecated and clang being the default. Documentation address:

https://developer.android.com/ndk/guides/cmake.html

targets ‘gmath’, ‘gperf’ indicate which projects to compile. (If not filled in, it will compile all.)

  • cpp/CMakeLists.txt

Introduction to Android NDK Development and CMake Usage

The outer CMakeLists mainly uses add_subdirectory. Consulting the CMake official documentation, we can see that this command adds a sub-path for building. The CMakeLists.txt in the sub-path will also be executed, meaning it will execute the CMakeLists.txt in gmath and gperf.

CMake official documentation:

  • cpp/gmath/CMakeLists.txt

Introduction to Android NDK Development and CMake Usage

This is one of the static library’s CMakeLists.txt, while the other is quite similar, only changing STATIC to SHARED (dynamic library).

add_library(gmath STATIC src/gmath.c) was previously used to compile a static library, with the source file being src/gmath.c.

set_target_properties command is used to set some properties of the target to change how they are built. In this command, the ARCHIVE_OUTPUT_DIRECTORY property of gmath is set, which changes the output path.

add_custom_command is a custom command. This command also copies the header files to the distribution_DIR.

The above explains the process of compiling static/dynamic libraries. To summarize, here are three points:

  1. Compile static/dynamic libraries

  2. Modify output paths

  3. Copy exposed header files

Next, let’s see how the app module uses the pre-built static/dynamic libraries.

  • app/src/main/cpp/CMakeLists.txt

Introduction to Android NDK Development and CMake Usage

I have placed my explanations in comments. You can see that it is roughly divided into four steps:

  1. Individually create static/dynamic libraries, directly referencing existing .a files or .so files.

  2. Create your application’s library hello-libs.

  3. Include previously exposed header files.

  4. Link the static/dynamic libraries.

This is quite understandable. After editing and syncing, you will find that the C/C++ code in hello-libs can reference the exposed header files to call internal methods..

References

First and foremost, the official Android NDK documentation is essential to read, even though it may not be complete.

https://developer.android.com/ndk/guides/index.html

When you first encounter NDK development and find the new Hello World project too simple, I recommend pulling down the googlesamples – android-ndk project.

https://github.com/googlesamples/android-ndk

There are multiple examples available that are much more comprehensive than the official documentation.

Introduction to Android NDK Development and CMake Usage

Google Samples

When you find that some NDK configurations in the examples do not meet your needs, you will need to consult the CMake official documentation for a complete list of supported functions. Additionally, here is a simplified Chinese translation of the CMake manual.

https://www.zybuluo.com/khan-lau/note/254724

The above documentation is solely to address compilation configuration issues encountered during NDK development. The specifics of C/C++ logic writing and JNI are not covered here.

Bonus

At the end of the article, here is a bonus section listing the pitfalls and tips encountered during CMake or NDK development in a Q&A format. This will be continuously updated.

Q1: How to specify the C++ standard?

A: In build_gradle, configure cppFlags -std

Introduction to Android NDK Development and CMake Usage

Q2: How to compile all source files in a directory with add_library?

A: Use the aux_source_directory method to place all path lists into a variable.

Introduction to Android NDK Development and CMake UsageQ3: How to debug the code in CMakeLists.txt?

A: Use the message method.

Introduction to Android NDK Development and CMake Usage

Then run and check the log in .externalNativeBuild/cmake/debug/{abi}/cmake_build_output.txt.

Q4: When will the CMakeLists.txt be executed?

A:After testing, it seems that it will execute during sync. After executing once, it generates cached files like the makefile in the externalNativeBuild directory. Therefore, if there are no changes in CMakeLists.txt, it seems that it will not execute again during the next sync. (Or delete the .externalNativeBuild directory.)

During the actual compilation, it seems to only read the already parsed makefile from the .externalNativeBuild directory for compilation, without executing CMakeLists.txt again.

More

After a long day of learning, take a break and enjoy some funny jokes to relax.Follow the most entertaining public account for a good mood every day.

Introduction to Android NDK Development and CMake Usage

If you have good technical articles to share with everyone, feel free to submit to my public account. For submission details, please click the “Submission” menu on the public account homepage.

Welcome long press the image below -> scan the QR code or scan to follow my public account:

Introduction to Android NDK Development and CMake Usage

Leave a Comment