Chapter 6
In This Chapter
Connecting C code to Java code
Creating an Android app with native code
Sometimes, you have to get your hands dirty. You have to pop the hood and figure out why smoke comes out of your car. You have to bake a cake for that special friend who’s allergic to the ingredients in store-bought cakes. Or, in order to build the perfect mobile app, you must bypass Android’s comfortable Java coating and dig deep to find your true inner geek. You must create part of an app in the primitive, nuts-and-bolts, down-and-dirty language called C.
Book II, Chapter 2 explains how Java puts a virtual machine between your processor’s hardware and a running application. Java programs don’t turn directly into sets of instructions that your processor can then run. Instead, your processor runs another set of instructions; namely, a virtual machine. (Your Android device runs the Android Runtime; your laptop computer runs the Java Virtual Machine.) The virtual machine interprets a Java program’s instructions and carries out these instructions on your processor’s behalf.
Anyway, this added layer of software (between the Java instructions and your processor’s circuits) has both benefits and drawbacks. Isolation from the hardware enhances portability and security. But the added software layer might slow down a program’s execution. The layer also prevents Java from micromanaging the processor.
Imagine solving your allergic friend’s problem by ordering a cake directly from a local bakery. “My friend has a wheat allergy. Can you make the cake without using any wheat products?” At a classy establishment, the baker knows how to avoid wheat gluten. But your friend is also allergic to carrageenan with polysorbate 80, an emulsifier found in many commercial food products. “Please don’t use any carrageenan polysorbate 80,” you say. And the person behind the bakery counter says, “We’ll try our best. We can’t check for every chemical in every ingredient that we use.”
So you leave the bakery without completing the order. For the sake of your friend’s health, you need complete control over the baking process. Delegating some of the work to a baker in the back room (or to a virtual machine executing instructions on behalf of your device’s processor) just isn’t good enough.
Another potent reason for using non-Java code is to avoid rewriting code that you already have — code written in another programming language. Imagine having a thousand-line C program that reliably computes a decent daily investment strategy. (No, I don’t have such a program, in case you’re wondering.) The program does lots of fancy calculations, but the program has no user-friendly interface. The code has no windows, no buttons, and nothing nice for the user to click. You want to package this program inside an Android application. The app presents choices to the user, computes today’s investment strategy with its complicated formulas, and then displays details of the strategy (in a friendly, colorful way) on the user’s screen.
You can try rewriting the C program in Java. But translating between two closely related languages (such as C and Java) is a virtual rat’s nest. The translation is often messy and unreliable. A better plan is to write the user interface as an Android Java app and let your Java app defer to your existing C program only for the intricate investing strategy calculations. All you need is a way to exchange information between a Java program and a C program.
The creators of Android realized that developers would want to use non-Java code. So Android has a framework that mediates between Java and other languages. As you might have guessed from this section’s title, that framework is NDK — Android’s Native Development Kit. With Android’s NDK, you can write code that executes directly on a mobile device’s processor without relying on a virtual machine to carry out the instructions.
The NDK doesn’t come as part of Android’s standard Software Development Kit — its SDK. In fact, you don’t get the NDK by checking for available packages in the Android SDK Manager. The NDK is a separate archive file, available for download at http://developer.android.com
. (If I’m lucky, the particular URL will still be http://developer.android.com/tools/sdk/ndk
when you read this chapter.) On Windows, the NDK download is usually an .exe
file. For Mac or Linux, the NDK download is usually a .bin
file. The downloaded file is a self-extracting archive file. When you execute the file, your development computer extracts all the files stored in the archive.
To be more precise, the computer creates a new subdirectory of the directory containing the downloaded file. If you execute android-ndk-r10d.exe
while it’s in your xyz
directory, you get a new xyz/android-ndk-r10d
directory. So your first step is to move the downloaded file to a place where you want your new android-ndk-
whatever
directory to live. It hardly matters where you move the file, as long as you remember where you move it.
The way you expand the NDK download’s contents depends on your development computer’s operating system.
android-ndk-
whatever
.exe
file’s icon.
Click the Terminal button at the bottom of Android Studio’s main window.
A Terminal window opens in the lower portion of Android Studio’s main window.
In the Terminal window, use the Change Directory command (cd
) to navigate to the directory containing the downloaded NDK archive.
For example, if you moved the downloaded file to the /Users/JaneQReader
directory, type
cd /Users/JaneQReader
ls ndk*
android-ndk-r10c-darwin-x86_64.bin
, or something like that.)android-ndk-r10c-darwin-x86_64.bin
, type the following two commands:
chmod a+x android-ndk-r10c-darwin-x86_64.bin
./android-ndk-r10c-darwin-x86_64.bin
When you follow these steps, your computer creates an android-ndk-
something-or-other
directory and puts a few files and subfolders into the directory. By “a few files and folders,” I mean approximately 50,000 files in more than 4,000 folders. The extracted files consume more than three gigabytes of space. (The expanding takes about fifteen minutes on one of my favorite computers.)
Make note of the folder containing your extracted NDK materials. In later sections, I refer to this folder as your NDK_HOME
directory.
An NDK-enabled application is almost exactly like an ordinary Android application, except it’s different! (That’s a joke, by the way.) To create a simple NDK-enabled application, do the following:
Use Android Studio to create a new project.
As you create the new project, you can fill in the dialog boxes' fields any way you like, but if you want to follow along with the steps in my example, name the application My NDK App
with the default MainActivity
. For the package name, select com.allmycode.myndkapp
. For the Project Location, use a subdirectory named MyNDKApp
.
Books I and II have all the information you’d need to create an “ordinary” simple Android application.
With the Designer tool, add a Plain Text item (also known as an EditText) to activity_main.xml
(the main layout).
The palette has two similarly named items — the Plain TextView in the Widgets category, and the Plain Text in the Text Fields category. The one you want here is the Plain Text item in the Text Fields category. (This information is true as of mid-2015. If your version of Android Studio is different, look for any EditText
item.)
For details about adding a Plain Text view (and for some details about the next several steps) see Book II, Chapter 1, and Book III, Chapter 2.
activity_main.xml
(the main layout).
Assign a name to the button’s click event listener.
If you’re following along at home, I give the button’s onClick
property the name onButtonClick
. (I use the Properties view to set this property.)
Add event listener code to your app’s activity.
I don’t know about your activity, but my activity contains the following event listener code:
public void onButtonClick(View view) {
Editable name = ((EditText)
findViewById(R.id.editText)).getText();
Toast.makeText(getApplication(),
getString() + name, Toast.LENGTH_LONG).show();
}
Most of this event listener code is fairly harmless. But please remember that the names in the code must correspond to names in your project. For example, the method name (in my example, onButtonClick
) must be the same as the name you assigned as the button’s onClick
listener in Step 4. Also, the ID editText
must be same as the ID of the view that you created in Step 2.
The only unusual thing about the event listener code is the call to a method named getString
. You don’t declare getString
the way you declare most Java methods.
To your activity class (named MainActivity
in Step 1) add the following code:
public native String getString();
static {
System.loadLibrary("my-jni-app");
}
With or without Android, the Java technology suite comes with JNI — the Java Native Interface. The purpose of JNI is to help Java programs communicate with code written in other programming languages. In this step’s code, JNI tells your program to expect the getString
method’s body to be written in a language other than Java.
The first line
public native String getString();
tells Java to look for the body of getString
somewhere else (outside the Java class in which the line appears). The rest of the code
static {
System.loadLibrary("my-jni-app");
}
tells your program to look for method bodies in a place called my-jni-app
. And whaddaya know? This section’s example includes some C-language code in a file named my-jni-app.c
.
Listing 6-1 pulls together all the code in your MainActivity.java
file.
In Android Studio’s main menu, choose File ⇒ New ⇒ Folder ⇒ JNI Folder.
Android displays a Customize the Activity dialog box.
In the Customize the Activity dialog box, click Finish.
As a result, the Project tool window’s tree has a new branch labeled c
. (See Figure 6-1.) The folder’s real name is app/src/main/jni
, but the label in the Project tool window is c
.
In the Project tool window, right-click the new c
branch and then choose New ⇒ File. (Mac users Ctrl-click instead of right-click.)
Android Studio opens its New File dialog box.
In the Enter a New File Name field, type Android.mk and then click OK.
A .mk
file is like a C-language make
file, except it’s shorter. For more information, see the “Android.mk files” sidebar.
The first time you create a .mk
file, Android Studio may display a Register New File Type Association dialog box. If so, select the file type Text. (See Figure 6-2.)
c
branch.
In the Enter a New File Name field, type my-jni-app.c and then click OK.
This new file is destined to contain code written in C. You can give the file any name you want (but if you don’t use the name my-jni-app
, change my-jni-app
to your alternate name everywhere else in these instructions). Of course, in this example, you’re creating C code, so the filename should end with the .c
extension.
Using Android Studio’s editor, type the following code into the Android.mk
file:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := my-jni-app
LOCAL_SRC_FILES := my-jni-app.c
include $(BUILD_SHARED_LIBRARY)
This code tells the computer how to put the parts of your project together into an Android application. For more details, see the “Android.mk files” sidebar.
Using Android Studio’s editor, type the code from Listing 6-2 into the my-jni-app.c
file.
In Listing 6-2, the name Java_com_allmycode_myndkapp_MainActivity_getString
is not arbitrary. You must use your app’s package name (in this example, com.allmycode.myndkapp
) in your C code.
If you’re not a seasoned C programmer, you may be wondering what the code in my-jni-app.c
means. Well, you’re in luck. There’s a sidebar for that! (The sidebar’s name is “C programming in 600 words or less.”)
Click the Terminal button at the bottom of Android Studio’s main window.
A Terminal window opens in the lower portion of Android Studio’s main window. (On Windows computers, this Terminal window is actually an MS-DOS command window.)
In the Terminal window, type the command
cd app/src/main
and then press Enter.
In this step, I assume that you’ve just opened the Terminal window, and that you haven’t done any directory-changing in that window. If that’s not the case, type cd
PROJECT_HOME
/app/src/main
. In place of PROJECT_HOME
, type the full pathname of the directory where your project is housed. (Refer to this chapter’s “Directory names” sidebar.)
When you issue this command, you navigate to the directory containing your project’s AndroidManifest.xml
file.
Don’t navigate away from your Android project’s app/src/main
directory. Or if you do navigate away, navigate back to the app/src/main
directory before proceeding to the next step.
In the Terminal window, type the following command and then press Enter:
ANDROID_HOME/tools/android update project
--path . --subprojects --target android-21
In the command, where I have ANDROID_HOME
, type the location of the Android SDK on your development computer. In place of 21
, type the targetSdkVersion
number for this Android project (from the project’s build.gradle
file).
So for example, on my PC, I type the following:
C:UsersBarryAppDataLocalAndroidsdk/tools/android
update project --path . --subprojects
--target android-21
If a pathname contains blank spaces (on a Mac or a PC), I enclose the entire pathname in quotation marks. For example, if my ANDROID_HOME
is c:Program FilesAndroidsdk
, I type
"C:Program FilesAndroidsdk/tools/android"
update project --path . --subprojects
--target android-21
In spite of the way things look on this printed page, I type the entire command on a single line (with a blank space before update
and a blank space before --target
).
I begged the people at John Wiley & Sons, Inc., to publish a book whose pages are two feet wide, but they didn’t do it! When you type this step’s command, you don’t intentionally start a new line anywhere in the middle of the command. In this step, the command that I type looks like it’s two or three lines long, but that’s only because this book’s page is too narrow. Normally you just keep typing along one line. (And if the Terminal window takes its own initiative to wrap your typing to a new line, you’re okay.)
This step’s android update project
command creates a build.xml
file. This build.xml
file contains a set of instructions telling Java how to bundle your application. This project needs a build.xml
file because of the special NDK stuff in the project.
If all goes well, the Terminal window responds to your command with text like
Updated local.properties
No project name specified, using Activity name
'MainActivity'.
If you wish to change it, edit the first line of
build.xml.
Added file ./build.xml
Added file ./proguard-project.txt
Don’t navigate away from your Android project’s app/src/main
directory. Or if you do navigate away, navigate back to the app/src/main
directory before proceeding to the next step.
Type the following command and then press Enter:
NDK_HOME/ndk-build
Where I have NDK_HOME
, type the location of your Android NDK installation. (See this chapter’s “Getting the NDK” section.)
After issuing the ndk-build
command, your computer responds with a message like this:
Compile thumb : my-jni-app <= my-jni-app.c
SharedLibrary : libmy-jni-app.so
Install: libmy-jni-app.so =>
libs/armeabi/libmy-jni-app.so
Congratulations! The message indicates that your C code has been translated into a usable library for ARM processors.
Add the following code to your project’s build.gradle
file.
ndk {
moduleName "my-jni-app"
}
Put this code inside the existing defaultConfig
block:
defaultConfig {
applicationId "com.allmycode.myndkapp"
minSdkVersion 21
targetSdkVersion 21
versionCode 1
versionName "1.0"
ndk {
moduleName "my-jni-app"
}
}
Add your NDK_HOME
directory name to the end of one of your project’s local.properties
files.
On my Mac, the project’s local.properties
file ends with these two lines:
sdk.dir=/Users/Barry/Development/sdk
ndk.dir=/Users/Barry/android-ndk-r10d
On a PC, the use of slashes is a bit strange, so the project’s local.properties
file ends with these two lines:
sdk.dir=C:\Users\Barry\AppData\Local\Android\Sdk
ndk.dir=C:\android-ndk-r10d
One way or another, Android Studio knows where the Android NDK lives.
You can find two files named local.properties
in your project. One such file lives inside your project’s app/src/main
directory. Don’t add the ndk.dir
line to the end of that local.properties
file. Add the ndk.dir
line to the end of the local.properties
file that’s in your project’s root directory.
Run your Android project.
After some delay (and much anticipation), you see a screen like the one in Figure 6-3.
Type your name in the EditText view and then click the button.
A toast notification appears on your emulator’s screen. In Figure 6-4, the name Barry comes from the activity’s EditText view. (It’s no big deal.) But the word Hello in the notification comes from a C-language program — namely, the program in Listing 6-2.
Sure! Fetching a "Hello"
string from a C program isn’t the most useful app you’ve ever seen. But the ability to call C code to help with an Android app’s work has lots of potential.
Listing 6-1: Your Project’s Main Activity
package com.allmycode.myndkapp;
import android.app.Activity;
import android.os.Bundle;
import android.text.Editable;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public native String getString();
static {
System.loadLibrary("my-jni-app");
}
public void onButtonClick(View view) {
Editable name = ((EditText)
findViewById(R.id.editText)).getText();
Toast.makeText(getApplication(),
getString() + name, Toast.LENGTH_LONG).show();
}
}
Listing 6-2: Your C Program
#include <string.h>
#include <jni.h>
jstring
Java_com_allmycode_myndkapp_MainActivity_getString
(JNIEnv* env, jobject obj)
{
return (*env)->NewStringUTF(env, "Hello, ");
}
The most common hurdle for new NDK programmers involves correctly connecting a method call with its method. In Listing 6-1, for example, your Java program calls getString()
, but with all the naming conventions and linking tricks, the system may not see the connection to the C-language method Java_com_allmycode_examples_ndk_MyActivity_getString
in Listing 6-2. Your application crashes, and Android Studio’s Logcat view displays an UnsatisfiedLinkError
.
If this happens to you, retrace your steps. The connection between a Java method and its corresponding C/C++ method can be very brittle. Check the spelling of names, check the messages you receive when you invoke ndk-build
, and check your folder structure. If you’re patient and persistent, you can get the stars and planets to align beautifully.
3.238.82.77