Mastering Android NDK

4.6 (9 reviews total)
By Sergey Kosarevsky , Viktor Latypov
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Using Command-line Tools

About this book

Android NDK is used for multimedia applications that require direct access to system resources. NDK is also the key for portability, which in turn allows a reasonably comfortable development and debugging process using familiar tools such as GCC and Clang toolchains.

This is a hands-on guide to extending your game development skills with Android NDK. The book takes you through many clear, step-by-step example applications to help you further explore the features of Android NDK and some popular C++ libraries and boost your productivity by debugging the development process.

Through the course of this book, you will learn how to write portable multi-threaded native code, use HTTP networking in C++, play audio files, use OpenGL ES 3, and render high-quality text. Each chapter aims to take you one step closer to building your application. By the end of this book, you will be able to create an engaging, complete gaming application.

Publication date:
September 2015
Publisher
Packt
Pages
328
ISBN
9781785288333

 

Chapter 1. Using Command-line Tools

In this chapter, we will take a tour of the main command-line tools, specifically related to the creation and packaging of Android applications. We will learn how to install and configure Android NDK on Microsoft Windows, Apple OS X, and Ubuntu/Debian Linux, and how to build and run your first native application on an Android-based device. Usage of command-line tools to build your projects is essential for cross-platform mobile development using C++.

Note

This book is based on the Android SDK revision 24.3.3 and the Android NDK r10e. The source code was tested with Android API Level 23 (Marshmallow).

Our main focus will be the command-line centric and platform-independent development process.

Note

Android Studio is a very nice new portable development IDE, which has recently arrived at version 1.4. However, it still has very limited NDK support and will not be discussed in this book.

 

Using Android command-line tools on Windows


To start developing native C++ applications for Android in a Microsoft Windows environment, you will need some essential tools to be installed on your system.

Start NDK development for Android using the following list of all the prerequisites you will need:

The current versions of these tools will run on Windows without using any intermediate compatibility layer; they do not require Cygwin any more.

As much as it pains us to write this, Android SDK and NDK should still be installed into folders that do not contain any whitespaces in their names. This is a limitation of build scripts within the Android SDK; the unquoted environment variables content are split into words based on tab, space and newline characters.

We will install the Android SDK to D:\android-sdk-windows, the Android NDK to D:\ndk, and other software to their default locations.

In order to compile our portable C++ code for Windows, we need a decent toolchain. We recommend using the latest version of the MinGW from the Equation package available at http://www.equation.com. You can choose 32- or 64-bit versions as you go.

Once all the tools are in their folders, you need to set environment variables to point to those install locations. The JAVA_HOME variable should point to the Java Development Kit folder:

JAVA_HOME="D:\Program Files\Java\jdk1.8.0_25"

The NDK_HOME variable should point to the Android NDK installation folder:

NDK_HOME=D:\NDK

The ANDROID_HOME should point to the Android SDK folder:

ANDROID_HOME=D:\\android-sdk-windows

Note

Note the double backslash in the last line.

Both NDK and SDK will have new versions from time to time, so it might be helpful to have the version number on the folder name and manage NDK folders per project if necessary.

 

Using Android command-line tools on OS X


Installation of Android development tools on OS X is straightforward. First of all, you will need to download the required official SDK and NDK packages from http://developer.android.com/sdk/index.html. As we are going for command-line tools, we can use the SDK Tools Only package available at http://dl.google.com/android/android-sdk_r24.0.2-macosx.zip. As for the NDK, OS X Yosemite works with the 64-bit Android NDK, which can be downloaded from http://developer.android.com/tools/sdk/ndk/index.html.

We will install all these tools into the user's home folder; in our case, it is /Users/sk.

To get Apache Ant and Gradle, the best way would be to install the package manager Homebrew from http://brew.sh and bring in the required tools using the following commands:

$ brew install ant
$ brew install gradle

This way you will not be bothered with installation paths and other low-level configuration stuff. The following are the steps to install packages and set path for them:

Note

Since the notion of this book is doing stuff from the command line, we will indeed do so the hard way. However, you are encouraged to actually visit the download page, http://developer.android.com/sdk/index.html, in your browser and check for updated versions of the Android SDK and NDK.

  1. Download the Android SDK for OS X from the official page and put it into your home directory:

    >curl -o android-sdk-macosx.zip http://dl.google.com/android/android-sdk_r24.0.2-macosx.zip
    
  2. Unpack it:

    >unzip android-sdk-macosx.zip
    
  3. Then, download the Android NDK. It comes as a self-extracting binary:

    >curl -o android-ndk-r10e.bin http://dl.google.com/android/ndk/android-ndk-r10e-darwin-x86_64.bin
    
  4. So, just make it executable and run it:

    >chmod +x android-ndk-r10e.bin
    >./android-ndk-r10e.bin
    
  5. The packages are in place. Now, add paths to your tools and all the necessary environment variables to the .profile file in your home directory:

    export PATH=/Users/sk/android-ndk-r10e:/Users/sk/android-ndk-r10e/prebuilt/darwin-x86_64/bin:/Users/sk/android-sdk-macosx/platform-tools:$PATH
    
  6. Use these variables within Android scripts and tools:

    export NDK_ROOT="/Users/sk/android-ndk-r10e"
    export ANDROID_SDK_ROOT="/Users/sk/android-sdk-macosx"
    
  7. Edit the local.properties file to set up the paths on a per-project basis.

 

Using Android command-line tools on Linux


Installation on Linux is as easy as its OS X counterpart.

Note

Indeed, Linux development environment is truly native for all kinds of Android development since all the toolchains and Android Open Source Project are based on Linux tools.

Here, we will point out just some differences. First of all, we don't need to install Homebrew. Just go with the available package manager. On Ubuntu, we prefer using apt. The following are the steps to install the packages as well as set path on Linux:

  1. Let's start with updating all apt packages and installing the default Java Development Kit:

    $ sudo apt-get update
    $ sudo apt-get install default-jdk
    
  2. Install the Apache Ant build automation tool:

    $ sudo apt-get install ant
    
  3. Install Gradle:

    $ sudo apt-get install gradle
    
  4. Download the official Android SDK which suits your version of Linux from http://developer.android.com/sdk/index.html, and unpack it into a folder in your home directory:

    $ wget http://dl.google.com/android/android-sdk_r24.0.2-linux.tgz
    $ tar –xvf android-sdk_r24.0.2-linux.tgz
    
  5. Download the official NDK package suitable for your Linux, 32- or 64-bit, and run it:

    $ wget http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86_64.bin
    $ chmod +x android-ndk-r10e-linux-x86_64.bin
    $ ./android-ndk-r10e-linux-x86_64.bin
    

    The executable will unpack the content of the NDK package into the current directory.

  6. Now you can set up the environment variables to point to the actual folders:

    NDK_ROOT=/path/to/ndk
    ANDROID_HOME=/path/to/sdk
    

    Note

    It is useful to add environment variables definitions to /etc/profile or /etc/environment. This way these settings will be applicable to all the users of the system.

 

Creating an Ant-based application template manually


Let's start with the lowest level and create a template for our applications buildable with Apache Ant. Every Android application which is to be built using Apache Ant should contain a predefined directories structure and configuration .xml files. This is usually done using Android SDK tools and IDEs. We will explain how to do it by hand to let you know the machinery behind the curtains.

Tip

Downloading the example code

You can download the example code files from your account at http://www.packtpub.com for all the Packt Publishing books you have purchased. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

For this book, the source code files can be downloaded or forked from the following GitHub repository as well: https://github.com/corporateshark/Mastering-Android-NDK

The directory structure of our minimalistic project looks like the following screenshot (see the source code bundle for the complete source code):

We need to create the following files within this directory structure:

  • res/drawable/icon.png

  • res/values/strings.xml

  • src/com/packtpub/ndkmastering/App1Activity.java

  • AndroidManifest.xml

  • build.xml

  • project.properties

The icon icon.png should be there, and currently contains a dummy image of an Android application:

The file strings.xml is required to make use of the Android localization system. In the manifest AndroidManifest.xml, we use the string parameter app_name instead of the actual application name. The file strings.xml resolves this parameter into a human readable string:

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string name="app_name">AntApp1</string>
</resources>

The Java source code of the minimal buildable application is in the App1Activity.java file:

package com.packtpub.ndkmastering;
import android.app.Activity;
public class App1Activity extends Activity
{
};

The rest three files, AndroidManifest.xml, build.xml, and project.properties, contain the description of the project necessary for Ant to build it.

The manifest AndroidManifest.xml is as follows:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.packtpub.ndkmastering"
android:versionCode="1"
android:versionName="1.0.0">

Our application will require Android 4.4 (API Level 19) and is tested with Android 6.0 (API Level 23):

<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23" />

Most of the examples in this book will require OpenGL ES 3. Let's mention it here:

<uses-feature android:glEsVersion="0x00030000"/>
<application android:label="@string/app_name"
android:icon="@drawable/icon"
android:installLocation="preferExternal"
android:largeHeap="true"
android:allowBackup="true">

Here is the name of the main activity:

<activity android:name="com.packtpub.ndkmastering.App1Activity"
android:launchMode="singleTask"

We want a fullscreen application in the landscape orientation:

android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:screenOrientation="landscape"

Our application can be started from the system launcher. The displayable name of the application is stored in the app_name parameter:

android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name">
<intent-filter>
  <action android:name="android.intent.action.MAIN" />
  <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

Note

You can read the official Google documentation on the application manifest at http://developer.android.com/guide/topics/manifest/manifest-intro.html.

The file build.xml is much simpler and will resemble mostly what Android tools would generate:

<?xml version="1.0" encoding="UTF-8"?>
<project name="App1" default="help">
  <loadproperties srcFile="project.properties" />
  <fail message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through an env var"
    unless="sdk.dir"/>
  <import file="${sdk.dir}/tools/ant/build.xml" />
</project>

There is a difference to Android SDK Tools, since we don't use ant.properties here. This was done just for the sake of simplicity and just has an educational purpose.

The same situation exists with the file project.properties, which contains platform-specific declarations:

target=android-19
sdk.dir=d:/android-sdk-windows

Now, our first application (which does not even contain any native code yet) is ready to be built. Use the following one-liner to build it:

$ ant debug

If everything was done correctly, you should see the tail of the output similar to the following:

To install an .apk file from the command line, run adb install -r bin/App1-debug.apk to install the freshlybuilt .apk on your device. Start the application from your launcher (AntApp1) and enjoy the black screen. You can use the BACK key to exit the application.

 

Creating a Gradle-based application template manually


Gradle is a more versatile Java building tool compared to Ant, which lets you handle external dependencies and repositories with ease.

Note

We recommend that you watch this video from Google available at https://www.youtube.com/watch?v=LCJAgPkpmR0 and read this official command-line building manual available at http://developer.android.com/tools/building/building-cmdline.html before proceeding with Gradle.

The recent versions of Android SDK are tightly integrated with Gradle, and Android Studio is built using it as its build system. Let's extend our previous 1_AntApp application to make it buildable with Gradle.

First, go to the root folder of the project, and create the build.gradle file with the following content:

buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:1.0.0'
  }
}
apply plugin: 'com.android.application'
android {
  buildToolsVersion "19.1.0"
  compileSdkVersion 19
  sourceSets {
    main {
      manifest.srcFile 'AndroidManifest.xml'
      java.srcDirs = ['src']
      resources.srcDirs = ['src']
      aidl.srcDirs = ['src']
      renderscript.srcDirs = ['src']
      res.srcDirs = ['res']
      assets.srcDirs = ['assets']
    }
  }
  lintOptions {
    abortOnError false
  }
}

After this, run the command gradle init. The output should be similar to the following:

>gradle init
:init
The build file 'build.gradle' already exists. Skipping build initialization.
:init SKIPPED
BUILD SUCCESSFUL
Total time: 5.271 secs

The subfolder .gradle will be created in the current folder. Now, run the following command:

>gradle build

The tail of the output should look as follows:

:packageRelease
:assembleRelease
:assemble
:compileLint
:lint
Ran lint on variant release: 1 issues found
Ran lint on variant debug: 1 issues found
Wrote HTML report to file:/F:/Book_MasteringNDK/Sources/Chapter1/2_GradleApp/build/outputs/lint-results.html
Wrote XML report to F:\Book_MasteringNDK\Sources\Chapter1\2_GradleApp\build\outputs\lint-results.xml
:check
:build
BUILD SUCCESSFUL
Total time: 9.993 secs

The resulting .apk packages can be found in the build\outputs\apk folder. Try installing and running 2_GradleApp-debug.apk on your device.

 

Embedding native code


Let's stick to the topic of this book and write some native C++ code for our template application. We will start with the jni/Wrappers.cpp file, which will contain a single function definition:

#include <stdlib.h>
#include <jni.h>
#include <android/log.h>
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "NDKApp", __VA_ARGS__))
extern "C"
{
  JNIEXPORT void JNICALL Java_com_packtpub_ndkmastering_AppActivity_onCreateNative( JNIEnv* env, jobject obj )
  {
    LOGI( "Hello Android NDK!" );
  }
}

This function will be called from Java using the JNI mechanism. Update AppActivity.java as follows:

package com.packtpub.ndkmastering;
import android.app.Activity;
import android.os.Bundle;
public class AppActivity extends Activity
{
  static
  {
    System.loadLibrary( "NativeLib" );
  }
  @Override protected void onCreate( Bundle icicle )
  {
    super.onCreate( icicle );
    onCreateNative();
  }
  public static native void onCreateNative();
};

Now, we have to build this code into an installable .apk package. We need a couple of configuration files for this. The first one, jni/Application.mk, contains the platform and toolchain information:

APP_OPTIM := release
APP_PLATFORM := android-19
APP_STL := gnustl_static
APP_CPPFLAGS += -frtti
APP_CPPFLAGS += -fexceptions
APP_CPPFLAGS += -DANDROID
APP_ABI := armeabi-v7a-hard
APP_MODULES := NativeLib
NDK_TOOLCHAIN_VERSION := clang

We use the latest version of the Clang compiler—that is 3.6, as we write these lines, and the armeabi-v7a-hard target, which enables support of hardware floating point computations and function arguments passing via hardware floating point registers resulting in a faster code.

The second configuration file is jni/Android.mk, and it specifies which .cpp files we want to compile and what compiler options should be there:

TARGET_PLATFORM := android-19
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := NativeLib
LOCAL_SRC_FILES += Wrappers.cpp
LOCAL_ARM_MODE := arm
COMMON_CFLAGS := -Werror -DANDROID -DDISABLE_IMPORTGL
ifeq ($(TARGET_ARCH),x86)
  LOCAL_CFLAGS := $(COMMON_CFLAGS)
else
  LOCAL_CFLAGS := -mfpu=vfp -mfloat-abi=hard -mhard-float -fno-short-enums -D_NDK_MATH_NO_SOFTFP=1 $(COMMON_CFLAGS)
endif
LOCAL_LDLIBS := -llog -lGLESv2 -Wl,-s
LOCAL_CPPFLAGS += -std=gnu++11
include $(BUILD_SHARED_LIBRARY)

Here, we link against OpenGL ES 2, specify compiler switches to enable the hardware floating point for non-x86 targets and list the required .cpp source files.

Use the following command from the root folder of the project to build the native code:

>ndk-build

The output should be as follows:

>ndk-build
[armeabi-v7a-hard] Compile++ arm  : NativeLib <= Wrappers.cpp
[armeabi-v7a-hard] SharedLibrary  : libNativeLib.so
[armeabi-v7a-hard] Install        : libNativeLib.so => libs/armeabi-v7a/libNativeLib.so

The last thing is to tell Gradle that we want to pack the resulting native library into the .apk. Edit the build.gradle file and add the following line to the main section of sourceSets:

jniLibs.srcDirs = ['libs']

Now, if we run the command gradle build, the resulting package build\outputs\apk\3_NDK-debug.apk will contain the required libNativeLib.so file. You can install and run it as usual. Check the line Hello Android NDK! printed into the Android system log with adb logcat.

Note

Those who do not want to tackle Gradle in such a small project without dependencies will be able to use good old Apache Ant. Just run the command ant debug to make it happen. No additional configuration files are required to put shared C++ libraries into .apk this way.

 

Building and signing release Android applications


We learned how to use the command line to create Android applications with the native code. Let's put the final stroke on the topic of the command-line tools and learn how to prepare and sign the release version of your application.

The detailed explanation of the signing procedure on Android is given in the developer manual at http://developer.android.com/tools/publishing/app-signing.html. Let's do it using Ant and Gradle.

First of all, we need to rebuild the project and create a release version of the .apk package. Let's do it with our 3_NDK project. We invoke ndk-build and Apache Ant using the following commands:

>ndk-build
>ant release

The tail of the output from Ant looks as follows:

-release-nosign:
[echo] No key.store and key.alias properties found in build.properties.
[echo] Please sign F:\Book_MasteringNDK\Sources\Chapter1\3_NDK\bin\App1-release-unsigned.apk manually
[echo] and run zipalign from the Android SDK tools.
[propertyfile] Updating property file: F:\Book_MasteringNDK\Sources\Chapter1\3_NDK\bin\build.prop
[propertyfile] Updating property file: F:\Book_MasteringNDK\Sources\Chapter1\3_NDK\bin\build.prop
[propertyfile] Updating property file: F:\Book_MasteringNDK\Sources\Chapter1\3_NDK\bin\build.prop
[propertyfile] Updating property file: F:\Book_MasteringNDK\Sources\Chapter1\3_NDK\bin\build.prop
-release-sign:
-post-build:
release:
BUILD SUCCESSFUL
Total time: 2 seconds

Let's do the same thing with Gradle. Maybe you have already noticed when we run gradle build there is a 3_NDK-release-unsigned.apk file in the build/outputs/apk folder. This is exactly what we need. This will be our raw material for the signing procedure.

Now, we need to have a valid release key. We can create a self-signed release key using keytool from the Java Development Kit using the following command:

$ keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000

This will ask us to fill out all the fields necessary for the key:

Enter keystore password:
Re-enter new password:
What is your first and last name?
  [Unknown]:  Sergey Kosarevsky
What is the name of your organizational unit?
  [Unknown]:  SD
What is the name of your organization?
  [Unknown]:  Linderdaum
What is the name of your City or Locality?
  [Unknown]:  St.Petersburg
What is the name of your State or Province?
  [Unknown]:  Kolpino
What is the two-letter country code for this unit?
  [Unknown]:  RU
Is CN=Sergey Kosarevsky, OU=SD, O=Linderdaum, L=St.Petersburg, ST=Kolpino, C=RU correct?
  [no]:  yes
Generating 2048 bit RSA key pair and self-signed certificate (SHA1withRSA) with a validity of 10000 days
for: CN=Sergey Kosarevsky, OU=SD, O=Linderdaum, L=St.Petersburg, ST=Kolpino, C=RU
Enter key password for <alias_name>
  (RETURN if same as keystore password):
[Storing my-release-key.keystore]

Now, we are ready to proceed with the actual .apk package signing. Use the jarsigner tool from the Java Development Kit to do this:

>jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore my-release-key.keystore 3_NDK-release-unsigned.apk alias_name

This command is interactive, and it will require the user to enter the keystore and the key passwords. However, we can provide both passwords as arguments to this command in the following way:

>jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore my-release-key.keystore -storepass 123456 –keypass 123456 3_NDK-release-unsigned.apk alias_name

Of course, passwords should match with what you have entered while creating your release key and keystore.

There is one more important thing left before we can safely proceed with publishing our .apk package on Google Play. Android applications can access uncompressed content within .apk using memory-mapped files and mmap() system calls, yet mmap() may imply some alignment restrictions on the underlying data. We need to align all uncompressed data within .apk on 4-byte boundaries. The Android SDK has the zipalign tool to do this, as seen in the following command:

>zipalign -v 4 3_NDK-release-unsigned.apk 3_NDK-release.apk

Now, our .apk is ready to be published on Google Play.

 

Organizing the cross-platform code


This book continues the idea from our previous book Android NDK Game Development Cookbook, Packt Publishing: the possibility of cross-platform development using the principle What You See (on a desktop PC) is What You Get (on a mobile device). Most of the application logic can be developed and tested in a familiar desktop environment such as Windows with all necessary tools at hand, and this can be built for Android using the NDK whenever necessary.

To organize and maintain the cross-platform C++ source code, we need to split everything into platform-specific and platform-independent parts. Our Android-specific native code will be stored in the jni subfolder of the project, exactly as we did in our previous minimalistic example. The shared platform-independent C++ code will go into the src-native subfolder.

 

Using TeamCity continuous integration server with Android applications


TeamCity is a powerful continuous integration and deployment server, which can be used to automate your Android application builds. This can be found at https://www.jetbrains.com/teamcity.

Note

TeamCity is free for small projects that require no more than 20 build configurations and 3 build agents and is absolutely free for open source projects. Ask for an open-source license at https://www.jetbrains.com/teamcity/buy.

The server installation procedure is straightforward. Windows, OS X, or Linux machine can be used as the server or a build agent. Here, we will show how to install TeamCity on Windows.

Download the latest version of the installer from https://www.jetbrains.com/teamcity/download and run it using the following command:

>TeamCity-9.0.1.exe

Install all components and run it as Windows Service. For simplicity, we will run both the server and the agent on a single machine, as shown in the following screenshot:

Choose the desired TeamCity server port. We will use the default HTTP port 80. Run the TeamCity Server and Agent services under the SYSTEM account.

Once the server is online, open your browser and connect to it using the address http://localhost. Create a new project and a build configuration.

Note

To work with TeamCity, you should put the source code of your project into a version control system. Git and GitHub will be a good choice.

If your project is already on GitHub, you can create a Git VCS root pointing to the URL of your GitHub repository, like this https://github.com/<your login>/<your project>.git.

Add a new command-line build step and type the content of the script:

ndk-build
ant release

You can also add signing using jarsigner here and use the zipalign tool to create the final production .apk.

Now, go to the General Settings step and add an artifact path to bin/3_NDK-release.apk. The project is ready for continuous integration.

 

Summary


In this chapter, we learned how to install and configure the essential tools for Android native development using the command line, and how to write Android application basic configuration files manually without falling back to graphical IDEs. In the subsequent chapters, we will practice these skills and build some projects.

About the Authors

  • Sergey Kosarevsky

    Sergey Kosarevsky is a software engineer with experience in C++ and 3D graphics. He worked for mobile industry companies and was involved in mobile projects at SPB Software, Yandex, Layar and Blippar. He has more than 12 years of software development experience and more than 6 years of Android NDK experience. Sergey got his PhD in the field of mechanical engineering from St. Petersburg Institute of Machine-Building in Saint-Petersburg, Russia. In his spare time, Sergey maintains and develops an open source multiplatform gaming engine, the Linderdaum Engine (http://linderdaum.com).

    Browse publications by this author
  • Viktor Latypov

    Viktor Latypov is a software engineer and mathematician with experience in compiler development, device drivers, robotics, and high-performance computing and with a personal interest in 3D graphics and mobile technology. Surrounded by computers for more than 20 years, he enjoys every bit of developing and designing software for anything with a CPU inside. Viktor holds a PhD in applied mathematics from Saint-Petersburg State University.

    Browse publications by this author

Latest Reviews

(9 reviews total)
Just the way development is intended. I'm linking it very much so far.
Good book, love having pdf and epub for the e-book formats.
Good