Android NDK Programming Tutorial : Getting Started with the NDK

Saturday, January 7, 20120 comments

Android NDK Programming Tutorial, Learn how to install the Android NDK and begin using it. By the end of this tutorial, you will have created your own project that makes a simple call from Java code to native C code.

Prerequisite Experience

Before we get started, we need to take a moment here to discuss the level of this tutorial. It’s flagged as advanced. The reason for this is that we, the authors, are going to assume that you would agree with the following statements:

  1. You are experienced with Java and C.
  2. You are comfortable using the command line.
  3. You know how to figure out what version of Cygwin, awk, and other tools you have.
  4. You are comfortable with Android Development.
  5. You have a working Android development environment (as if this writing, the authors are using Android 2.2)
  6. You use Eclipse or can translate Eclipse instructions to your own IDE with ease.

If you aren’t comfortable with these, you’re welcome to read this tutorial, of course, but you may have difficulties at certain steps that would be resolved by being comfortable with the above. That said, using the NDK is still a process that is prone to problems and issues even if you consider yourself a mobile development veteran. Be aware that you may have to do some troubleshooting of your own before you get everything working smoothly on your development system.

The complete sample project for this tutorial can be downloaded open source code.

A Note About When to Use NDK

So, if you’re reading this tutorial, you may already be considering the NDK for your Android projects. However, we’d like to take a moment to talk about why the NDK is important, when it should be used, and—just as importantly, when it should not be used.

Generally speaking, you only need to use the NDK if your application is truly processor bound. That is, you have algorithms that are using all of the processor within the DalvikVM and would benefit from running natively. Also, don’t forget that in Android 2.2, a JIT compiler will improve the performance of such code as well.

Another reason to use the NDK is for ease of porting. If you’ve got loads of C code for your existing application, using the NDK could speed up your project’s development process as well as help keep changes synchronized between your Android and non-Android projects. This can be particularly true of OpenGL ES applications written for other platforms.

Don’t assume you’ll increase your application’s performance just because you’re using native code. The Java<->Native C exchanges add some overhead, so it’s only really worthwhile if you’ve got some intensive processing to do.

Step 0: Downloading the Tools

Alright, let’s get started. You need to download the NDK. We’ll do this first, as while it’s downloading you can check to make sure you have the right versions of the rest of the tools you need.

Download the NDK for your operating system from the Android site.

Now, check the versions of your tools against these:

  1. If on Windows, Cygwin 1.7 or later
  2. Update awk to the most recent version (We’re using 20070501)
  3. GNU Make 3.81 or later (We’re using 3.81)

If any of these versions are too old, please update them before continuing.

Step 1: Installing the NDK

Now that the NDK is downloaded (it is, right?), you need to unzip it. Do so and place it in an appropriate directory. We put ours in the same directory that we put the Android SDK. Remember where you put it.
At this point, you may want to add the NDK tools to your path. If you’re on Mac or Linux, you can do this with your native path setting. If you’re on Windows using Cygwin, you need to configure the Cygwin path setting.

Step 2: Creating the Project

Create a regular Android project. To avoid problems later, your project must reside in a path that contains no spaces. Our project has a package name of “com.mamlambo.sample.ndk1” with a default Activity name of “AndroidNDK1SampleActivity” – you’ll see these appear again soon.

At the top level of this project, create a directory called “jni” – this is where you’ll put your native code. If you’re familiar with JNI, the Android NDK is heavily based on JNI concepts – it is, essentially, JNI with a limited set of headers for C compilation.

Step 3: Adding Some C Code

Now, within the jni folder, create a file called native.c. Place the following C code in this file to start; we’ll add another function later:

#include   
  • #include
  • #include

  • #define DEBUG_TAG "NDK_AndroidNDK1SampleActivity"

  • void Java_com_mamlambo_sample_ndk1_AndroidNDK1SampleActivity_helloLog(JNIEnv * env, jobject this, jstring logThis)
  • {
  • jboolean isCopy;
  • const char * szLogThis = (*env)->GetStringUTFChars(env, logThis, &isCopy);

  • __android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "NDK:LC: [%s]", szLogThis);

  • (*env)->ReleaseStringUTFChars(env, logThis, szLogThis);
  • }
  • This function is actually fairly straightforward. It takes in a Java object String parameter, converts it in to a C-string, and then writes it out to LogCat.

    The name of the function, though, is important. It follows the specific pattern of “Java,” followed by the package name, followed by the class name, followed by the method name, as defined from Java. Every piece is separated by an underscore instead of a dot.

    The first two parameters of the function are critical, too. The first parameter is the JNI environment, frequently used with helper functions. The second parameter is the Java object that this function is a part of.

    Step 4: Calling Native From Java

    Now that you have written the native code, let’s switch back over to Java. In the default Activity, create a button and add a button handler however you want. From within your button handler, make the call to helloLog:

    1. helloLog("This will log to LogCat via the native call.");

    Then you have to add the function declaration on the Java side. Add the following declaration to your Activity class:

    1. private native void helloLog(String logThis);

    This tells the compilation and linking system that the implementation for this method will be from the native code.

    Finally, you need to load the library that the native code will ultimately compile to. Add the following static initializer to the Activity class to load the library by name (the library name itself is up to you, and will be referenced again in the next step):


    1. static {
    2. System.loadLibrary("ndk1");
    3. }

    Step 5: Adding the Native Code Make File

    Within the jni folder, you now need to add the makefile that will be used during compilation. This file must be named “Android.mk” and if you named your file native.c and your library ndk1, then the Android.mk contents will look like this:

    1. LOCAL_PATH := $(call my-dir)

    2. include $(CLEAR_VARS)

    3. LOCAL_LDLIBS := -llog

    4. LOCAL_MODULE := ndk1
    5. LOCAL_SRC_FILES := native.c

    6. include $(BUILD_SHARED_LIBRARY)

    Step 6: Compiling the Native Code

    Now that your native code is written and your make file is in place, it’s time to compile the native code. From the command line (Windows users, you’ll want to do this within Cygwin), you’ll need to run the ndk-build command from the root directory of your project. The ndk-build tool is found within the NDK tools directory. We find it easiest to add this tool to our path.

    Fig 1: Typical build output from the ndk-build command

    On subsequent compiles, you can make sure everything is recompiled if you use the “ndk-build clean” command.

    Step 7: Running the Code

    Now you’re all set to run the code. Load the project in to your favorite emulator or handset, watch LogCat, and push the button.

    One of two things may have happened. First, it may have worked. If so, congratulations! But you might want to read on, anyway. You probably got an error to LogCat saying something like, “Could not execute method of activity.” This is fine. It just means you missed a step. This is easy to do in Eclipse. Usually, Eclipse is configured to recompile automatically. What it doesn’t do is recompile and relink automatically if it doesn’t know anything has changed. And, in this case, what Eclipse doesn’t know is that you compiled the native code. So, force Eclipse to recompile by “cleaning” the project (Project->Clean from the Eclipse toolbar).

    Step 8: Adding Another Native Function

    This next function will demonstrate the ability to not only return values, but to return an object, such as a String. Add the following function to native.c:


    1. jstring Java_com_mamlambo_sample_ndk1_AndroidNDK1SampleActivity_getString(JNIEnv * env, jobject this, jint value1, jint value2)
    2. {
    3. char *szFormat = "The sum of the two numbers is: %i";
    4. char *szResult;

    5. // add the two values
    6. jlong sum = value1+value2;

    7. // malloc room for the resulting string
    8. szResult = malloc(sizeof(szFormat) + 20);

    9. // standard sprintf
    10. sprintf(szResult, szFormat, sum);

    11. // get an object string
    12. jstring result = (*env)->NewStringUTF(env, szResult);

    13. // cleanup
    14. free(szResult);

    15. return result;
    16. }

    For this to compile, you’ll want to add an include statement as well for stdio.h. And, to correspond to this new native function, add the following declaration in your Activity Java class:

    1. private native String getString(int value1, int value2);

    You can now wire up the functionality however you like. We used the following two calls and outputs:

    1. String result = getString(5,2);
    2. Log.v(DEBUG_TAG, "Result: "+result);
    3. result = getString(105, 1232);
    4. Log.v(DEBUG_TAG, "Result2: "+result);

    Back to the C function, you’ll note that we did a couple things. First, we create need a buffer to write to for the sprintf() call using the malloc() function. This is reasonable so long as you don’t forget to free the results when you’re done using the free() function. Then, to pass the result back, you can use a JNI helper function called NewStringUTF(). This function basically it takes the C string and makes a new Java object out of it. This new String object can then be returned as the result and you’ll be able to use it as a regular Java String object from the Java class.

    Fig 2: Screen from sample implementation

    Instruction Sets, Compatibility, Etc.

    The Android NDK requires Android SDK 1.5 or later. In later versions of the NDK, new headers have been made available for expanded access to certain APIs—in particular, OpenGL ES libraries.

    However, that’s not the compatibility we’re talking about. This is native code, compiled to the processor architecture in use. So, one question you might be asking yourself is what processor architectures are supported? In the current NDK (as of this writing) only the ARMv5TE and ARMv7-A instruction sets are supported. By default, the target is set to ARMv5TE, which will work on all Android devices with ARM chips.

    There are plans for further instruction sets (x86 has been mentioned). This has an interesting implication: an NDK solution will not work on all devices. For instance, there are Android tablets out there that use the Intel Atom processor, which has an x86 instruction set.

    So how does the NDK work on the emulator? The emulator is running a true virtual machine, including full processor emulation. And yes, that means when running Java within the emulator you’re running a VM inside a VM.

    Conclusion

    How did you do? Did you get Android NDK installed and ultimately make a functional, running application that uses native C code as part of it? We hope so. There are many potential “gotchas!” along the way but in some cases, it can be worth the effort. As always, we’d love to hear your feedback.

    Share this article :
    Powered by Blogger.
     
    Copyright © 2011. Android Tutorials - All Rights Reserved
    Proudly powered by Blogger