This is continued from Part 1.
In the first part, we setup the project structure, wrote the java wrapper class, generated the c header, wrote the c library and write the Android.mk file.
Building shared library
With traditional JNI (not targetting Android devices), you just compile the library with gcc using the -shared option. However, for Android devices, we use the ndk-build script.
To compile, we first switch to the project directory (the directory that contains the jni and src directories then run the ndk-build script provided in the Android NDK:
cd Android-JNI-Demo /path/to/ndk-build # for example: ~/build/android-ndk/android-ndk-r7c/ndk-build
If when trying to compile on linux you get the following error:
Invalid attribute name: package
You’ll want to check the line endings of your AndroidManifest.xml. I used the dos2unix command to correct them.
If the build script found your Android.mk and the library compiled without issue, you’ll see the following:
Compile thumb : squared <= squared.c SharedLibrary : libsquared.so Install : libsquared.so => libs/armeabi/libsquared.so
So we’re happy with the library compilation and now we’ll move on the developing a simple UI to test whether our functions perform as we expect.
Develop Simple UI
By default, when we created a new Android project in Eclipse an activity was generated with the following:
package org.edwards_research.demo.jni; import android.app.Activity; import android.os.Bundle; public class Android_JNI_DemoActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } }
If we didn’t care about looking pretty, we could change this to something like:
package org.edwards_research.demo.jni; import android.app.Activity; import android.os.Bundle; import android.util.Log; public class Android_JNI_DemoActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); int b = 3; int a = SquaredWrapper.to4(b); Log.i("JNIDemo", String.format("%d->%d", b,a)); } }
and run it, either in the emulator or on a device (with USB debugging enabled), we would see in LogCat an entry tagged with JNIDemo. In this case, we’d expect something like 3->81, since 3^4 = 81. But we’ll do a little bit more to see the performance of the library directly on the UI.
Instead of walking through the specific steps of creating the UI, I’ll simply post the pertinent files:
Android_JNI_DemoActivity.java
package org.edwards_research.demo.jni; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.EditText; import android.widget.TextView; public class Android_JNI_DemoActivity extends Activity { private EditText etInput; private TextView txtTo2; private TextView txtTo4; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Define Input EditText, TextViews etInput = (EditText) findViewById(R.id.etInput); txtTo2 = (TextView) findViewById(R.id.resTo2); txtTo4 = (TextView) findViewById(R.id.resTo4); } public void cbCalculate(View view) { int in = 0; try{ in = Integer.valueOf( etInput.getText().toString() ); } catch(NumberFormatException e) { return ; } txtTo2.setText(String.format("%d", SquaredWrapper.squared(in))); txtTo4.setText(String.format("%d", SquaredWrapper.to4(in))); } }
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <TableLayout android:id="@+id/tableLayout1" android:layout_width="match_parent" android:layout_height="wrap_content" > <TableRow android:id="@+id/tableRow1" android:layout_width="wrap_content" android:layout_height="wrap_content" > <EditText android:id="@+id/etInput" android:layout_width="150dp" android:layout_height="wrap_content" android:inputType="number" /> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Calculate" android:onClick="cbCalculate"/> </TableRow> <TableRow android:id="@+id/tableRow2" android:layout_width="wrap_content" android:layout_height="wrap_content" > <TextView android:id="@+id/lblTo2" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/squared" /> <TextView android:id="@+id/resTo2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="" /> </TableRow> <TableRow android:id="@+id/tableRow3" android:layout_width="wrap_content" android:layout_height="wrap_content" > <TextView android:id="@+id/lblTo4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/to4" /> <TextView android:id="@+id/resTo4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="" /> </TableRow> <TableRow android:id="@+id/tableRow4" android:layout_width="wrap_content" android:layout_height="wrap_content" > </TableRow> </TableLayout> </LinearLayout>
res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">Android-JNI-Demo</string> <string name="squared">Squared:</string> <string name="to4">To 4:</string> </resources>
When run in the emulator the app looks as follows:
Image may be NSFW.
Clik here to view.
You can enter a number in the text field and press the calculate button which will result in the squared and ^4 calculations:
Image may be NSFW.
Clik here to view.
Image may be NSFW.
Clik here to view.
Of course this was a pretty silly demo library because a squared function could have been trivially implemented in java without the need for c code, cross compiling or dealing at all with the Java Native Interface, however it still illustrated the steps necessary to compile a native library against the Android NDK and how to import and use it in an Android Project.