Some LinkedIn profiles say developing with a particular IDE is a skill. No! Development without any IDE is the skill! | ||
--Sergey Kosarevsky |
In this chapter, we will cover the following recipes:
Installing Android development tools on Windows
Installing Android development tools on Linux
Creating an application template manually
Adding native C++ code to your application
Switching NDK toolchains
Supporting multiple CPU architectures
Basic rendering with OpenGL ES
Going cross platform
Unifying the cross-platform code
Linking and source code organization
Signing release Android applications
This chapter explains how to install and configure Android NDK on Microsoft Windows or Ubuntu/Debian Linux, and how to build and run your first application on an Android-based device. We will learn how to set-up different compilers and toolchains that come with Android NDK. In addition, we show how to setup the GCC toolchain for Windows to build your projects. The rest of the chapter is devoted to cross-platform development using C++.
To start developing games for Android you will need some essential tools to be installed on your system.
Here is the list of all the prerequisites you will need to start developing games for Android:
Android SDK at http://developer.android.com/sdk/index.html.
Android NDK at http://developer.android.com/tools/sdk/ndk/index.html (we used Android NDK r9b).
Apache Ant at http://ant.apache.org. This is a Java command-line tool which may be unfamiliar to C++ developers. It's purpose is to build Java applications, and since every Android application has a Java wrapper, this tool will help us to pack them into archives ready for deployment (these are called
.apk
packages, which stands for Android Package).Java SE Development Kit at http://www.oracle.com/technetwork/java/javase/downloads/index.html.
Former versions of SDK/NDK for Windows required a Cygwin environment, a Linux-like environment for Windows, to be installed. Up-to-date versions of these tools can run natively on Windows without any intermediate layer. We will focus on the Cygwin-less environment and will do all of the development without IDE. You heard it right, we will just use the command line. All the examples in this book were written and debugged on a Windows PC.
To compile native Windows applications presented in this book, you will need a decent C++ compiler, such as the MinGW package with a GCC toolchain. Using Microsoft Visual Studio is also possible.
Android SDK and NDK should be installed into folders that do not contain any whitespaces in their names.
Note
This requirement comes from the limitations of scripts in Android SDK. There is a nice discussion on StackOverflow which explains some reasons behind these limitations at http://stackoverflow.com/q/6603194/1065190.
Other tools can be installed to their default locations. We used the following paths in our Windows 7 system:
Tools |
Path |
---|---|
Android SDK |
|
Android NDK |
|
Apache Ant |
|
Java Development Kit |
|
All tools have pretty decent GUI installers (see the following image, that shows the Android SDK Manager from SDK R21) so you don't have to use the command line.

For the Windows environment, you need the MinGW GCC toolchain. The easy to install all-in-one package can be found at http://www.equation.com, in the Programming Tools section, Fortran, C, C++ subsection. Alternatively, you can download the official installer from http://www.mingw.org. We will use the one from www.equation.com
You need to set some environment variables to let the tools know where the files are located. The JAVA_HOME
variable should point to the Java Development Kit folder. The NDK_HOME
variable should point to the Android NDK installation folder, and ANDROID_HOME
should point to the Android SDK folder (note the double backslash). We used the following environment variable values:
JAVA_HOME=D:\Java\jdk1.6.0_23
NDK_HOME=D:\ndk
ANDROID_HOME=D:\\android-sdk-windows
The final configuration looks similar to the one shown in the following screenshot, which shows the Windows Environment Variables dialog box:

After MinGW has been successfully installed, you should also add the bin
folder from its installation folder to the PATH
environment variable. For example, if MinGW is installed to C:\MinGW
, then PATH
should contain the C:\MinGW\bin
folder.
Installation of the basic tools on Linux is as easy as it was with their Windows counterpart. In this recipe, we will see how to install the basic Android development tools on *nix systems.
We assume you already have an Ubuntu/Debian system with the apt
package manager. Refer to http://wiki.debian.org/Apt for details.
Carry out the following steps to install the required basic tools:
Make sure you are using the latest version of the packages for your OS by running the following command:
>sudo apt-get update
Install OpenJDK 6+:
>sudo apt-get install openjdk-6-jdk
Install the Apache Ant build automation tool:
>sudo apt-get install ant
Download the official Android SDK from http://developer.android.com. There is a bigger package next to it, with the ADT plugin for the Eclipse IDE. However, since we do all of our development from the command line, we won't need it. Run the following command:
>wget http://dl.google.com/android/android-sdk_r22.2.1-linux.tgz
Unpack the downloaded .
tgz
file (the actual version might vary, 22.2.1 is the latest version as of October 2013):>tar -xvf android-sdk_r22.2.1-linux.tgz
Use
~/<sdk>/tools/android
to install the latest Platform Tools and all of the SDKs—just like in the Windows case.Failure to do so will result in an error while trying to use the Ant tool when building any application for the Android.
Get the official Android NDK from http://developer.android.com:
>wget http://dl.google.com/android/ndk/android-ndk-r9b-linux-x86_64.tar.bz2
Unpack the downloaded NDK
.tgz
file:>tar -xvf android-ndk-r9b-linux-x86_64.tar.bz2
Set the
NDK_ROOT
environment variable to your Android NDK directory (for example,~/android-ndk-r9b
in our case):>NDK_ROOT=/path/to/ndk
It is useful to put this line and the
JAVA_HOME
definition to/etc/profile
or/etc/environment
, if these settings are applicable to all the users of the system.In case you are running a 64-bit system, you must ensure that you have the 32-bit Java runtime installed also.
Run the following command to install the libraries. Failure to do so may lead to errors with
adb
andaapt
tools:>sudo apt-get install ia32-libs
First of all, we are going to create a basic template for our applications. Every Android application that is to be built via Android SDK, should contain a predefined directory structure and the configuration .xml
files. This can be done using Android SDK tools and IDEs. In this recipe, we will learn how to do it manually. We will use these files later on as the very starting point for all our examples.
Let us set up the directory structure of our project (see the following screenshot):

This is a typical structure for any Android project. We will create all the required files manually rather than using Android tools.
Place the Java Activity
code into the App1\src\com\packtpub\ndkcookbook\app1\App1Activity.java file
, which should look as follows:
package com.packtpub.ndkcookbook.app1; import android.app.Activity; public class App1Activity extends Activity { };
The localizable application name should go to App1\res\values\strings.xml
. The string parameter app_name
is used in the AndroidManifest.xml
file to specify the user-readable name of our application, as seen in the following code:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">App1</string> </resources>
Now we need to write more scripts for Apache Ant and the Android SDK build system. They are necessary to build the .apk
package of your application.
The following is the
App1/project.properties
file:target=android-15 sdk.dir=d:/android-sdk-windows
We need two more files for Ant. The following is
App1/AndroidManifest.xml
:<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.packtpub.ndkcookbook.app1" android:versionCode="1" android:versionName="1.0.0"> <supports-screens android:smallScreens="false" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" android:anyDensity="true" /> <uses-sdk android:minSdkVersion="8" /> <uses-sdk android:targetSdkVersion="18" />
Our examples require at least OpenGL ES 2. Let Android know about it:
<uses-feature android:glEsVersion="0x00020000"/> <application android:label="@string/app_name" android:icon="@drawable/icon" android:installLocation="preferExternal" android:largeHeap="true" android:debuggable="false"> <activity android:name="com.packtpub.ndkcookbook.app1.App1Activity" android:launchMode="singleTask"
Create a full-screen application in a landscape screen orientation:
android:theme="@android:style/Theme.NoTitleBar.Fullscreen" android:screenOrientation="landscape" 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>
The second file is
App1/build.xml
:<?xml version="1.0" encoding="UTF-8"?> <project name="App1" default="help"> <property file="ant.properties" /> <loadproperties srcFile="project.properties" /> <import file="${sdk.dir}/tools/ant/build.xml" /> </project>
With all the listed files in place, we can now build the project and install it on an Android device by carrying out the following steps:
From the
App1
folder run:>ant debug
The tail of the output from the previous command should look like:
BUILD SUCCESSFUL Total time: 12 seconds
To install the app, run:
>adb install App1-debug.apk
You should see the output from
adb
, similar to the following commands:* daemon not running. starting it now on port 5037 * * daemon started successfully * 1256 KB/s (8795 bytes in 0.006s) pkg: /data/local/tmp/App1-debug.apk Success
The application can now be started from your Android launcher (named App1
). You will see just a black screen. You can exit the application using the BACK button.
Don't forget to put the application icon into App1\res\drawable\icon.png
. Refer to the book's code bundle if you want to build the app quickly, or put your own icon there. 72 x 72 32-bit will do just fine. You can find the official Android icons guidelines at http://developer.android.com/design/style/iconography.html.
The official documentation on the AndroidManifest.xml
file can be found at http://developer.android.com/guide/topics/manifest/manifest-intro.html.
Furthermore, you can update your applications without uninstalling the previous version using the adb -r
command-line switch in the following way:
>adb install -r App1-debug.apk
Otherwise, before installing a new version of your application you will have to uninstall the existing one using the following command:
>adb uninstall <package-name>
Let us expand our minimalistic Java template, which was discussed in the previous recipe, so we can create a placeholder for our native C++ code.
We need to copy all the files from our App1
project to save time while creating the initial project files. This recipe will focus on the changes to be made to the App1
project in order to add the C++ code to it.
Carry out the following steps to create a placeholder for our C++ code:
Add the
jni/Wrappers.cpp
file with the following code:#include <stdlib.h> #include <jni.h> #include <android/log.h> #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "App2", __VA_ARGS__)) extern "C" { JNIEXPORT void JNICALL Java_com_packtpub_ndkcookbook_app2_App2Activity_onCreateNative( JNIEnv* env, jobject obj ) { LOGI( "Hello World!" ); } }
We need to change our
Activity
class from the previous recipe to make use of the native code we just added in the preceding section, through the following code:package com.packtpub.ndkcookbook.app2; import android.app.Activity; import android.os.Bundle; public class App2Activity extends Activity { static {
Here we load the native library named
libApp2.so
. Note the omittedlib
prefix and.so
extension:System.loadLibrary( "App2" ); } @Override protected void onCreate( Bundle icicle ) { super.onCreate( icicle ); onCreateNative(); } public static native void onCreateNative(); };
Tell the NDK build system how to treat the
.cpp
file. Create thejni/Android.mk
file. TheAndroid.mk
file is used by the Android NDK build system to find out how to treat the source code of your project:TARGET_PLATFORM := android-7 LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_ARM_MODE := arm LOCAL_MODULE := App2 LOCAL_SRC_FILES += Wrappers.cpp LOCAL_ARM_MODE := arm COMMON_CFLAGS := -Werror -DANDROID -DDISABLE_IMPORTGL \ -isystem $(SYSROOT)/usr/include/ ifeq ($(TARGET_ARCH),x86) LOCAL_CFLAGS := $(COMMON_CFLAGS) else LOCAL_CFLAGS := -mfpu=vfp -mfloat-abi=softfp \ -fno-short-enums $(COMMON_CFLAGS) endif LOCAL_LDLIBS := -llog -lGLESv2 -Wl,-s LOCAL_CPPFLAGS += -std=gnu++0x include $(BUILD_SHARED_LIBRARY)
Note the
ifeq ($(TARGET_ARCH),x86)
section. Here we specify architecture-specific compiler flags for floating point support on ARMv7. This will give you hardware floating-point support on the ARM architecture and a warnings-free log on the x86 Android target architecture..Paste the following code into the
jni/Application.mk
file:APP_OPTIM := release APP_PLATFORM := android-7 APP_STL := gnustl_static APP_CPPFLAGS += -frtti APP_CPPFLAGS += -fexceptions APP_CPPFLAGS += -DANDROID APP_ABI := armeabi-v7a APP_MODULES := App2 NDK_TOOLCHAIN_VERSION := clang
First of all, we need to compile the native code. From the root of your
App2
project, run the following command:>ndk-build
You should see the following output:
Compile++ arm: App2 <= Wrappers.cpp SharedLibrary: libApp2.so Install : libApp2.so => libs/armeabi-v7a/libApp2.so
Now proceed to the
.apk
creation as in the previous recipe by running the following command:>ant debug
Your
libApp2.so
native shared library will be packed into theApp2-debug.apk
package. Install and run it. It will output aHello World!
string into the device log.
A toolchain is a set of tools that are used to build your project. A toolchain usually consists of a compiler, an assembler, and a linker. Android NDK comes with different toolchains—GCC and Clang—of different versions. It has a convenient and simple way to switch between them.
Look through the list of the available toolchains before proceeding. You can find all the available toolchains in the $(NDK_ROOT)/toolchains/
folder.
The parameter NDK_TOOLCHAIN_VERSION
in Application.mk
corresponds to one of the available toolchains. In NDK r9b, you can switch between three GCC versions—4.6, and 4.7, which are marked as deprecated and will be removed from the next NDK releases, and 4.8. And two Clang versions—Clang3.2, which is also marked as deprecated, and Clang3.3. The default toolchain in the NDK r9b is still GCC 4.6.
Starting from the NDK r8e, you can just specify clang
as the value of NDK_TOOLCHAIN_VERSION
. This option will select the most recent version of the available Clang toolchain.
Android NDK supports different CPU architectures such as ARMv5TE and ARMv7-based devices, x86, and MIPS (big-endian architecture). We can create fat binaries that can run on any of the supported platforms.
Find out the architecture of your Android-based device. You can do it using the adb
command as follows:
>adb shell cat /proc/cpuinfo
The following are the two approaches to pick an appropriate set of CPU architectures:
By default, the NDK will generate the code for ARMv5TE-based CPUs. Use the parameter
APP_ABI
inApplication.mk
to select a different architecture, for example (use only one line from the following list):APP_ABI := armeabi-v7a APP_ABI := x86 APP_ABI := mips
We can specify multiple architectures to create a fat binary that will run on any of them through the following command:
APP_ABI := armeabi armeabi-v7a x86 mips
Let us add some graphics to our sample Android application App2
. Here, we show how to create an off-screen bitmap, and then copy it to the screen using the OpenGL ES Version 2 or 3 available on your Android device.
We assume that the reader is somewhat familiar with OpenGL and the GL Shading Language (GLSL). Refer to http://www.opengl.org/documentation for the desktop OpenGL, and http://www.khronos.org/opengles for the mobile OpenGL ES documentation.
We need to write a simple vertex and fragment GLSL shader that will render our framebuffer on the screen using OpenGL ES. Let's put them directly into
jni/Wrappers.cpp
as strings. The following code shows the vertex shader:static const char g_vShaderStr[] = "#version 100\n" "precision highp float;\n" "attribute vec3 vPosition;\n" "attribute vec3 vCoords;\n" "varying vec2 Coords;\n" "void main()\n" "{\n" " Coords = vCoords.xy;\n" " gl_Position = vec4( vPosition, 1.0 );\n" "}\n";
The fragment shader is as follows:
static const char g_fShaderStr[] = "#version 100\n" "precision highp float;\n" "varying vec2 Coords;\n" "uniform sampler2D Texture0;\n" "void main()\n" "{\n" " gl_FragColor = texture2D( Texture0, Coords );\n" "}\n";
We will also need the following helper function to load our shaders into OpenGL ES:
static GLuint LoadShader( GLenum type, const char* shaderSrc ) { GLuint shader = glCreateShader( type ); glShaderSource ( shader, 1, &shaderSrc, NULL ); glCompileShader ( shader ); GLint compiled; glGetShaderiv ( shader, GL_COMPILE_STATUS, &compiled ); GLsizei MaxLength = 0; glGetShaderiv( shader, GL_INFO_LOG_LENGTH, &MaxLength ); char* InfoLog = new char[MaxLength]; glGetShaderInfoLog( shader, MaxLength, &MaxLength, InfoLog ); LOGI( "Shader info log: %s\n", InfoLog ); return shader; }
We will not go into all the details about the OpenGL ES programming here, and will instead focus on a minimal application (App3
) that should initialize the GLView
in Java; create fragment and vertex programs, create and fill the vertex array consisting of two triangles that form a single quadrilateral, and then render them with a texture, which is updated from g_FrameBuffer
contents. This is it—just draw the offscreen framebuffer. The following is the code to draw the full-screen quad textured with the offscreen buffer content:
const GLfloat vVertices[] = { -1.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f }; const GLfloat vCoords[] = { 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f }; glUseProgram ( g_ProgramObject );
These attribute variables are declared in a vertex shader. See the value of g_vShaderStr[]
in the preceding code.
GLint Loc1 = glGetAttribLocation(g_ProgramObject,"vPosition"); GLint Loc2 = glGetAttribLocation(g_ProgramObject,"vCoords"); glBindBuffer( GL_ARRAY_BUFFER, 0 ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 ); glVertexAttribPointer( Loc1, 3, GL_FLOAT, GL_FALSE, 0, vVertices ); glVertexAttribPointer( Loc2, 3, GL_FLOAT, GL_FALSE, 0, vCoords ); glEnableVertexAttribArray( Loc1 ); glEnableVertexAttribArray( Loc2 ); glDisable( GL_DEPTH_TEST ); glDrawArrays( GL_TRIANGLES, 0, 6 ); glUseProgram( 0 ); glDisableVertexAttribArray( Loc1 ); glDisableVertexAttribArray( Loc2 );
We also need a few JNI callbacks. The first one handles the surface size changes, as seen in the following code:
JNIEXPORT void JNICALLJava_com_packtpub_ndkcookbook_app3_App3Activity_SetSurfaceSize(JNIEnv* env, jclass clazz, int Width, int Height ) { LOGI( "SurfaceSize: %i x %i", Width, Height ); g_Width = Width; g_Height = Height; GLDebug_LoadStaticProgramObject(); glGenTextures( 1, &g_Texture ); glBindTexture( GL_TEXTURE_2D, g_Texture );
Disable mip-mapping through the following code:
glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_NEAREST ); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA,ImageWidth, ImageHeight, 0, GL_RGBA,GL_UNSIGNED_BYTE, g_FrameBuffer ); }
The second callback does the actual frame rendering:
JNIEXPORT void JNICALL Java_com_packtpub_ndkcookbook_app3_App3Activity_DrawFrame( JNIEnv* env, jobject obj ) {
Invoke our frame rendering callback through the following code:
OnDrawFrame(); glActiveTexture( GL_TEXTURE0 ); glBindTexture( GL_TEXTURE_2D, g_Texture ); glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0,ImageWidth, ImageHeight, GL_RGBA,GL_UNSIGNED_BYTE, g_FrameBuffer ); GLDebug_RenderTriangle(); }
The main idea is the possibility of cross-platform development in What You See (on a PC) is What You Get (on a device), when most of the application logic can be developed in a familiar desktop environment like Windows, and it can be built for Android using the NDK whenever necessary.
To perform what we just discussed, we have to implement some sort of abstraction on top of the NDK, POSIX, and Windows API. Such an abstraction should feature at least the following:
Ability to render buffer contents on the screen: Our framework should provide the functions to build the contents of an off-screen framebuffer (a 2D array of pixels) to the screen (for Windows we refer to the window as "the screen").
Event handling: The framework must be able to process the multi-touch input and virtual/physical key presses (some Android devices, such as the Toshiba AC 100, or the Ouya console, and other gaming devices, have physical buttons), timing events, and asynchronous operation completions.
Filesystem, networking, and audio playback: The abstraction layers for these entities need a ton of work to be done by you, so the implementations are presented in Chapter 3, Networking, Chapter 4, Organizing a Virtual Filesystem, and Chapter 5, Cross-platform Audio Streaming.
Let us proceed to write a minimal application for the Windows environment, since we already have the application for Android (for example,
App1
). A minimalistic Windows GUI application is the one that creates a single window and starts the event loop (see the following example inWin_Min1/main.c
):#include <windows.h> LRESULT CALLBACK MyFunc(HWND h, UINT msg, WPARAM w, LPARAM p) { if(msg == WM_DESTROY) { PostQuitMessage(0); } return DefWindowProc(h, msg, w, p); } char WinName[] = "MyWin";
The entry point is different from Android. However, its purpose remains the same— to initialize surface rendering and invoke callbacks:
int main() { OnStart(); const char WinName[] = "MyWin"; WNDCLASS wcl; memset( &wcl, 0, sizeof( WNDCLASS ) ); wcl.lpszClassName = WinName; wcl.lpfnWndProc = MyFunc; wcl.hCursor = LoadCursor( NULL, IDC_ARROW ); if ( !RegisterClass( &wcl ) ) { return 0; } RECT Rect; Rect.left = 0; Rect.top = 0;
The size of the window client area is predefined as
ImageWidth
andImageHeight
constants. However, the WinAPI functionCreateWindowA()
accepts not the size of the client area, but the size of the window, which includes caption, borders, and other decorations. We need to adjust the window rectangle to set the client area to the desired size through the following code:Rect.right = ImageWidth; Rect.bottom = ImageHeight; DWORD dwStyle = WS_OVERLAPPEDWINDOW; AdjustWindowRect( &Rect, dwStyle, false ); int WinWidth = Rect.right - Rect.left; int WinHeight = Rect.bottom - Rect.top; HWND hWnd = CreateWindowA( WinName, "App3", dwStyle,100, 100, WinWidth, WinHeight,0, NULL, NULL, NULL ); ShowWindow( hWnd, SW_SHOW ); HDC dc = GetDC( hWnd );
Create the offscreen device context and the bitmap, which holds our offscreen framebuffer through the following code:
hMemDC = CreateCompatibleDC( dc ); hTmpBmp = CreateCompatibleBitmap( dc,ImageWidth, ImageHeight ); memset( &BitmapInfo.bmiHeader, 0,sizeof( BITMAPINFOHEADER ) ); BitmapInfo.bmiHeader.biSize = sizeof( BITMAPINFOHEADER ); BitmapInfo.bmiHeader.biWidth = ImageWidth; BitmapInfo.bmiHeader.biHeight = ImageHeight; BitmapInfo.bmiHeader.biPlanes = 1; BitmapInfo.bmiHeader.biBitCount = 32; BitmapInfo.bmiHeader.biSizeImage = ImageWidth*ImageHeight*4; UpdateWindow( hWnd );
After the application's window is created, we have to run a typical message loop:
MSG msg; while ( GetMessage( &msg, NULL, 0, 0 ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } … }
This program only handles the window destruction event and does not render anything. Compilation of this program is done with a single command as follows:
>gcc -o main.exe main.c -lgdi32
To render a framebuffer on the screen, we need to create a so-called device context with an associated bitmap, and add the WM_PAINT
event handler to the window function.
To handle the keyboard and mouse events, we add the WM_KEYUP
and WM_MOUSEMOVE
cases to the switch
statement in the previous program. Actual event handling is performed in the externally provided routines OnKeyUp()
and OnMouseMove()
, which contain our game logic.
The following is the complete source code of the program (some omitted parts, similar to the previous example, are omitted). The functions OnMouseMove()
, OnMouseDown()
, and OnMouseUp()
accept two integer arguments that store the current coordinates of the mouse pointer. The functions OnKeyUp()
and OnKeyDown()
accept a single argument—the pressed (or released) key code:
#include <windows.h> HDC hMemDC; HBITMAP hTmpBmp; BITMAPINFO BmpInfo;
In the following code, we store our global RGBA framebuffer:
unsigned char* g_FrameBuffer;
We do all OS-independent frame rendering in this callback. We draw a simple XOR pattern (http://lodev.org/cgtutor/xortexture.html) into the framebuffer as follows:
void DrawFrame() { int x, y; for (y = 0 ; y < ImageHeight ; y++) { for (x = 0 ; x < ImageWidth ; x++) { int Ofs = y * ImageWidth + x; int c = (x ^ y) & 0xFF; int RGB = (c<<16) | (c<<8) | (c<<0) | 0xFF000000; ( ( unsigned int* )g_FrameBuffer )[ Ofs ] = RGB; } } }
The following code shows the WinAPI
window function:
LRESULT CALLBACK MyFunc(HWND h, UINT msg, WPARAM w, LPARAM p) { PAINTSTRUCT ps; switch(msg) { case WM_DESTROY: PostQuitMessage(0); break; case WM_KEYUP: OnKeyUp(w); break; case WM_KEYDOWN: OnKeyDown(w); break; case WM_LBUTTONDOWN: SetCapture(h); OnMouseDown(x, y); break; case WM_MOUSEMOVE: OnMouseMove(x, y); break; case WM_LBUTTONUP: OnMouseUp(x, y); ReleaseCapture(); break; case WM_PAINT: dc = BeginPaint(h, &ps); DrawFrame();
Transfer the g_FrameBuffer
to the bitmap through the following code:
SetDIBits(hMemDC, hTmpBmp, 0, Height,g_FrameBuffer, &BmpInfo, DIB_RGB_COLORS); SelectObject(hMemDC, hTmpBmp);
And copy it to the window surface through the following code:
BitBlt(dc, 0, 0, Width, Height, hMemDC, 0, 0, SRCCOPY); EndPaint(h, &ps); break; } return DefWindowProc(h, msg, w, p); }
Since our project contains a make file the compilation can be done via a single command:
>make all
Running this program should produce the result as shown in the following screenshot, which shows the Win_Min2 example running on Windows:

The main difference between the Android and Windows implementation of a main loop can be summarized in the following way. In Windows, we are in control of the main loop. We literally declare a loop, which pulls messages from the system, handles input, updates the game state, and render s the frame (marked green in the following figure). Each stage invokes an appropriate callback from our portable game (denoted with blue color in the following figure). On the contrary, the Android part works entirely differently. The main loop is moved away from the native code and lives inside the Java Activity and GLSurfaceView classes. It invokes the JNI callbacks that we implement in our wrapper native library (shown in red). The native wrapper invokes our portable game callbacks. Let's summarize it in the following way:

The rest of the book is centered on this kind of architecture and the game functionality will be implemented inside these portable On...() callbacks.
There is yet another important note. Responding to timer events to create animation can be done on Windows with the SetTimer()
call and the WM_TIMER
message handler. We get to that in Chapter 2, Porting Common Libraries, when we speak about rigid body physics simulations. However, it is much better to organize a fixed time-step main loop, which is explained later in the book.
Right now, we have two different versions of a simple program (Win_Min2
and App3
). Let us see how to unify the common parts of the code.
In Android, the application initialization phase is different, and since we use a mixed Java plus C++ approach, the entry points will be different. In C++, we are tied to, int main()
or DWORD WinMain()
functions; whereas in Android it is up to us to choose which JNI function we may call from our Java starter code. Event handling and rendering the initialization code are also quite different, too. To do so, we mark sections of the code with pre-processor definitions and put the different OS code into different files—Wrappers_Android.h
and Wrappers_Windows.h
.
We use the standard macros to detect the OS for which the program is being compiled: Windows-targeted compilers provide the _WIN32
symbol definition, and the __linux__
macro is defined on any Linux-based OS, including Android. However, the __linux__
defination is not enough, since some of the APIs are missing in Android. The macro ANDROID
is a non-standard macro and we pass the -DANDROID
switch to our compiler to identify the Android target in our C++ code. To make this for every source file, we modify the CFLAGS
variable in the Android.mk
file.
Finally, when we write the low-level code, the detection looks like the following code:
#if defined(_WIN32) // windows-specific code #elif defined(ANDROID) // android-specific code #endif
For example, to make an entry point look the same for both the Android and Windows versions, we write the following code:
#if defined(_WIN32) # define APP_ENTRY_POINT() int main() #elif defined(ANDROID) # define APP_ENTRY_POINT() int App_Init() #endif
Later we will replace the int main()
definition with the APP_ENTRY_POINT()
macro.
To detect more operating systems, compilers, and CPU architectures, it is useful to check out a list of predefined macros at http://predef.sourceforge.net.
In the previous recipes, we learned how to create basic wrappers that allow us to run our application on Android and Windows. However, we used an ad-hoc approach since the amount of source code was low and fit into a single file. We have to organize our project source files in a way suitable for building the code for larger projects in Windows and Android.
Recall the folder structure of the App3
project. We have the src
and jni
folders inside our App2
folder. The jni/Android.mk
, jni/Application.mk
, and build.xml
files specify the Android build process. To enable the Windows executable creation, we add a file named Makefile
, which references the main.cpp
file.
The following is the content of Makefile
:
CC = gcc all: $(CC) -o main.exe main.cpp -lgdi32 -lstdc++
The idea is that when we add more and more OS-independent logic, the code resides in .cpp
files, which do not reference any OS-specific headers or libraries. For the first few chapters, this simple framework that delegates frame rendering and event handling to portable OS-independent functions (OnDrawFrame()
, OnKeyUp()
and so on) is enough.
Now we can create a cross-platform application, debug it on a PC, and deploy it to Android devices. We cannot, however, upload it on Google Play because it is not (yet) signed properly with the release key.
A detailed explanation of the signing procedure on Android is given in the developer manual at http://developer.android.com/tools/publishing/app-signing.html. We will focus on the signing from the command line and automating the entire process via batch files.
First of all, we need to rebuild the project and create a release version of the .apk
package. Let's do it with our App2
project:
>ndk-build -B >ant release
You should see a lot of text output from Ant
, which ends with something like the following command:
-release-nosign: [echo] No key.store and key.alias properties found in build.properties. [echo] Please sign App2\bin\App2-release-unsigned.apk manually [echo] and run zipalign from the Android SDK tools.
Let us generate a self-signed release key using keytool
from the JDK through the following command:
>keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
Fill out all the fields necessary for the key, as in the following command:
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 application signing. Use the jarsigner
tool from the JDK through the following code:
>jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore my-release-key.keystore bin\App2-release-unsigned.apk alias_name
This command is interactive, and it will require the user to enter the keystore password and the key password. However, we can provide passwords in a batch file in the following way:
>jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore my-release-key.keystore -storepass 123456 –keypass 123456 bin\App2-release-unsigned.apk alias_name
Passwords should match what you entered while creating your release key and keystore.
There is one more step left before we can safely publish our .apk
package on Google Play. Android applications can access uncompressed content within .apk
using mmap()
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. Android SDK has the zipalign
tool to do it, as seen in the following command:
>zipalign -v 4 bin\App2-release-unsigned.apk App2-release.apk
Now our .apk
is ready to be published.
Chapter 2, Porting Common Libraries