Using the Beacon Mountain Toolset and NDK for Native App Development


Download as PDF

Download Source Code

Summary: The goal of this project is to demonstrate how easy it is to build native Android apps with the Beacon Mountain toolset and Intel NDK.  We will do this by building a simple game. We will walk through the steps of installing tools with Beacon Mountain, building the game, and testing it with the Intel® Hardware Accelerated Execution Manager (Intel® HAXM) emulator. Commented source code is also available.

Installing Beacon Mountain

Beacon Mountain is a one-click install for most of the tools needed for developing Android* applications, including Eclipse* and the Android SDK and NDK. This can save hours or even days of downloading, building, and installing different packages and development tools.

Install Beacon Mountain from here: http://software.intel.com/en-us/vcsource/tools/beaconmountain

Creating the project

  1. Open Eclipse ADT and create a new workspace called MazeGame.



    Click the New Android Application button and set the project name to MazeGame. Change all API levels to API 17: Android 4.2.



    Click Next, accepting all default settings, until the Finish button appears, then click it.
     
  2. Since we are creating an app that will involve native C++ code, we need to set the NDK location. Click Window->Preferences and expand the Android menu. Browse to the location of your Beacon Mountain install folder, select the NDK folder inside it, and click OK.


     
  3. To enable native C++ compilation, right-click the project, and select Android Tools->Add Native Support.



    Accept the default library name by clicking Finish.
     
  4. By default, our project will only build for ARM devices. To enable building for x86 devices, we'll need to create an Application.mk file alongside our Android.mk in the /jni folder and add the following.
APP_ABI := x86 armeabi
APP_STL := stlport_static

After building, you should see armeabie and x86 folders inside MazeGame/MazeGame/bin.

Game Structure

Although there are many good ways to structure our game, we'll start with the simplest possible format:

  • A nearly empty activity that loads a view.
  • A view that extends GLSurfaceView. We'll call into our native code from here to render each frame.
  • A C++ MazeGame class that will manage all the game objects, the physics engine, communication with the Java* wrapper and OpenGL* setup.
  • A C++ GameObject class that will manage object position, 3D model parsing, and drawing itself.

Calling Native C++ Code From Java

To call native code, we'll need to load our library (the one we configured when we created the Project) at the end of our view file.

static {
        System.loadLibrary("MazeGame");
    }

Note that the actual library (inside the lib/x86 folder) will be called libMazeGame.so, not MazeGame.so.

We'll also need to define Java versions of the native functions we'll be calling:

    public native void init(int rotationDegrees);
    public native void restart();
    public native void setRotation(int degrees);
    public native void loadResources(Bitmap circuitBoardBitmap, Bitmap componentsBitmap, Bitmap stripesBitmap, Bitmap ballBitmap);
    public native void resize(int width, int height);
    public native void renderFrame(double timeStepSeconds, double currTimeSeconds);
    public native void accelerometerChanged(float x, float y);
    public native void deinit();

Finally, we'll need to define these functions in MazeGame.cpp. Native code functions require a very unique format to be externally callable:

JNIEXPORT void JNICALL Java_com_example_mazegame_MazeGameView_init(JNIEnv* env, jobject thisClazz, int rotationDegrees){
gameInst = new MazeGame(env, thisClazz, rotationDegrees);
gameInst->restart();
}

Notice the function name. It starts with the full classpath of the Java file that will be calling into it. Also, the first two arguments are passed in by the system, so they are required, and there are no matching parameters for them on the Java side.

Because this is C++ and not C, we'll need to add EXTERN C linkage for them above the function definitions.

extern "C" {
JNIEXPORT void JNICALL Java_com_example_mazegame_MazeGameView_init(JNIEnv* env, jobject obj, int rotationDegrees);

Calling Java From C++

Some tasks, like playing sounds and opening dialogs, are best done in Java, so we'll need a way to call back out from our native C++ code. In our constructor, we'll save references to the calling class and the PlaySound method on that class:

MazeGame::MazeGame(JNIEnv* env, jobject clazz, int rotationDegrees)
{
_environment = env;
_callingClass = (jclass)(env->NewGlobalRef(clazz));
jclass viewClass = env->FindClass("com/example/mazegame/MazeGameView");
_playSoundMethodID = env->GetMethodID(viewClass, "PlaySound", "(Ljava/lang/String;)V");
_showGameOverDialogMethodID = env->GetMethodID(viewClass, "ShowGameOverDialog", "()V");

Then, when we are ready to play a sound, we can simply call the saved reference:

void MazeGame::playSound(const char* soundId){
jstring jstr = _environment->NewStringUTF(soundId);
    _environment->CallVoidMethod(_callingClass, _playSoundMethodID, jstr);
}

Integrating Box2D

One of the best things about the NDK is that it allows development teams to use existing C++ libraries, such as the well-known Box2D physics engine. After downloading and unzipping Box2D, move it into the jni folder. We'll also need to link in all of the Box2D libraries in our jni/Android.mk file:

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := maze-game
FILE_LIST := $(wildcard $(LOCAL_PATH)/*.cpp) $(wildcard $(LOCAL_PATH)/Box2D/Collision/*.cpp) $(wildcard $(LOCAL_PATH)/Box2D/Collision/Shapes/*.cpp) $(wildcard $(LOCAL_PATH)/Box2D/Common/*.cpp) $(wildcard $(LOCAL_PATH)/Box2D/Dynamics/*.cpp) $(wildcard $(LOCAL_PATH)/Box2D/Dynamics/Contacts/*.cpp) $(wildcard $(LOCAL_PATH)/Box2D/Dynamics/Joints/*.cpp)
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%)

Now we can include Box2D in our code:

#include 
...
b2World* _world;

Testing with the Intel HAXM Emulator

The Intel HAXM emulator, part of the Beacon Mountain toolset, provides a massive speed increase over the stock Android emulators. This can be crucial for game development, as testing many scenarios becomes impossible at low frame rates.

Begin by right-clicking the project and choosing Properties. Click the Run/Debug Settings item in the left-nav. To test our project, we'll need to add a launch configuration. So click the New button and select Android Application from the list.

Under the Android tab, click Browse and select the main project. Then click the Target tab and select the x86 device from the list.

Click OK. We can now test our project by right-clicking it and selecting Run As->Android Application.

Summary

This has been a high-level overview of how the Beacon Mountain toolset can accelerate Android game development. For more information, download the full source code of the sample application or check out the Beacon Mountain home page (http://software.intel.com/en-us/vcsource/tools/beaconmountain).

Per informazioni più dettagliate sulle ottimizzazioni basate su compilatore, vedere il nostro Avviso sull'ottimizzazione.