Sunday, July 28, 2013

Android Native Development

I decided to do a quick Android app that would do the addition of two numbers, with one version fully in java, and the second where the UI is Java but the addition itself is native (in C).

In the Java code, the key things I learn were (see code):
  1. How to reference the elements in the UI
  2. Use of EditText and TextView.
  3. How to pass from string to numbers/int or vice versa.
The code is:
 package com.example.adder;  
 import android.os.Bundle;  
 import android.app.Activity;  
 import android.view.Menu;  
 import android.view.View;  
 import android.widget.Button;  
 import android.widget.EditText;  
 import android.widget.TextView;  
 public class MyAdderActivity extends Activity {  
      private EditText mnumberA, mnumberB;  
      private TextView mResult;  
      private Button madd;  
      @Override  
      protected void onCreate(Bundle savedInstanceState) {  
           super.onCreate(savedInstanceState);  
           setContentView(R.layout.activity_my_adder);  
           mResult=(TextView)findViewById(R.id.adderResult);  
           mnumberA=(EditText)this.findViewById(R.id.editNumberA);  
           mnumberB=(EditText)this.findViewById(R.id.editNumberB);  
           madd=(Button)this.findViewById(R.id.button1);  
           madd.setOnClickListener(new View.OnClickListener() {  
                @Override  
                public void onClick(View v) {  
                     // TODO Auto-generated method stub  
                     int A=Integer.parseInt(mnumberA.getText().toString());  
                     int B=Integer.parseInt(mnumberB.getText().toString());  
                     mResult.setText(Integer.toString(A+B));  
                }  
           });  
      }  
      @Override  
      public boolean onCreateOptionsMenu(Menu menu) {  
           // Inflate the menu; this adds items to the action bar if it is present.  
           getMenuInflater().inflate(R.menu.my_adder, menu);  
           return true;  
      }  
 }  

In the native case, we replace the addition (yeah, I know, just the addition, but it is just to practice) by a call in C to add the two numbers. We talk a bit about this in here (points 8 and 9). See references in the end, but here is my short explanation:
  1. Create your java Android project, as usual (like on the previous case). Say we call the project MyProject. This will appear in Eclipse as the "top name" in your branch of the workspace and as the name of the directory, within your workspace folder.
  2. The java source will be in a package. Something like com.myname.mypackage. This is in fact the directory tree structure under MyProject/src where Eclipse places the .java file.
  3. Inside your main activity class (say MyActivity) include/load the native (C) library you are going to create with something like "static { System.loadLibrary("MyLib"); }. MyLib is the .dll (Windows) or .so (Unix/Android). As we are centered around Android, it'll be the second. By the way, as we will see below, the actual file name of the library will be "libMyLib.so", but the first "lib" and the ".so" are not mentioned.
  4. Declare within MyActivity class a native method (java) that will be used to call the native function (C). Something like "private (or public) native [return type] MyMethod([arguments...]);". For instance:  "private native int add_numbers(int A, int B);" and call it in your java code, like usual... As usual, if you declared it public, then you will be able to call it from outside your class.
  5. Create a jni directory in your project. You can do it from inside Eclipse, or outside, and do "refresh" for Eclipse to find it.
  6. Put inside it the .c sources (New-->File-->whatever.c...). Somehow when I used .cpp it was not working (I'll get a "java.lang.UnsatisfiedLinkError exception !" at run time because it does not find the functions).
  7. The wording within the c code is not "straighforward". Basically it is telling the jni and the NDK how to create the library so that the methods calls refer to these c functions. Here are the main things:
    1. Add "#include <jni.h>" at the beginning. Note: JNI stands for Java Native Interface and  it is a programming framework that enables Java code running in a Java Virtual Machine (JVM) to call, and to be called by, native applications (programs specific to a hardware and operating system platform) and libraries written in other languages such as C, C++ and assembly
    2. The name of the function needs to be declared as "Java"+{package name}+{class name}+{method name}, using "_" between words. For instance, for our example, it would be "Java_com_myname_mypackage_MyActivity_MyMethod"
    3. As arguments, at the very least (even if the C function does not need any), we will need to write "JNIEnv *env, jobject thisObj".
    4. The rest of the arguments need to be the usual ones that the C function would have. Nevertheless, as java types and C types are not 100% compatible, look at the links below to see what to do in each case. On my example, this is straightforward (see below). Notice that the arguments use java types, not C types. Same thing for returned results...
  8. Add the Android.mk which basically tells the ndk-build (see next) what to build and how to name it. Basically:
    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE    := MyLib
    LOCAL_SRC_FILES := whatever.c

    include $(BUILD_SHARED_LIBRARY)
    The two bold lines are the ones changing most of the time. Notice that LOCAL_MODULE is the final name of the library, although the real file name is slightly different (libMyLib.so).
  9. To then compile and create the library, in Windows Explorer go to the project folder, SHIFT+right click --> Open command window here. Then type {NDK path}+ndk-build. If you had previous builds that you want to clean up, use "ndk-build clean". Note: this may be doable within Eclipse (allowing you to clean up compile errors...), but have not experimented yet with it.
  10. Now you should be able to just run the java project (launch your app).
  11. Watch out when you launch your app, as some times the previous launch may be still active (go to the task in your phone and kill it...) or may have the old library in there and don't notice that it is missing (in that case, uninstall the app). I know... this shouldn't be the case, but something like that just happened to me...
Here is my code for the adder case:
AdderJNI.java
 package com.example.jniadder;  
 import android.os.Bundle;  
 import android.app.Activity;  
 import android.view.Menu;  
 import android.view.View;  
 import android.widget.Button;  
 import android.widget.EditText;  
 import android.widget.TextView;  
 public class AdderJNI extends Activity {  
      private EditText mnumberA, mnumberB;  
      private TextView mResult;  
      private Button madd;  
      @Override  
      protected void onCreate(Bundle savedInstanceState) {  
           super.onCreate(savedInstanceState);  
           setContentView(R.layout.activity_adder_jni);  
           mResult=(TextView)findViewById(R.id.adderResult);  
           mnumberA=(EditText)this.findViewById(R.id.editNumberA);  
           mnumberB=(EditText)this.findViewById(R.id.editNumberB);  
           madd=(Button)this.findViewById(R.id.button1);  
           mResult.setText( stringFromJNI() );  
           madd.setOnClickListener(new View.OnClickListener() {  
                @Override  
                public void onClick(View v) {  
                     // TODO Auto-generated method stub  
                     //setView R.layout.adderResult="3";  
                     int A=Integer.parseInt(mnumberA.getText().toString());  
                     int B=Integer.parseInt(mnumberB.getText().toString());  
                     //mResult.setText(Integer.toString(A+B));                    // Just using Java  
                     mResult.setText(Integer.toString(addnumbers(A,B)));      // Using native  
                }  
           });  
      }  
      @Override  
      public boolean onCreateOptionsMenu(Menu menu) {  
           // Inflate the menu; this adds items to the action bar if it is present.  
           getMenuInflater().inflate(R.menu.adder_jni, menu);  
           return true;  
      }  
      private native int addnumbers(int A,int B);  
      public native String stringFromJNI();  
      static {  
     System.loadLibrary("JNIadder2");  
   }  
 }  

JNIlib.c
 #include <jni.h>  
 #include <string.h>  
 jstring  
 Java_com_example_jniadder_AdderJNI_stringFromJNI( JNIEnv* env,  
                                                             jobject thisObj )  
 {  
   return (*env)->NewStringUTF(env, "Your Result");  
 }  
 jint  
 Java_com_example_jniadder_AdderJNI_addnumbers(JNIEnv *env, jobject thisObj, jint n1, jint n2)  
 {  
   // jint is mapped to int  
   return (n1+n2);  
 }  

And a screenshot of eclipse with the Android.mk:

References:
These can be a bit confusing as some times they don't explain what is what, they refer to java native development, not necessarily related to Android NDK (slight change of notation) or they are a bit obsolete (refer to previous versions):
  1. Look  in the NDK folder for a "docs" folder, and open the file OVERVIEW.html. Nice explanation...
  2. There is a nice video tutorial here.  
  3. Coderwall 
  4. Java Native Interface (very detailed)
  5. Another tutorial, lots of details, but a bit less than the previous one.
PS.: Please, click here to see an index of other posts on Android.

No comments:

Post a Comment