Thursday, July 23, 2009

Writing applications using the Android NDK

I did not find any easy to read tutorials on how to exactly build an Android app with some native code component. Its true that the NDK package comes with some docs, but for Android beginners, this may not be an exciting prospect. However, I still maintain that the docs in the package are a must-read.

The following app simply prints out a small string returned from a C function on the LogView.

1. Create a Normal Android app. Lets call it 'native1'.
2. The main source file should look like this.


package com.earl.native1;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class native1 extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

Log.i("--EARL--", new NativeLib().getHelloWorld());
}
}

class NativeLib
{
public native String getHelloWorld();

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

}



3. If you are using Eclipse, the class files built will be in the 'bin' directory.
4. Navigate to it and run 'javah' on the class file that contains the native method.

javah -jni com.earl.native1.NativeLib

5. After this, you will get one header file. This file contains the JNIEXPORTs generated from the java source.
6. Copy this file and paste it in a folder in the 'sources' dir of the Android NDK. I'm calling the folder 'earl'.
7. Now, create the C implementation file - earl.c It should look like this.


#include "com_earl_native1_NativeLib.h"

JNIEXPORT jstring JNICALL Java_com_earl_native1_NativeLib_getHelloWorld
(JNIEnv * env, jobject obj)
{
return (*env)->NewStringUTF(env, "Greetings from Earlence !");
}



8. Now you have to create the Android.mk file. This file describes your native code module to the build system. This file is created in the same dir as the source file.



# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := helloworld
LOCAL_SRC_FILES := earl.c

include $(BUILD_SHARED_LIBRARY)




9. Now, navigate to the 'apps' dir in the NDK home and create a folder of your choice. This will hold the Android Application you built using Eclipse earlier. I am calling it earl_jni. In this folder, create another one and call it 'projects'. In this, copy your ENTIRE eclipse project 'native1'. Then, navigate one level up to earl_jni and create the Application.mk file.



APP_PROJECT_PATH := $(call my-dir)/project
APP_MODULES := helloworld



10. Now, navigate to the NDK home and run the make command.

$ make APP=earl_jni

11. You should see this output if everything works correctly.


Android NDK: Building for application 'earl_jni'
Compile thumb : helloworld <= sources/earl/earl.c
SharedLibrary :
libhelloworld.so
Install : libhelloworld.so => apps/earl_jni/project/libs/armeabi


12. Now, a folder called 'libs' will be created in apps/earl_jni/projects. This contains the .so file i.e the native library. Copy this into the eclipse project root. Compile and run. You should see something like on the LogView


I/ActivityManager( 564): Starting activity: Intent { action=android.intent.acti
on.MAIN categories={android.intent.category.LAUNCHER} flags=0x10200000 comp={com
.earl.native1/com.earl.native1.native1} }
I/--EARL--( 699): Greetings from Earlence !
I/ActivityManager( 564): Displayed activity com.earl.native1/.native1: 704 ms


Congratulations!!! You have just built your first JNI app for Android.





19 comments:

  1. Can you then just copy the .so file into the original eclipse project folder and load up the debugger? Or do I need to reload eclipse pointing to the new project folder?

    ReplyDelete
  2. yes, copy the 'lib' folder that gets created in the NDK directory. Then, refresh your eclipse project.

    ReplyDelete
  3. Hi
    I tried the example given above exactly as you've described. I got the following error.


    $make APP=earl_jni
    Android NDK: Building for appplication 'earl_jni'
    sources/earl/Android.mk: build/cor/clear-vars.mkLOCAL_MODULE: No such file or directory
    sources/earl/Android.mk:13: :=: No such file or directory
    sources/earl/Android.mk:13: helloworld: No such file or directory
    build/core/buile-shared-library.mk:24: *** Android NDK: Missing LOCAL_MODULE before including BUILD_SHARED_LIBRARY in sources/earl/Android.mk .
    Stop.

    where is this 'helloworld' library we are trying to load..?

    ReplyDelete
  4. ouch...

    Everything seemed to go to plan (other than some confusion around the project vs projects directory name) until the last step. After copying the .so file to the eclipse project root folder, refreshing and running, the app crashes with the DDMS LogCat output saying..

    ERROR/AndroidRuntime(1113): Caused by: java.lang.UnsatisfiedLinkError: Library helloworld not found

    any ideas?

    ReplyDelete
  5. I copied the whole libs directory over instead of the libhelloworld.so file and it works now.

    Thanks

    ReplyDelete
  6. Im getting this error:

    Exception Ljava/lang/UnsatisfiedLinkError; thrown during Lcs/washington/tester/NativeLib

    any ideas?

    ReplyDelete
  7. Great tutorial! Got my first "hello world" C app up and running. :)

    However, you don't actually need to move your Eclipse library or header file anywhere. You can use soft links to "fake" that. This should make app development simpler.

    1. Make a folder in your Eclipse project (eg NativeTest) and put everything that needs to go in the NDK sources/ directory there: Android.mk, *.h, *.c. Then, ln -s /.../youreclipseproject/NativeTest/ /.../android-ndk/sources/NativeTest/

    2. Instead of copying your Eclipse project into the apps/ directory, just make a symbolic link inside apps/ that points to your Eclipse project. That way the "libs" directory will get created inside your Eclipse project, so you can run & debug your project just as usual from inside Eclipse without needing to duplicate or move files. I made a file Application.mk inside the top level directory of my Eclipse folder.

    ReplyDelete
  8. 4. Navigate to it and run 'javah' on the class file that contains the native method.

    When I do this I get

    "'javah' is not recognized as an internal or external command

    How do i navigate to the project bin folder and be able to ust javah thats in my JDK bin folder?

    ReplyDelete
  9. Hi,

    I have crated library i.e .so file everything is fine until then. when i try to run my application, ddms thowing error msg "No JNI_OnLoad found in /data/data/com.wishu.jnijava/lib/libtest_jni_lib.so 0x4329c140"
    Please let me know what could be the problem...

    Thank You
    Vishal

    ReplyDelete
  10. Where do i need to go and write the make command...as in inside windows command prompt or inside cygwin.
    inside command prompt it is giving me the error that make command is not found, and inside cygwin it says APP=earl_ini is not a command.

    please guide me
    thank you

    ReplyDelete
  11. thanks for the tutorial! Very helpful.

    I'm kind of curious why you're copying the entire eclipse project into the ndk directory though. If you create a jni folder in your eclipse project directory and put your NativeLib.h and hello.c files into it, you should be able to just call ndk-build in your project root and not have to do as much moving around and copying of files and folders. The only snags I hit when trying that were making sure my project path didn't have any spaces in it and updating my eclipse project (f5 in the Package Explorer) to make it see the new lib and jni directories.

    Vish: Are you sure that's an error message? I receive the same thing on LogCat when I run, but it's listed as a debug message. I just assumed it meant that there's an expected onLoad function that we're not defining (because we don't expressly need one).

    draffodx: I got the same thing at first too. Along with the basic Java stuff you need to install for eclipse, you also need to install the Java JDK. Assuming default options, javah can then be found in C:\Program Files\java\jdk1.whateverVersion\bin\javah.exe The default JDK install should also automatically add this to your path.

    ReplyDelete
  12. Very Very Excellent job done, it helped me allot, thanks.

    free android app

    ReplyDelete
  13. Latest reviews and news about android phones, free droid app and android mobile

    free android appe

    ReplyDelete
  14. lsndel06@lsndel06-PC /cygdrive/c/android-ndk-r5b
    $ make APP=ndk_demo
    Android NDK: There is no Android.mk under apps/ndk_demo/project/jni
    Android NDK: If this is intentional please define APP_BUILD_SCRIPT to point
    Android NDK: to a valid NDK build script.
    build/core/add-application.mk:126: *** Android NDK: Aborting... . Stop.


    I GOT THIS ERROR EVERY TIME WHEN I RUN THE MAKE COMMAND.

    ReplyDelete
  15. the system has changed with the new NDK. most of these steps may or may not be applicable. things have become easier. d.android.com should provide enough information.

    ReplyDelete
  16. 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...

    ReplyDelete
  17. This is one of the reliable post.I like your log strategy.This is one of the wonderful post.
    Android app developers

    ReplyDelete
  18. Actual acceptable and informative article absolutely . It helps me a lot to enhance my knowledge, I absolutely like the way the presented presented his views , a lot of thanks for all your admired assignment on this article.

    Android developers

    ReplyDelete