Chapter 6

Going Native

In This Chapter

arrow Connecting C code to Java code

arrow 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 Native Development Kit

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.

Getting the NDK

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.

warning The NDK files don’t like to be inside folders with blank spaces in their names. For example, in Windows, don’t move your android-ndk- whatever .exe file to the C:Program Files directory, or to any subdirectory of the C:Program Files directory.

The way you expand the NDK download’s contents depends on your development computer’s operating system.

  • On Windows, double-click the android-ndk- whatever .exe file’s icon.
  • On Linux or on a Mac, do the following:
    1. 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.

    2. 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

    3. Type the command

      ls ndk*

      and look for the name of the downloaded NDK archive. (It’s probably android-ndk-r10c-darwin-x86_64.bin, or something like that.)
    4. Assuming that the downloaded file’s name is 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.

Creating an Application

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:

  1. 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.

    crossreference Books I and II have all the information you’d need to create an “ordinary” simple Android application.

  2. With the Designer tool, add a Plain Text item (also known as an EditText) to activity_main.xml (the main layout).

    remember 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.)

    crossreference 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.

  3. Using the Designer tool, add a button to activity_main.xml (the main layout).
  4. 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.)

  5. 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();

    }

     

    remember 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.

  6. 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.

  7. In Android Studio’s main menu, choose File  ⇒  New  ⇒  Folder  ⇒  JNI Folder.

    Android displays a Customize the Activity dialog box.

  8. 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.

  9. 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.

  10. 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.

    tip 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.)

  11. Repeat Step 9 to create yet another file in the c branch.
  12. 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.

  13. 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.

  14. Using Android Studio’s editor, type the code from Listing 6-2 into the my-jni-app.c file.

    warning 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.”)

  15. 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.)

  16. In the Terminal window, type the command

    cd app/src/main

    and then press Enter.

    tip 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.

    warning 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.

  17. 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).

    warning 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

     

    remember 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.

  18. 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.

  19. 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"

        }

    }

  20. 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.

    warning 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.

  21. Run your Android project.

    After some delay (and much anticipation), you see a screen like the one in Figure 6-3.

  22. 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.

image

Figure 6-1: The Project tool window’s tree has a branch labeled c.

image

Figure 6-2: Registering a new file type association.

image

Figure 6-3: Your app starts running.

image

Figure 6-4: You’ve made toast!

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.

tip When you try to run this chapter’s app, you might see a frightening No rule to make target error message. If you do, try changing NDK_HOME to a path with fewer characters and with no blank spaces. Check the Android.mk file for any stray blank spaces. And finally, on Windows, add an additional whatever .c file to your project’s c branch. Then redo Steps 17 and 18. The new .c file doesn’t have to contain any text. It simply has to exist. No doubt about it … the need for a second .c file is a bug, not a feature!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.238.82.77