Android Studio and NDK development


It’s seven hundred ninety two o’clock on a Friday. You’re supposed to be preparing for the amusement park on the weekend but instead you’re scouring the internet trying to figure out how to get some clunky C++ code to work in your newer Gradle-based Android Studio project. Maybe it’s because of the Monday morning deadline you were handed from your manager. Maybe you just love to trade cotton candy and roller coaster weekends with the joy of deciphering seg-faults and native core dumps in your logical window. Whatever the case, you now find yourself scratching a bald spot into the middle of your nicely groomed coiffure and puzzling over incomplete discussions and documentation on the NDK. Hi, I’m Cliff. You’re here because you’re losing sleep/hair/time-at-Great-America over the lack of examples using Android Studio for NDK projects. I’m here because I was once in your position, scratching a similarly sized hole into my scalp. First, a preamble is in order.

You probably already know that Android Studio is this great new IDE that makes writing apps for Android a breeze and that there is no built in support for C++. You’ve likely noted the absence of “New C++ module” wizards and experimented with creating a file with cpp/cxx/misc extensions only to discover syntax highlighting is offered only for those whole cling to Eclipse. So what now? Is it even feasible to take such a dramatic step into the native world with A/S? Today we’ll walk through a trivial example to demonstrate the basics of tool support and explore hidden (undocumented) areas of Android Studio as it is actively maturing every day. I will create an NDK project as I author this post with the intention of learning while I teach. I’m assuming you have already downloaded and extracted the NDK on your machine as I go through the example. Let’s begin, shall we?

We create a new project using the trusty ol’ new project wizard. I’m naming my project MyNativeApp only because I’m short of clever names/puns that I would typically substitute. I merely want my app to contain a button which invokes a native function to return a value and a TextView to display the value. I drop into design mode after the wizard completes and flesh out a no-frills UI with these requirements in mind.

Minimal UI for NDK project
Minimal UI for NDK project

Note the onClick property setting in the lower right of the above screen shot. I use this as a quick/easy way to map methods/actions to buttons. Hitting the Cmd+Control+Up arrow hot key takes me directly into the related symbol or source code behind the UI. (I just LOVE IntelliJ Android Studio hotkeys!) I add the following code just to test my the foundation I just established:


    public void onGo(View sender) {
        TextView textView = (TextView) findViewById(R.id.textView);
        textView.setText(getMagicValue());
    }

    private String getMagicValue() {
        return "The magic comes from within the NDK! " + new Date();
    }

Here I return a hard-coded string with a date concatenated. The code is trivial enough to be written without thought yet dynamic enough to confirm it is indeed functional. Pressing Ctrl+R will build/run the app (after accepting the appropriate device from the run dialog popup). Now that we’ve stablished a working app with minimal input/output we will move on to the meat and potatoes of the project. We add the following native method:

    public native String getNativeValue();

This magic method includes the “native” keyword and lacks a definition. I will define the method using C++ code. For those of you who are unfamiliar with Java and C++ integration, the use of the “native” keyword on a method triggers the compiler to include some special fairy dust in its output. The magic fairy dust allows the runtime find the definition of the method in a compiled C++ filed and jump between the Java method the C++ function that defines the definition. It’s actually slightly more elaborate than that but fairy dust and magic jumps will suffice for now.

Part of making the jump is to define a CPP header file. First I change the base class from ActionBarActivity to Activity for reasons I will explain in a moment. I create a new “jni” folder under “app/src/main”. (Take note that the jni folder is colored blue in Android Studio, this indicates that it is a source folder.) I compile the project using the Cmd+F9 hotkey sequence and I can then open my terminal using the terminal tool window from Android Studio and run the following command (It’s important to use the terminal from Android Studio if you’re following along with this step or else you will need to change directory to the root of your project.):

javah -classpath /Users/clifton/Library/Android/sdk/platforms/android-22/android.jar:app/build/intermediates/classes/debug -d app/src/main/jni com.cliftoncraig.ndk.mynativeapp.MainActivity

JavaH
The javah command is built into the JDK. If you’re following along and it doesn’t work for you define a JAVA_HOME environment variable which points to the location where you installed the JDK then try appending $JAVA_HOME/bin to your PATH environment variable (or append %JAVA_HOME%\bin to the %Path% environment variable on Windows). This command reads compiled java class files and generates C++ header files for native methods defined in the class files. In my example I am giving it a classpath that includes the compiled output of my app module under the app folder. These classes are found under “app/build/intermediates/classes/debug”. The classpath also includes the Android runtime jar file for API 22. It is important that you include every jar and class file referenced by the class that you generate headers for. This is the reason why I changed from ActionBarActivity to a regular Activity. Had I not made the change I would need to find and include the proper Android support Jar that includes this base class. I give javah a destination parameter which uses “-d” followed by the path to the new “jni” folder I created earlier. Finally I give it the fully qualified class name that I wish to generate header files for, “com.cliftoncraig.ndk.mynativeapp.MainActivity”. After running the command I get a header that defines the C++ interface to the native function.

Result JNI header file
Result JNI header file

I look in the header and grab the relevant piece of code that I need to define the native function.

/*
 * Class:     com_cliftoncraig_ndk_mynativeapp_MainActivity
 * Method:    getNativeValue
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_cliftoncraig_ndk_mynativeapp_MainActivity_getNativeValue
  (JNIEnv *, jobject);

I create a new file under the “app/src/main/jni” folder named “mynative” and copy/paste the above code into it. Next I add a “#include” directive at the top of the file to include the generated header then modify the function declaration to add formal parameter names and add an actual definition.

#include "com_cliftoncraig_ndk_mynativeapp_MainActivity.h"

JNIEXPORT jstring JNICALL Java_com_cliftoncraig_ndk_mynativeapp_MainActivity_getNativeValue
  (JNIEnv *env, jobject iobj) {
  return env->NewStringUTF("This magic comes from JNI!");
}

I add NDK specifics to the build.grade file. I do this by modifying the defaultConfig block under the android block as follows:

    defaultConfig {
        applicationId "com.cliftoncraig.ndk.mynativeapp"
        minSdkVersion 15
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
        ndk {
            moduleName "mynative"
            abiFilter "armeabi-v7a"
            cFlags "-DANDROID_NDK"
            stl "stlport_static"
        }
    }

I have to add a property, ndk.dir, to the local.properties file under the app folder. I hit Cmd+F9 to build the project and examine the build output directory. Under the app/build/intermediates/ndk/debug/lib/armeabi-v7a folder I find a “libmynative.so” file. This is the compiled native object file that is bundled in the apk. The next step is to load the library from the Java code. I add the following code to the MainActivity.java file:

public class MainActivity extends Activity {

    static {
        System.loadLibrary("mynative");
    }
    //code truncated for brevity
}

I change the onGo method to call into the native method:

public class MainActivity extends Activity {

    public void onGo(View sender) {
        TextView textView = (TextView) findViewById(R.id.textView);
        textView.setText(getNativeValue());
    }
    //code truncated for brevity
}

I build and run the project using the Ctrl+R hotkey and verify the function is being called after clicking the go button. If you are following along then your app should be functional and you will have a very simple Android app with native C++ code. This is hardly the extent of what you can do with Android Studio. WE will now work through some tips/tricks.

Generating the header
We can create external tools to run arbitrary commands such as the javah command we saw earlier. I hit the Cmd+Shift+A hotkey to invoke the action lookup prompt. This prompt allows you to lookup any action in Android Studio. Typing external tool will find the external tool dialog. I add an external tool by clicking the plus icon at the botton of the dialog which pops up an external tool definition window. I complete the window as follows.

External tool definition
External tool definition

The top part of the window contains settings for the tool name, group and description information. These settings are straight forward. The interesting settings are at the bottom of the window. I use macros which are special variables that evaluate to various values. The macros pertain to the currently focused file or selected object. For example, if you are editing the MainActivity then this file would be considered the focused file. Examine the settings in the above screenshot. The JDKPath macro evaluates to the path of the JDK configured for your environment. The classpath macro evaluates to the project class path, saving me the need to worry about the various jars and dependencies of my project. I use the -d flag with another macro to push the output to a src/main/jni folder under the directory for the module belonging to the currently focused file. The FileClass macro evaluates to the qualified classname representing the currently focused file.

Now you have a tool to help you generate CPP headers for Java classes that have native methods. This is enough to get you some really basic NDK support. I’ll post an example later on where I’ll attempt to do something slightly more involved.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s