Get $500+ of the best After Effects files, video templates and music for only $20!
Advanced Android: Getting Started with the NDK

Advanced Android: Getting Started with the NDK

Tutorial Details
  • Technology: Android SDK, NDK, C
  • Difficulty: Advanced
  • Estimated Completion Time: 60-90 minutes

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 <jni.h>
#include <string.h>
#include <android/log.h>

#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:

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:

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):

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

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:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_LDLIBS := -llog

LOCAL_MODULE    := ndk1
LOCAL_SRC_FILES := native.c

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:

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

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

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

    // standard sprintf
    sprintf(szResult, szFormat, sum);

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

    // cleanup
    free(szResult);

    return result;
}

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:

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:

String result = getString(5,2);
Log.v(DEBUG_TAG, "Result: "+result);
result = getString(105, 1232);
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.

About the Authors

Mobile developers Lauren Darcey and Shane Conder have coauthored several books on Android development: an in-depth programming book entitled Android Wireless Application Development and Sams TeachYourself Android Application Development in 24 Hours. When not writing, they spend their time developing mobile software at their company and providing consulting services. They can be reached at via email to androidwirelessdev+mt@gmail.com, via their blog at androidbook.blogspot.com, and on Twitter @androidwireless.

Need More Help Writing Android Apps? Check out our Latest Books and Resources!

Buy Android Wireless Application Development, 2nd Edition  Buy Sam's Teach Yourself Android Application Development in 24 Hours  Mamlambo code at Code Canyon

Add Comment

Discussion 31 Comments

  1. Ian Rodriguez says:

    Excellent work!!!

    Very nice tut, been trying to find something about the NDK.

    Cheers!!!!!

  2. Alec Gorge says:

    Whoa. That’s a long function name.

  3. Jasperdc says:

    I’m really wondering why it is taking so long before some decent games are ported to Android like what is happening on the Palm WebOS environment… many of those games really look AMAZING! Even EA games managed to port some pretty high-end games to that platform.

    But still nothing on Android… which is really disappointing to be honest.

    • Author

      There are many great games on Android, even from big name publishers. Finding them, though, seems to be the problem.

      But, games like Asphalt HD, Call of Duty, Guitar Hero, and so on look like they should be every bit as good as their iPhone counterparts. Even EA has Android game(s).

      Oddly, some game companies — such as Gameloft — provide some of their games only as direct purchases from their own website and *not* via the Android Market. That won’t help the perception about good games being on the Market.

      • Coop says:

        If porting game engine is this easy, there would have many iphone games porting to android.

        Be real

  4. Ryan says:

    I guess I fail prerequisite #4, as I’m new to Android development. Not sure how to “In the default Activity, create a button and add a button handler however you want.” Could someone make some source code available that shows this? Otherwise super tutorial!!

    • vinay says:

      add this code to xml file

      add ” Button btn;” code in ur java file above @overide and inside “oncreate” class paste the following code

      btn=(Button)findViewById(R.id.Button01);

      btn.setOnClickListener(new View.OnClickListener() {

      @Override
      public void onClick(View v) {
      //your action…

      }
      });

      i hope it helpful:)

      • keifer says:

        I do not know anything about this field, so I have one question, How to run after successfully compile? =P

  5. Michael says:

    Hi,
    Thanks for the tutorial.
    I tried following on it but I always get the following error message :
    make: *** No rule to make target `/native.c’
    I have Eclipse 3.6 , latest cygwin and latest ndk package for Windows.

    What am I doing wrong?

  6. Slawek says:

    Hi,

    I’ve got the same issue like Michael. Could you help us?

    Best regards

  7. Nick says:

    Here’s a shortcut to an Android.mk that will work…

    # 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:

    # LOCAL_PATH := $(call my-dir)
    LOCAL_PATH := ./jni

    include $(CLEAR_VARS)

    LOCAL_LDLIBS := -llog
    LOCAL_MODULE := ndk1
    LOCAL_SRC_FILES := native.c
    include $(BUILD_SHARED_LIBRARY)

    # 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
    # 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.

    # Todo: add this to make external tools to be done with it…
    # For now: go to root of project and run ndk-build or ndk-build clean

  8. ntg says:

    Actually, found out what was wrong with Android.mk after all:
    delete all leading and following spaces from

    LOCAL_PATH := $(call my-dir)
    ^^^^delete these spaces….

  9. ntg says:

    Actually, found out what was wrong with Android.mk after all:
    delete all leading and following spaces from

    LOCAL_PATH := $(call my-dir)
    ^————————————-^^^^delete these spaces…

    Nick

    • LxZv says:

      I Tried but, doesn work, then I delete ALL the spaces something like this:

      LOCAL_PATH:=$(call my-dir)<– No spaces here
      include $(CLEAR_VARS)<– No spaces here
      LOCAL_LDLIBS:=-llog<– No spaces here
      LOCAL_MODULE:=ndk1<– No spaces here
      LOCAL_SRC_FILES:=native.c<– No spaces here
      include $(BUILD_SHARED_LIBRARY)<– No spaces here
      <– No spaces here

      And it works :)

  10. Charles Maxwell says:

    I have built an Android application using shared libraries using the NDK. The emulator is currently running on a Linux x86 platform. However, I now need to move libraries over to an ARM processor. I also would like to use a cross compiler specific for the device’s platform that also handles C++ better. Everything I’ve read so far is rather cryptic.

    1) How do I change to a new platform?
    2) How do I change to use an existing cross-compiler?

    • Author

      The emulator is emulating the Android environment. Applications running within it, even NDK ones, still run within the Dalvik virtual machine. The NDK portions use JNI to pass down to a lower layer. The emulator at this level is emulating the ARM code.

      As such, the code you’re compiling and running will work on the emulator as well as actual hardware devices. The fact that the emulator is running on an x86 Linux machine isn’t relevant.

      Another possible interpretation of your question is that you’re asking how to port the libraries over from Linux to Android on ARM. You’ll first need the source code for them. Then you’ll need to include them as you would other libraries within your Android build environment. The details of this are highly dependent on exactly what you’re building (and is thus well beyond the realm of a generic answer ;) ).

  11. Abraham says:

    Awesome Tutorial, thanks for sharing sir!!!

  12. mpla says:

    wtf? I haven’t seen any c-code like that? And they call that native? LOL

  13. immibis says:

    sizeof(szFormat) should really be strlen(szFormat), since the size of the pointer is probably 4, even though the string is much longer.

  14. DietCokeOfEvil says:

    Great post, this really helped me get up and running.

  15. Divye Kapoor says:

    There’s a bug in your native code:
    You incorrectly size the buffer szResult to be of size 24/28 bytes leading to a potential buffer overflow in your call to sprintf(szResult, szFormat, sum);

    In the code:

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

    the size of the char array allocated to szResult is 24 bytes as sizeof(szFormat) == 4 or 8 as szFormat is of type char* (which is 4 bytes on a 32 bit machine and 8 bytes on a 64 bit machine). The results would have been different if you had initialized szFormat as char[] szFormat = “…”; where upon sizeof(szFormat) would have given you the size of the format string in bytes.

    Please fix.

    Thanks.

  16. Kiet says:

    Thank you. Your demo is short and precise. I was able to pass data from C to Java. Just missing the add button as Vinay pointed out.
    Kiet

  17. Anand says:

    Hi,

    I want to write JNI for existing c code, I have c code and header, i
    created the .so file using cygwin, I wrote the Android.mk file and
    using cygwin created the shared library first it was showing
    “undefined reference to function name” error, then I removed “include $
    (CLEAR_VARS)” and again run the ndk-build command then shared library
    was created but while running it was showing UnsatisfiedLinker
    Error.my make file is
    LOCAL_PATH := $(call my-dir)

    include $(CLEAR_VARS)

    LOCAL_MODULE := test
    LOCAL_SRC_FILES := libtest.so

    include $(PREBUILT_SHARED_LIBRARY)

    #LOCAL_PATH := $(call my-dir)

    #include $(CLEAR_VARS)

    LOCAL_C_INCLUDES := $(LOCAL_PATH)

    LOCAL_MODULE := jni_anand
    LOCAL_CPP_EXTENSION := .cpp
    LOCAL_SRC_FILES := anand.cpp
    LOCAL_SHARED_LIBRARY := test

    include $(BUILD_SHARED_LIBRARY)

    Thanks…

  18. Abraham says:

    Awesome, thanks a lot.

  19. Martin says:

    Really nice guide, thank you for that!

    I have however a suggestion to improvement, you could mention that it is very important not to have any non intended spaces in the Android.mk

    When I copied your Android.mk from the site two whitespaces was inserted after LOCAL_PATH := (call my-dir) this gave me an unfriendly error and it was hard figuring out the cause, as always uncle internet saved my day.

    This might however help others.

    Thanks for a superb guide!
    Martin.

  20. krish says:

    nice tutorials..pls send me simple login form using android native apps

  21. Hwang says:

    Thx from korea!

    I love your posts.

  22. Kike says:

    Excellent post!!
    I had some trouble building in VM (virtual box – android x86). But I resolved by adding a file “Application.mk” in the “jni” folder with the line:

    APP_ABI: = x86

    Greetings (from the third world). Kike.

  23. fangstar says:

    Nice Guide.

    Had a trouble with the extra spaces in the makefile but otherwise perfect.

  24. aj_shehan says:

    The android developer site tells that using NDK does not improve the application performance since it runs on JVM nayway, is this true? are there any improvements in performance when using NDK in application practically? I read on a blog which also provides a tutorial on a basic app that uses NDK and it said that it does improve performance. Can you please clarify this for me.

    Than You

Add a Comment

To add a code snippet to your comment, please wrap your code like so: <pre name="code" class="html">YOUR CODE</pre>. You can replace the class name with "js," "css," "sql," or "php." If there are any "<" or ">" within your code, please search and replace them with: &lt; and &gt; respectively.