Compile and deploy official sample applications from the Android NDK with Ant build tool and native code compiler ndk-build
Posted by filter-coffee on Feb 21st, 2012
In this article by Sylvain Ratabouil, author of Android NDK Beginner’s Guide we are going to do the following:
(Discover the native side of Android and inject the power of C/C++ in your applications with this book and ebook)
By the end of this article, you should know how to start up a new Android native project on your own.
A man with the most powerful tools in hand is unarmed without the knowledge of their usage. Eclipse, GCC, Ant, Bash, Shell, Linux—any new Android programmer needs to deal with this technologic ecosystem. Depending on your background, some of these names may sound familiar to your ears. Indeed, that is a real strength; Android is based on open source bricks which have matured for years. Theses bricks are cemented by the Android Development Kits (SDK and NDK) and their set of new tools: Android Debug Bridge (ADB), Android Asset Packaging Tool (AAPT), Activity Manager (AM), ndk-build, and so on. So, since our development environment is set up, we can now get our hands dirty and start manipulating all these utilities to create, compile, and deploy projects which include native code.
Compiling and deploying NDK sample applications I guess you cannot wait anymore to test your new development environment. So why not compile and deploy elementary samples provided by the Android NDK first to see it in action? To get started, I propose to run HelloJni, a sample application which retrieves a character string defined inside a native C library into a Java activity (an activity in Android being more or less equivalent to an application screen).
Let's compile and deploy HelloJni project from command line using Ant:
(Move the mouse over the image to enlarge.)
The result should look like the following extract:
We have compiled, packaged, and deployed an official NDK sample application with Ant and SDK command-line tools. We will explore them more in later part. We have also compiled our first native C library (also called module) using the ndk-build command. This library simply returns a character string to the Java part of the application on request. Both sides of the application, the native and the Java one, communicate through Java Native Interface. JNI is a standard framework that allows Java code to explicitly call native C/C++ code with a dedicated API.
Finally, we have launched HelloJni on our device from an Android shell (adb shell) with the am Activity Manager command. Command parameters passed in step 8 come from the Android manifest: com.example.hellojni is the package name and com.example.hellojni. HelloJni is the main Activity class name concatenated to the main package.
<!--?xml version="1.0" encoding="utf-8"?-->
Because Android SDK, NDK, and their open source bricks are not bound to Eclipse or any specific IDE, creating an automated build chain or setting up a continuous integration server becomes possible. A simple bash script with Ant is enough to make it work!
HelloJni sample is a little bit... let's say rustic! So what about trying something fancier? Android NDK provides a sample named San Angeles. San Angeles is a coding demo created in 2004 for the Assembly 2004 competition. It has been later ported to OpenGL ES and reused as a sample demonstration in several languages and systems, including Android. You can find more information by visiting one of the author's page: Jet.ro.
To test this demo, you need to follow the same steps:
As this application uses OpenGL ES 1, AVD emulation will work, but may be somewhat slow!
You may encounter some errors while compiling the application with Ant:
The reason is simple: in res/layout/ directory, main.xml file is defined. This file usually defines the main screen layout in Java application—displayed components and how they are organized. However, when Android 2.2 (API Level 8) was released, the layout_width and layout_height enumerations, which describe the way UI components should be sized, were modified: FILL_PARENT became MATCH_PARENT. But San Angeles uses API Level 4.
There are basically two ways to overcome this problem. The first one is selecting the right Android version as the target. To do so, specify the target when creating Ant project files:
$ android update project –p . -–target android-8
This way, build target is set to API Level 8 and MATCH_PARENT is recognized. You can also change the build target manually by editing default.properties at the project root and replacing:
with the following line:
The second way is more straightforward: erase the main.xml file! Indeed, this file is in fact not used by San Angeles demo, as only an OpenGL screen created programmatically is displayed, without any UI components.
When compiling an Android application, always check carefully if you are using the right target platform, as some features are added or updated between Android versions. A target can also dramatically change your audience wideness because of the multiple versions of Android in the wild... Indeed, targets are moving a lot and fast on Android!.
All these efforts are not in vain: it is just a pleasure to see this old-school 3D environment full of flat-shaded polygons running for the first time. So just stop reading and run it!
Exploring android SDK tools Android SDK includes tools which are quite useful for developers and integrators. We have already overlooked some of them including the Android Debug Bridge and android command. Let's explore them deeper.
You may have not noticed it specifically since the beginning but it has always been there, over your shoulder. The Android Debug Bridge is a multifaceted tool used as an intermediary between development environment and emulators/devices. More specifically, ADB is:
ADB shell is a real Linux shell embedded in ADB client. Although not all standard commands are available, classical commands, such as ls, cd, pwd, cat, chmod, ps, and so on are executable. A few specific commands are also provided such as:
To display device log messages
To dump system state
To dump kernel messages
ADB shell is a real Swiss Army knife. It also allows manipulating your device in a flexible way, especially with root access. For example, it becomes possible to observe applications deployed in their "sandbox" (see directory /data/data) or to a list and kill currently running processes.
ADB also offers other interesting options; some of them are as follows:
To transfer a file to your computer
To transfer a file to your device or emulator
To install an application package
To reinstall an application, if already deployed
To list all Android devices currently connected, including emulators
To restart an Android device programmatically
To sleep, until a device or emulator is connected to your computer (for example,. in a script)
To launch the ADB server communicating with devices and emulators
To terminate the ADB server
To print the whole device state (like dumpsys)
To get an exhaustive help with all options and flags available
To ease the writing of issued command, ADB provides facultative flags to specify before options:
-s To target a specific device
-d To target current physical device, if only one is connected (or an error message is raised)
-e To target currently running emulator, if only one is connected (or an error message is raised)
ADB client and its shell can be used for advanced manipulation on the system, but most of the time, it will not be necessary. ADB itself is generally used transparently. In addition, without root access to your phone, possible actions are limited. For more information, see Developer.android.com.
Root or not root.
If you know the Android ecosystem a bit, you may have heard about rooted phones and non-rooted phones. Rooting a phone means getting root access to it, either "officially" while using development phones or using hacks with an end user phone. The main interest is to upgrade your system before the manufacturer provides updates (if any!) or to use a custom version (optimized or modified, for example, CyanogenMod). You can also do any possible (especially dangerous) manipulations that an Administrator can do (for example, deploying a custom kernel). Rooting is not an illegal operation, as you are modifying YOUR device. But not all manufacturers appreciate this practice and usually void the warranty.
Using the information provided, you should be able to connect to your phone like in the good old days of computers (I mean a few years ago!) and execute some basic manipulation using a shell prompt. I propose you to transfer a resource file by hand, like a music clip or a resource that you will be reading from a future program of yours.
To do so, you need to open a command-line prompt and perform the following steps:
The command named android is the main entry point when manipulating not only projects but also AVDs and SDK updates. There are few options available, which are as follows:
-p The project path
-n The project name
-t The Android API target
-k The Java package, which contains application's main class
-a The application's main class name (Activity in Android terms)
$ android create project –p ./MyProjectDir –n MyProject –t
android-8 –k com.mypackage –a MyActivity
-p The project path
-n To change the project name
-l To include an Android library project (that is, reusable code). The path must be relative to the project directory).
-t To change the Android API target
There are also options to create library projects (create lib-project, update lib- project) and test projects (create test-project, update test-project). I will not go into details here as this is more related to the Java world.
As for ADB, android command is your friend and can give you some help:
$ android create project –help
Command android is a crucial tool to implement a continuous integration toolchain in order to compile, package, deploy, and test a project automatically entirely from command line.
With adb, android, and ant commands, you have enough knowledge to build a minimal automatic compilation and deployment script to perform some continuous integration. I assume here that you have a versioning software available and you know how to use it. Subversion (also known as SVN) is a good candidate and can work in local (without a server).
Perform the following operations:
Do not forget to check command results using $?. If the returned value is different from 0, it means an error occurred. Additionally, you can use grep or some custom tools to check potential error messages.
A free monkey to test your App!
In order to automate UI testing on an Android application, an interesting utility that is provided with the Android SDK is MonkeyRunner, which can simulate user actions on a device to perform some automated UI testing. Have a look at Developer.android.com .
To favor automation, a single Android shell statement can be executed from command-line as follows:
adb shell ls /sdcard/
To execute a command on an Android device and retrieve its result back on your host shell, execute the following command: adb shell "ls / notexistingdir/ 1> /dev/null 2> &1; echo \$?" Redirection is necessary to avoid polluting the standard output. The escape character before $? is required to avoid early interpretation by the host shell.
Now you are fully prepared to automate your own build toolchain!
Creating your first android project using eclipse In the first part of the article, we have seen how to use Android command-line tools. But developing with Notepad or VI is not really attractive. Coding should be fun! And to make it so, we need our preferred IDE to perform boring or unpractical tasks. So let's see now how to create an Android project using Eclipse.
Eclipse views and perspectives
Several times in this book, I have asked you to look at an Eclipse View like the Package Explorer View, the Debug View, and so on. Usually, most of them are already visible, but sometimes they are not. In that case, open them through main menu: Window | Show View | Other…. Views in Eclipse are grouped in perspectives, which basically store your workspace layout. They can be opened through main menu: Window | Open Perspective | Other…. Note that some contextual menus are available only in some perspectives.
We have created our first Android project using Eclipse. In a few screens and clicks, we have been able to launch the application instead of writing long and verbose commands. Working with an IDE like Eclipse really gives a huge productivity boost and makes programming much more comfortable!
ADT plugin has an annoying bug that you may have already encountered: Eclipse complains that your Android project is missing the required source folder gen whereas this folder is clearly present. Most of the time, just recompiling the project makes this error disappear. But sometimes, Eclipse is recalcitrant and refuses to recompile projects. In that case, a little-known trick, which can be applied in many other cases, is to simply open the Problems view, select these irritating messages, delete them without mercy (Delete key or right-click and Delete) and finally recompile the incriminated project.
Android projects created with ADT are always Java projects. But thanks to Eclipse flexibility, we can turn them into C/C++ projects too; we are going to see this at the end of this article.
Avoiding space in file paths
When creating a new project, avoid leaving a space in the path where your project is located. Although Android SDK can handle that without any problem, Android NDK and more specifically GNU Make may not really like it.
It is not possible to talk about Android without touching a word about Dalvik. Dalvik, which is also the name of an Icelandic village, is a Virtual Machine on which Android bytecode is interpreted (not native code!). It is at the core of any applications running on Android. Dalvik is conceived to fit the constrained requirements of mobile devices. It is specifically optimized to use less memory and CPU. It sits on top of the Android kernel which provides the first layer of abstraction over hardware (process management, memory management, and so on).
Android has been designed with speed in mind. Because most users do not want to wait for their application to be loaded while others are still running, the system is able to instantiate multple Dalvik VMs quickly, thanks to the Zygote process. Zygote, whose name comes from the very first biologic cell of an organism from which daughter cells are reproduced, starts when the system boots up. It preloads (or "warms up") all core libraries shared among applications as well as a Dalvik instance. To launch a new application, Zygote is simply forked and the initial Dalvik instance is copied. Memory consumption is lowered by sharing as many libraries as possible between processes.
Dalvik operates on Android bytecode, which is different from Java bytecode. Bytecode is stored in an optimized format called Dex generated by an Android SDK tool named dx. Dex files are archived in the final APK with the application manifest and any native libraries or additional resources needed. Note that applications can get further optimized during installation on end user's device.
Interfacing Java with C/C++ Keep your Eclipse IDE opened as we are not done with it yet. We have a working project indeed. But wait, that is just a Java project, whereas we want to unleash the power of Android with native code! In this part, we are going to create C/C++ source files, compile them into a native library named mylib and let Java run this code.
The native library mylib that we are going to create will contain one simple native method getMyData() that returns a basic character string. First, let's write the Java code to declare and run this method.
Now, let's prepare the project files required to build the native code.
As project files for native compilation are ready, we can write the expected native source code. Although the C implementation file must be written by hand, the corresponding header file can be generated with a helper tool provided by the JDK: javah.
In Mac OS X, Linux, and Cygwin, you can easily find the location of an executable available in $PATH, by using the which command.
$ which javah
Eclipse is not yet configured to compile native code, only Java code. Until we do that in the last part of this article, we can try to build native code by hand.
The native library is compiled in the libs/armeabi directory and is named libmylib.so. Temporary files generated during compilation are located in the obj/local directory.
In the previous part, we created an Android Java project. In this second part, we have interfaced Java code to a native library compiled with the Android NDK from a C file. This binding from Java to C allows retrieving through Java Native Interfaces a simple Java string allocated in the native code. The example application shows how Java and C/C++ can cooperate together:
Native methods are declared on the Java side with the native keyword. These methods have no body (like an abstract method) as they are implemented on the native side. Only their prototype needs to be defined. Native methods can have parameters, a return value, any visibility (private, protected, package protected or public) and can be static, like classic Java methods. Of course, they require the native library with method implementations to be loaded before they are called. A way to do that is to invoke System.loadLibrary() in a static initialization block, which is initialized when the containing class is loaded. Failure to do so results in an exception of type java.lang.UnsatisfiedLinkError, which is raised when the native method is invoked for the first time.
Although it is not compulsory, javah tool provided by the JDK is extremely useful to generate native prototypes. Indeed, JNI convention is tedious and error-prone. With generated headers, you immediately know if a native method expected by the Java side is missing or has an incorrect signature. I encourage you to use javah systematically in your projects, more specifically, each time native method's signature is changed. JNI code is generated from .class files, which means that your Java code must be first compiled before going through javah conversion. Implementation needs to be provided in a separate C/C++ source file.
Remember that a very specific naming convention, which is summarized by the following pattern, must be followed by native side methods:
Java___ (JNIEnv* pEnv,
Native method name is prefixed with Java_ and the packages/class name (separated by _) containing it separated. First argument is always of type JNIEnv and the preceding arguments are the actual parameters given to the Java method.
Native library building process is orchestrated by a Makefile named Android.mk. By convention, Android.mk is in folder jni, which is located inside the project's root. That way, ndk-build command can find this file automatically when the command is invoked. Therefore, C/C++ code is by convention also located in jni directory (but this can be changed by configuration).
Android Makefiles are an essential piece of the NDK building process. Thus, it is important to understand the way they work to manage a project properly. An Android.mk file is basically a "baking" file, which defines what to compile and how to compile. Configuration is performed using predefined variables, among which are: LOCAL_PATH, LOCAL_MODULE and LOCAL_SRC_FILES.
The Android.mk file presented in MyProject is a very simple Makefile example. Each instruction serves a specific purpose:
LOCAL_PATH := $(call my-dir)
The preceding code indicates native source files location. Instruction $(call < function> ) allows evaluating a function and function my-dir returns the directory path of the last executed Makefile. Thus, as Makefiles usually share their directory with source files, this line is systematically written at the beginning of each Android.mk file to find their location.
Makes sure no "parasite" configuration disrupts compilation. When compiling an application, a few LOCAL_XXX variables need to be defined. The problem is that one module may define additional configuration settings (like a compilation MACRO or a flag) through these variables, which may not be needed by another module.
Keep your modules clean
To avoid any disruption, all necessary LOCAL_XXX variables should be cleared before any module is configured and compiled. Note that LOCAL_PATH is an exception to that rule and is never cleared out.
LOCAL_MODULE := mylib
The preceding line of code defines your module name. After compilation, the output library is named according to the LOCAL_MODULE variable flanked by a lib prefix and a .so suffix. This LOCAL_MODULE name is also used when a module depends on another module.
LOCAL_SRC_FILES := com_myproject_MyActivity.c
The preceding line of code indicates which source files to compile. File path is expressed relative to the LOCAL_PATH directory.
This last instruction finally launches the compilation process and indicates which type of library to generate.
With Android NDK, it is possible to produce shared libraries (also called dynamic libraries, like DLL on Windows) as well as static libraries:
In contrast with shared libraries, static libraries can be stripped, which means that unnecessary symbols (like a function which is never called from the embedding library) are removed from the final binary. They make shared libraries bigger but "all-inclusive", without dependencies. This avoids the "DLL not found" syndrome well known on Window.
Shared vs. Static modules
Whether you should use a static or shared library depends on the context:
then consider turning it into a shared library because they avoid memory duplication (which is a very sensible issue on mobile devices). On the other hand:
Compiling native code from eclipse You probably agree with me, writing code in Eclipse but compiling it by hand is not very satisfying. Although the ADT plugin does not provide any C/C++ support, Eclipse does this through CDT. Let's use it to turn our Android project into a hybrid Java-C/C++ project.
To check whether Eclipse compilation works fine, let's introduce surreptitiously an error inside the com_myproject_MyActivity.c file. For example:
private static final String = "An error here!";
JNIEXPORT jstring Java_com_myproject_MyActivity_getMyData
Now, let's compile MyProject with Eclipse:
And... oops! An error got insidiously inside the code. An error? No we are not dreaming! Our Android project is compiling C/C++ code and parsing errors:
If warnings about the include file which the CDT Indexer could not find do not appear, go to C/C++ perspective, then right-click on the project name in the Project Explorer view and select Index/Search for Unresolved Includes item. The Search view appears with all unresolved inclusions.
We managed to integrate Eclipse CDT plugin with an Android project using CDT conversion wizard. In a few clicks, we have turned a Java project into a hybrid Java/C/C++ project! By tweaking CDT project properties, we managed to launch ndk-build command to produce the library mylib defined in Android.mk. After getting compiled, this native library is packaged automatically into the final Android application by ADT.
Running javah automatically while building
If you do not want to bother executing manually javah each time native methods changes, you can create an Eclipse builder:
JNI header files will now be generated automatically each a time project is compiled.
In step 8 and 9, we enabled Building on resource save option. This allows automatic compilation to occur without human intervention, for example, when a save operation is triggered. This feature is really nice but can sometimes cause a build cycle: Eclipse keeps compiling code so we moved CDT Builder just before Android Package Builder, in step 9, to avoid Android Pre Compiler and Java Builder to triggering CDT uselessly. But this is not always enough and you should be prepared to deactivate it temporarily or definitely as soon as you are fed up!>
Build command invocation is performed automatically when a file is saved. This is practical but can be resource and time consuming and can cause some build cycle. That is why it is sometimes appropriate to deactivate the Build automatically option from main menu through Project. A new button: appears in the toolbar to trigger a build manually. You can then re-enable automatic building.
Summary Although setting up, packaging, and deploying an application project are not the most exciting tasks, but they cannot be avoided. Mastering them will allow being productive and focused on the real objective: producing code.
In this artilce, we have seen how to use NDK command tools to compile and deploy Android projects manually. This experience will be useful to make use of continuous integration in your project. We have also seen how to make both Java and C/C++ talk together in a single application using JNI. Finally we have created a hybrid Java/C/C++ project using Eclipse to develop more efficiently.
With this first experiment in mind, you got a good overview of how the NDK works.