This is part eight in a series on learning ARM assembly on Android. This part covers calling Assembly code from Android applications.
Part 1: Motivation and device set up
Part 2: A walk-through of a simple ARM assembly program
Part 3: Registers, memory, and addressing modes
Part 4: Gnu tools for assembly; GCC and GDB
Part 5: Stack and Functions
Part 6: Arithmetic and Logical Expressions
Part 7: Conditional Execution
=> Part 8: Assembly in Android code
The articles follow in series, each article builds on the previous.
Native Development Kit
You've written some assembly code, made it run real fast, and now you want to include it in an Android application. What's the advantage of running little assembly programs that can't use the full feature set available to Android? You need the Android Native Development Kit.
Download the Native Development Kit from the Android development page. Follow the instructions on that page to install it. Installation is little more than unzipping the file in the correct directory.
Sample Application
My sample application has three parts. The first is the most familiar part: this is the assembly source. This is in an assembly source file called jni/multiple.s. This code computes 10y for a given value y.
The assembly code is called from a C stub. The C stub must have a very fixed name: Java_name_of_package_Class_function. This looks downright ugly but is required for Java to look up the correct function. I create a C stub to hold the strange name, and to accept the weird JNI arguments. You don't need to have a C stub, but it makes life easy.
The type jint is a java int that you can treat as a 32 bit int value. Other types are jboolean, jbyte, jchar, jshort, jlong, jfloat, jdouble, and jobject. Notice the signature of the JNI function: it accepts the environment, which is a JNIEnv pointer, and an arbitrary object. Finally, we have the input value, which is an integer. In its implementation, we call our ARM assembly function on the input. The return value is a jint, which indicates that we are returning an integer.
The stub and the assembly input are compiled by calling the following command from the root directory of your project. This is the directory that contains jni/, src/, res/, ..etc. I am assuming the ndk is installed in /usr/local/android-sdk-linux_x86/android-ndk-r6b.
Finally, there is a Java source code to create the Activity in Android. This code creates an Android application. It extends Activity, and overrides the onCreate method. In this, it creates a TextView, which is a label, and then sets the contents of the label to the return value of the function. It defines a function called factorialJNI which accepts an integer input and returns an integer. It is marked as native, indicating that its implementation is not in Java.
Finally, a static initialisation loads the jni library that was defined in the XML file.
You can download the entire ARM Android assembly example as an Eclipse project here.
Speed versus Complexity
Just because you are calling assembly code does not automatically make your program faster. The Dalvik virtual machine runs most code fairly fast, and the effort to develop assembly code is not worth the minor improvement in code execution speed. Here are some reasons why you might want to use native code.
A careful consideration of implementation speed and code complexity will lead you to the correct balance.
Part 1: Motivation and device set up
Part 2: A walk-through of a simple ARM assembly program
Part 3: Registers, memory, and addressing modes
Part 4: Gnu tools for assembly; GCC and GDB
Part 5: Stack and Functions
Part 6: Arithmetic and Logical Expressions
Part 7: Conditional Execution
=> Part 8: Assembly in Android code
The articles follow in series, each article builds on the previous.
Native Development Kit
You've written some assembly code, made it run real fast, and now you want to include it in an Android application. What's the advantage of running little assembly programs that can't use the full feature set available to Android? You need the Android Native Development Kit.
Download the Native Development Kit from the Android development page. Follow the instructions on that page to install it. Installation is little more than unzipping the file in the correct directory.
Sample Application
My sample application has three parts. The first is the most familiar part: this is the assembly source. This is in an assembly source file called jni/multiple.s. This code computes 10y for a given value y.
@ This file is jni/multiple.s .text .align 2 .global armFunction .type armFunction, %function armFunction: @ Multiply by 10. Input value and return value in r0 stmfd sp!, {fp,ip,lr} mov r3, r0, asl #3 add r0, r3, r0, asl #1 ldmfd sp!, {fp,ip,lr} bx lr .size armFunction, .-armFunction
The assembly code is called from a C stub. The C stub must have a very fixed name: Java_name_of_package_Class_function. This looks downright ugly but is required for Java to look up the correct function. I create a C stub to hold the strange name, and to accept the weird JNI arguments. You don't need to have a C stub, but it makes life easy.
The type jint is a java int that you can treat as a 32 bit int value. Other types are jboolean, jbyte, jchar, jshort, jlong, jfloat, jdouble, and jobject. Notice the signature of the JNI function: it accepts the environment, which is a JNIEnv pointer, and an arbitrary object. Finally, we have the input value, which is an integer. In its implementation, we call our ARM assembly function on the input. The return value is a jint, which indicates that we are returning an integer.
/* This file is jni/hello-jni.c */ #include <jni.h> /* This stub calls the function. It helps to have a stub like this to * save yourself the hassle of defining the function call in * Assembly. */ jint Java_com_eggwall_android_assembly_AssemblyActivity_factorialJNI( JNIEnv* env, jobject object, jint input) { /* Try calling some local code */ return armFunction(input); }Finally, there is a file that defines the sources in a Makefile. This is the jni/Android.mk file. It puts together the stub and the assembly code into a library called "hello-jni".
# This file is jni/Android.mk LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # I want ARM, not thumb. LOCAL_ARM_MODE := arm # Name of the local module LOCAL_MODULE := hello-jni # The files that make up the source code LOCAL_SRC_FILES := hello-jni.c multiple.s include $(BUILD_SHARED_LIBRARY)
The stub and the assembly input are compiled by calling the following command from the root directory of your project. This is the directory that contains jni/, src/, res/, ..etc. I am assuming the ndk is installed in /usr/local/android-sdk-linux_x86/android-ndk-r6b.
$ ls AndroidManifest.xml assets/ bin/ build.properties build.xml default.properties gen/ jni/ libs/ local.properties obj/ proguard.cfg res/ src/ $ /usr/local/android-sdk-linux_x86/android-ndk-r6b/ndk-build Compile arm : hello-jni <= multiple.s SharedLibrary : libhello-jni.so Install : libhello-jni.so => libs/armeabi/libhello-jni.so
Finally, there is a Java source code to create the Activity in Android. This code creates an Android application. It extends Activity, and overrides the onCreate method. In this, it creates a TextView, which is a label, and then sets the contents of the label to the return value of the function. It defines a function called factorialJNI which accepts an integer input and returns an integer. It is marked as native, indicating that its implementation is not in Java.
Finally, a static initialisation loads the jni library that was defined in the XML file.
package com.eggwall.android.assembly; import android.app.Activity; import android.widget.TextView; import android.os.Bundle; public class AssemblyActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Create a new Textview TextView tv = new TextView(this); // Print the multiple of 13 through assembly. tv.setText("The multiple was: " + factorialJNI(13)); setContentView(tv); } /** * Multiply the number by 10. * @param input, the number to be multiplied * @return the multiple of the number */ public native int factorialJNI(int input); /* This is used to load the 'hello-jni' library on application * startup. The library has already been unpacked into * /data/data/com.eggwall.android.AssemblyActivity/lib/libhello-jni.so at * installation time by the package manager. */ static { System.loadLibrary("hello-jni"); } }That is a lot of code to run a single assembly function! But now that you've seen the overall structure, you can begin modifying it to run your own assembly code. This is not a good way to experiment with assembly programming, though. Assembly programs can be hard to debug and it helps to have good tools during development. I would recommend developing using emacs, gcc, gdb, and other GNU tools, just as before. When the code is working correctly, hook it into Android Java source. The Android NDK has some useful debugging facilities, but I would consider them options of last resort.
You can download the entire ARM Android assembly example as an Eclipse project here.
Speed versus Complexity
Just because you are calling assembly code does not automatically make your program faster. The Dalvik virtual machine runs most code fairly fast, and the effort to develop assembly code is not worth the minor improvement in code execution speed. Here are some reasons why you might want to use native code.
- Legacy code. You have a lot of existing code that you want to plug into Android.
- Optimised code. You have CPU-intensive code that has been carefully optimised.
A careful consideration of implementation speed and code complexity will lead you to the correct balance.
- Create a new function called factorial(int a) and its corresponding stub. Call it from Java.
- Create a new source file called factorial.s, and put the function in there. Modify jni/Android.mk to run it correctly.
- Try adding an input area, where a number is entered. Pass this number to the assembly source code, and print out its factorial.