Android.NDK.Beginner.Guide

User Manual:

Open the PDF directly: View PDF PDF.
Page Count: 436 [warning: Documents this large are best viewed by clicking the View PDF Link!]

Android NDK
Beginner's Guide
Discover the nave side of Android and inject the power
of C/C++ in your applicaons
Sylvain Ratabouil
BIRMINGHAM - MUMBAI
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
Android NDK
Beginner's Guide
Copyright © 2012 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system,
or transmied in any form or by any means, without the prior wrien permission of the
publisher, except in the case of brief quotaons embedded in crical arcles or reviews.
Every eort has been made in the preparaon of this book to ensure the accuracy of the
informaon presented. However, the informaon contained in this book is sold without
warranty, either express or implied. Neither the author nor Packt Publishing, and its dealers
and distributors will be held liable for any damages caused or alleged to be caused directly
or indirectly by this book.
Packt Publishing has endeavored to provide trademark informaon about all of the companies
and products menoned in this book by the appropriate use of capitals. However, Packt
Publishing cannot guarantee the accuracy of this informaon.
First published: January 2012
Producon Reference: 1200112
Published by Packt Publishing Ltd.
Livery Place
35 Livery Street
Birmingham B3 2PB, UK.
ISBN 978-1-84969-152-9
www.packtpub.com
Cover Image by Marcus Grandon (marcusgrandon@mac.com)
Credits
Author
Sylvain Ratabouil
Reviewers
Marko Gargenta
Dr. Frank Grützmacher
Robert Mitchell
Acquision Editor
Sarah Cullington
Lead Technical Editor
Dayan Hyames
Technical Editor
Pramila Balan
Copy Editor
Laxmi Subramanian
Project Coordinator
Jovita Pinto
Proofreader
Lynda Sliwoski
Indexer
Hemangini Bari
Graphics
Valenna D'silva
Producon Coordinators
Prachali Bhiwandkar
Melwyn D'sa
Nilesh Mohite
Cover Work
Alwin Roy
About the Author
Sylvain Ratabouil is a conrmed IT consultant with experience in C++ and Java
technologies. He worked for the space industry and got involved in aeronauc projects at
Valtech Technologies where he now takes part in the Digital Revoluon.
Sylvain earned the master's degree in IT from Paul Sabaer University in Toulouse and did
M.Sc. in Computer Science from Liverpool University.
As a technology lover, he is passionate about mobile technologies and cannot live or sleep
without his Android smartphone.
I would like to thank Steven Wilding for oering me to write this book;
Sneha Harkut and Jovita Pinto for awaing me with so much paence;
Reshma Sundaresan, and Dayan Hyames for pung this book on the
right track; Sarah Cullington for helping me nalizing this book;
Dr. Frank Grützmacher, Marko Gargenta, and Robert Mitchell for
all their helpful comments.
About the Reviewers
Dr. Frank Grützmacher has worked for several major German rms in the area of large
distributed systems. He was an early user of dierent Corba implementaons in the past.
He got his Ph.D. in the eld of electrical engineering, but with the focus on distributed
heterogeneous systems. In 2010, he was involved in a project, which changed parts of the
Android plaorm for a manufacturer. From there, he got his knowledge about the android
NDK and nave processes on this plaorm.
He has already worked as a reviewer for another Android 3.0 book.
Robert Mitchell is an MIT graduate with over 40 years experience in Informaon
Technology and is semirered. He has developed soware for all the big iron companies:
IBM, Amdahl, Fujitsu, Naonal Semiconductor, and Storage Technology. Soware companies
include Veritas and Symantec. Recent languages that he knows are Ruby and Java, with a
long background in C++.
www.PacktPub.com
Support les, eBooks, discount offers and more
You might want to visit www.PacktPub.com for support les and downloads related to
your book.
Did you know that Packt oers eBook versions of every book published, with PDF and ePub
les available? You can upgrade to the eBook version at www.PacktPub.com and as a print
book customer, you are entled to a discount on the eBook copy. Get in touch with us at
service@packtpub.com for more details.
At www.PacktPub.com, you can also read a collecon of free technical arcles, sign up for
a range of free newsleers and receive exclusive discounts and oers on Packt books and
eBooks.
http://PacktLib.PacktPub.com
Do you need instant soluons to your IT quesons? PacktLib is Packt's online digital book
library. Here, you can access, read and search across Packt's enre library of books.
Why Subscribe?
Fully searchable across every book published by Packt
Copy and paste, print and bookmark content
On demand and accessible via web browser
Free Access for Packt account holders
If you have an account with Packt at www.PacktPub.com, you can use this to access
PacktLib today and view nine enrely free books. Simply use your login credenals for
immediate access.
Table of Contents
Preface 1
Chapter 1: Seng Up your Environment 7
Geng started with Android development 7
Seng up Windows 8
Time for acon – preparing Windows for Android development 8
Installing Android development kits on Windows 12
Time for acon – installing Android SDK and NDK on Windows 13
Seng up Mac OS X 18
Time for acon – preparing Mac OS X for Android development 18
Installing Android development kits on Mac OS X 20
Time for acon – installing Android SDK and NDK on Mac OS X 20
Seng up Linux 22
Time for acon – preparing Ubuntu Linux for Android development 22
Installing Android development kits on Linux 27
Time for acon – installing Android SDK and NDK on Ubuntu 27
Seng up the Eclipse development environment 29
Time for acon – installing Eclipse 29
Emulang Android 33
Time for acon – creang an Android virtual device 33
Developing with an Android device on Windows and Mac OS X 37
Time for acon – seng up your Android device on Windows and Mac OS X 37
Developing with an Android device on Linux 39
Time for acon – seng up your Android device on Ubuntu 39
Troubleshoong a development device 42
Summary 43
Chapter 2: Creang, Compiling, and Deploying Nave Projects 45
Compiling and deploying NDK sample applicaons 46
Time for acon – compiling and deploying the hellojni sample 46
Table of Contents
[ ii ]
Exploring Android SDK tools 51
Android debug bridge 51
Project conguraon tool 54
Creang your rst Android project using eclipse 56
Time for acon – iniang a Java project 56
Introducing Dalvik 59
Interfacing Java with C/C++ 60
Time for acon – calling C code from Java 60
More on Makeles 65
Compiling nave code from Eclipse 67
Time for acon – creang a hybrid Java/C/C++ project 67
Summary 72
Chapter 3: Interfacing Java and C/C++ with JNI 73
Working with Java primives 74
Time for acon – building a nave key/value store 75
Referencing Java objects from nave code 85
Time for acon – saving a reference to an object in the Store 85
Local and global JNI references 90
Throwing excepons from nave code 91
Time for acon – raising excepons from the Store 92
JNI in C++ 96
Handling Java arrays 96
Time for acon – saving a reference to an object in the Store 97
Checking JNI excepons 106
Summary 107
Chapter 4: Calling Java Back from Nave Code 109
Synchronizing Java and nave threads 110
Time for acon – running a background thread 111
Aaching and detaching threads 120
More on Java and nave code lifecycles 121
Calling Java back from nave code 122
Time for acon – invoking Java code from a nave thread 122
More on callbacks 133
JNI method denions 134
Processing bitmaps navely 135
Time for acon – decoding camera feed from nave code 136
Summary 146
Chapter 5: Wring a Fully-nave Applicaon 147
Creang a nave acvity 148
Time for acon – creang a basic nave acvity 148
Table of Contents
[ iii ]
Handling acvity events 155
Time for acon – handling acvity events 155
More on Nave App Glue 166
UI thread 167
Nave thread 168
Android_app structure 170
Accessing window and me navely 171
Time for acon – displaying raw graphics and implemenng a mer 172
More on me primives 181
Summary 181
Chapter 6: Rendering Graphics with OpenGL ES 183
Inializing OpenGL ES 184
Time for acon – inializing OpenGL ES 184
Reading PNG textures with the asset manager 193
Time for acon – loading a texture in OpenGL ES 194
Drawing a sprite 208
Time for acon – drawing a Ship sprite 209
Rendering a le map with vertex buer objects 220
Time for acon – drawing a le-based background 221
Summary 238
Chapter 7: Playing Sound with OpenSL ES 239
Inializing OpenSL ES 241
Time for acon – creang OpenSL ES engine and output 241
More on OpenSL ES philosophy 248
Playing music les 249
Time for acon – playing background music 249
Playing sounds 256
Time for acon – creang and playing a sound buer queue 257
Event callback 266
Recording sounds 268
Summary 272
Chapter 8: Handling Input Devices and Sensors 273
Interacng with Android 274
Time for acon – handling touch events 276
Detecng keyboard, D-Pad, and Trackball events 288
Time for acon – handling keyboard, D-Pad, and trackball, navely 289
Probing device sensors 298
Time for acon – turning your device into a joypad 300
Summary 313
Table of Contents
[ iv ]
Chapter 9: Porng Exisng Libraries to Android 315
Developing with the Standard Template Library 316
Time for acon – embedding GNU STL in DroidBlaster 316
Stac versus shared 326
STL performances 327
Compiling Boost on Android 328
Time for acon – embedding Boost in DroidBlaster 328
Porng third-party libraries to Android 338
Time for acon – compiling Box2D and Irrlicht with the NDK 339
GCC opmizaon levels 346
Mastering Makeles 346
Makele variables 347
Makele Instrucons 348
Summary 351
Chapter 10: Towards Professional Gaming 353
Simulang physics with Box2D 353
Time for acon – simulang physics with Box2D 354
More on collision detecon 366
Collision modes 367
Collision ltering 368
More resources about Box2D 369
Running a 3D engine on Android 369
Time for acon – rendring 3D graphics with Irrlicht 370
More on Irrlicht scene management 381
Summary 382
Chapter 11: Debugging and Troubleshoong 383
Debugging with GDB 383
Time for acon – debugging DroidBlaster 384
Stack trace analysis 392
Time for acon – analysing a crash dump 392
More on crash dumps 396
Performance analysis 397
Time for acon – running GProf 398
How it works 403
ARM, thumb, and NEON 403
Summary 405
Index 411
Preface
The short history of compung machines has witnessed some major events, which
forever transformed our usage of technology. From the rst massive main frames to
the democrazaon of personal computers, and then the interconnecon of networks.
Mobility is the next revoluon. Like the primive soup, all the ingredients are now
gathered: an ubiquitous network, new social, professional and industrial usages, a
powerful technology. A new period of innovaon is blooming right now in front of our
eyes. We can fear it or embrace it, but it is here, for good!
The mobile challenge
Today's mobile devices are the product of only a few years of evoluon, from the rst
transportable phones to the new ny high-tech monsters we have in our pocket. The
technological me scale is denitely not the same as the human one.
Only a few years ago, surng on the successful wave of its musical devices, Apple and
its founder Steve Jobs combined the right hardware and the right soware at the right
me not only to sasfy our needs, but to create new ones. We are now facing a new
ecosystem looking for a balance between iOS, Windows Mobile, Blackberry, WebOS, and
more importantly Android! The appete of a new market could not let Google apathec.
Standing on the shoulder of this giant Internet, Android came into the show as the best
alternave to the well established iPhones and other iPads. And it is quickly becoming
the number one.
In this modern Eldorado, new usages or technically speaking, applicaons (acvies, if
you already are an Android adept) sll have to be invented. This is the mobile challenge.
And the dematerialized country of Android is the perfect place to look for. Android is
(mostly) an open source operang system now supported by a large panel of mobile
device manufacturers.
Preface
[ 2 ]
Portability among hardware and adaptability to the constrained resources of mobile devices:
this is the real essence of the mobile challenge from a technical perspecve. With Android,
ones has to deal with mulple screen resoluons, various CPU and GPU speed or capabilies,
memory limitaons, and so on, which are not topics specic to this Linux-based system,
(that is, Android) but can parcularly be incommoding.
To ease portability, Google engineers packaged a virtual machine with a complete framework
(the Android SDK) to run programs wrien in one of the most spread programming language
nowadays: Java. Java, augmented with the Android framework, is really powerful. But rst,
Java is specic to Android. Apple's products are wrien for example in Objecve C and can be
combined with C and C++. And second, a Java virtual machine does not always give you enough
capability to exploit the full power of mobile devices, even with just-in-me compilaon
enabled. Resources are limited on these devices and have to be carefully exploited to oer
the best experience. This is where the Android Nave Development Kit comes into place.
What this book covers
Chapter 1, Seng Up your Environment, covers the tools required to develop an applicaon
with the Android NDK. This chapter also covers how to set up a development environment,
connect your Android device, and congure the Android emulator.
Chapter 2, Creang, Compiling, and Deploying Nave Projects, we will compile, package, and
deploy NDK samples and create our rst Android Java/C hybrid project with NDK and Eclipse.
Chapter 3, Interfacing Java and C/C++ with JNI, presents how Java integrates and
communicates with C/C++ through Java Nave Interface.
Chapter 4, Calling Java Back from Nave Code, we will call Java from C to achieve
bidireconal communicaon and process graphic bitmaps navely.
Chapter 5, Wring a Fully-nave Applicaon, looks into the Android NDK applicaon life-cycle.
We will also write a fully nave applicaon to get rid of Java.
Chapter 6, Rendering Graphics with OpenGL ES, teaches how to display advanced 2D and 3D
graphics at full speed with OpenGL ES. We will inialize display, load textures, draw sprites
and allocate vertex and index buers to display meshes.
Chapter 7, Playing Sound with OpenSL ES, adds a musical dimension to nave applicaons
with OpenSL ES, a unique feature provided only by the Android NDK. We will also record
sounds and reproduce them on the speakers.
Preface
[ 3 ]
Chapter 8, Handling Input Devices and Sensors, covers how to interact with an Android
device through its mul-touch screen. We will also see how to handle keyboard events
navely and apprehend the world through sensors and turn a device into a game controller.
Chapter 9, Porng Exisng Libraries to Android, we will compile the indispensable C/C++
frameworks, STL and Boost. We will also see how to enable excepons and RunTime Type
Informaon. And also port our own or third-party libraries to Android, such as, Irrlicht 3D
engine and Box2D physics engine.
Chapter 10, Towards Professional Gaming, creates a running 3D game controlled with
touches and sensors using Irrlicht and Box2D.
Chapter 11, Debugging and Troubleshoong, provides an in-depth analysis of the running
applicaon with NDK debug ulity. We will also analyze crash dumps and prole the
performance of our applicaon.
What you need for this book
A PC with either Windows or Linux or an Intel-based Mac. As a test machine, an Android device
is highly advisable, although the Android NDK provides an emulator which can sasfy most of
the needs of a hungry developer. But for 2D and 3D graphics, it is sll too limited and slow.
I assume you already understand C and C++ languages, pointers, object-oriented features,
and other modern language concepts. I also assume you have some knowledge about
the Android plaorm and how to create Android Java applicaons. This is not a strong
prerequisite, but preferable. I also guess you are not frighten by command-line terminals.
The version of Eclipse used throughout this book is Helios (3.6).
Finally, bring all your enthusiasm because these lile beasts can become really amazing
when they demonstrate all their potenal and sense of contact.
Who this book is for
Are you an Android Java programmer who needs more performance? Are you a C/C++
developer who doesn't want to bother with Java stu and its out-of-control garbage
collector? Do you want to create fast intensive mulmedia applicaons or games? Answer
yes to any of the above quesons and this book is for you. With some general knowledge
of C/C++ development, you will be able to dive head rst into nave Android development.
Preface
[ 4 ]
Conventions
In this book, you will nd several headings appearing frequently.
To give clear instrucons of how to complete a procedure or task, we use:
Time for action – heading
1. Acon 1
2. Acon 2
3. Acon 3
Instrucons oen need some extra explanaon so that they make sense, so they are
followed with:
What just happened?
This heading explains the working of tasks or instrucons that you have just completed.
You will also nd some other learning aids in the book, including:
Pop quiz – heading
These are short mulple choice quesons intended to help you test your own understanding.
Have a go hero – heading
These set praccal challenges and give you ideas for experimenng with what you
have learned.
You will also nd a number of styles of text that disnguish between dierent kinds of
informaon. Here are some examples of these styles, and an explanaon of their meaning.
Code words in text are shown as follows: "Open a command line window and key in
java –version to check the installaon."
A block of code is set as follows:
export ANT_HOME=`cygpath –u "$ANT_HOME"`
export JAVA_HOME=`cygpath –u "$JAVA_HOME"`
export ANDROID_SDK=`cygpath –u "$ANDROID_SDK"`
export ANDROID_NDK=`cygpath –u "$ANDROID_NDK"`
Preface
[ 5 ]
When we wish to draw your aenon to a parcular part of a code block, the relevant lines
or items are set in bold:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.hellojni"
android:versionCode="1"
android:versionName="1.0">
Any command-line input or output is wrien as follows:
$ make –version
New terms and important words are shown in bold. Words that you see on the screen, in
menus or dialog boxes for example, appear in the text like this: "When proposed, include
Devel/make and Shells/bash packages".
Warnings or important notes appear in a box like this.
Tips and tricks appear like this.
Reader feedback
Feedback from our readers is always welcome. Let us know what you think about this
book—what you liked or may have disliked. Reader feedback is important for us to develop
tles that you really get the most out of.
To send us general feedback, simply send an e-mail to feedback@packtpub.com, and
menon the book tle through the subject of your message.
If there is a topic that you have experse in and you are interested in either wring or
contribung to a book, see our author guide on www.packtpub.com/authors.
Customer support
Now that you are the proud owner of a Packt book, we have a number of things to help
you to get the most from your purchase.
Preface
[ 6 ]
Downloading the example code
You can download the example code les for all Packt books you have purchased from your
account at http://www.packtpub.com. If you purchased this book elsewhere, you can
visit http://www.packtpub.com/support and register to have the les e-mailed directly
to you.
Errata
Although we have taken every care to ensure the accuracy of our content, mistakes do
happen. If you nd a mistake in one of our books—maybe a mistake in the text or the
code—we would be grateful if you would report this to us. By doing so, you can save other
readers from frustraon and help us improve subsequent versions of this book. If you
nd any errata, please report them by vising http://www.packtpub.com/support,
selecng your book, clicking on the errata submission form link, and entering the details
of your errata. Once your errata are veried, your submission will be accepted and the
errata will be uploaded to our website, or added to any list of exisng errata, under the
Errata secon of that tle.
Piracy
Piracy of copyright material on the Internet is an ongoing problem across all media. At
Packt, we take the protecon of our copyright and licenses very seriously. If you come
across any illegal copies of our works, in any form, on the Internet, please provide us with
the locaon address or website name immediately so that we can pursue a remedy.
Please contact us at copyright@packtpub.com with a link to the suspected pirated material.
We appreciate your help in protecng our authors, and our ability to bring you
valuable content.
Questions
You can contact us at questions@packtpub.com if you are having a problem with any
aspect of the book, and we will do our best to address it.
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
1
Setting Up your Environment
Are you ready to take up the mobile challenge? Is your computer switched on,
mouse and keyboard plugged in, and screen illuminang your desk? Then lets
not wait a minute more!
In this rst chapter, we are going to do the following:
Download and install the necessary tools to develop applicaons using Android
Set up a development environment
Connect and prepare an Android device for development
Getting started with Android development
What dierenates mankind from animals is the use of tools. Android developers,
this authenc species you are about to belong to, are no dierent!
To develop applicaons on Android, we can use any of the following three plaorms:
Microso Windows PC
Apple Mac OS X
Linux PC
Windows 7, Vista, Mac OS X, and Linux systems are supported in both 32 and 64-bit versions,
but Windows XP in 32-bit mode only. Only Mac OS X computers of version 10.5.8 or later and
based on Intel architectures are supported (not PowerPC processors). Ubuntu is supported
only from version 8.04 (Hardy Heron).
Seng Up your Environment
[ 8 ]
Right, this is a good start but unless you are able to read and write binary language like English,
having an OS is not enough. We also need soware dedicated to Android development:
The JDK (Java Development Kit)
The Android SDK (Soware Development Kit)
The Android NDK (Nave Development Kit)
An IDE (Integrated Development Environment): Eclipse
Android, and more specically Android NDK compilaon system is heavily based on Linux.
So we also need to set up some ulies by default, and we need to install one environment
that supports them: Cygwin (unl NDK R7). This topic is covered in detail later in the chapter.
Finally, a good old command-line Shell to manipulate all these ulies is essenal: we will
use Bash (the default on Cygwin, Ubuntu, and Mac OS X).
Now that we know what tools are necessary to work with Android, lets start with the
installaon and setup process.
The following secon is dedicated to Windows. If you are a Mac or Linux
user, you can immediately jump to the Seng up Mac OS X or the
Seng up Linux secon.
Setting up Windows
Before installing the necessary tools, we need to set up Windows to host our Android
development tools properly.
Time for action – preparing Windows for Android development
To work with the Android NDK, we need to set up a Cygwin Linux-like environment
for Windows:
Since NDK R7, Cygwin installaon is not required anymore
(steps 1 to 9). The Android NDK provides addional nave Windows
binaries (for example, ndk-build.cmd).
1. Go to http://cygwin.com/install.html.
2. Download setup.exe and execute it.
3. Select Install from Internet.
Chapter 1
[ 9 ]
4. Follow the wizard screens.
5. Select a download site from where Cygwin packages are going to be downloaded.
Consider using a server in your country:
6. When proposed, include Devel/make and Shells/bash packages:
Seng Up your Environment
[ 10 ]
7. Follow the installaon wizard unl the end. This may take some me depending
on your Internet connecon.
8. Aer installaon, launch Cygwin. Your prole les get created on rst launch.
9. Enter the following command to check if Cygwin works:
$ make –version
To run Eclipse and allow compilaon of Android Java code to bytecode, a Java Development
Kit is required. On Windows, the obvious choice is the Oracle Sun JDK:
1. Go to the Oracle website and download the latest Java Development Kit: http://
www.oracle.com/technetwork/java/javase/downloads/index.html.
2. Launch the downloaded program and follow the installaon wizard. At the end
of the installaon, a browser is opened asking for JDK registraon. This step is
absolutely not compulsory and can be ignored.
3. To make sure the newly installed JDK is used, lets dene its locaon in environment
variables. Open the Windows Control panel and go to the System panel (or right-
click on Computer item in the Windows Start menu and select Properes). Then go
to Advanced system sengs. The System Properes window appears. Finally, select
Advanced tab and click on the Environment Variables buon.
4. In the Environment Variables window, inside the System variables list, insert the
JAVA_HOME variable with JDK installaon directory as value and validate. Then
edit PATH (or Path) and insert the %JAVA_HOME%\bin directory before any other
directory and separate it with a semicolon. Validate and close the window.
5. Open a command-line window and key in java –version to check the installaon.
The result should be similar to the following screenshot. Check carefully to make
sure that the version number corresponds to the version of the newly installed JDK:
$ java –version
Chapter 1
[ 11 ]
To compile projects from the command line, the Android SDK supports Ant—a Java-based
build automaon ulity. Lets install it:
1. Go to http://ant.apache.org/bindownload.cgi and download Ant binaries,
packed within a ZIP archive.
2. Unzip Ant in the directory of your choice (for example, C:\Ant).
3. Go back to the Environment Variables window, as in step 12, and create the
ANT_HOME variable with the Ant directory as the value. Append the %ANT_HOME%\
bin directory to PATH:
4. From a classic Windows terminal, check the Ant version to make sure it is
properly working:
Seng Up your Environment
[ 12 ]
What just happened?
We have prepared Windows with the necessary underlying ulies to host Android
development tools: Cygwin and Java Development Kit.
Cygwin is an open source soware collecon that allows the Windows plaorm to emulate
a Unix-like environment. It aims at navely integrang soware based on POSIX standard
(such as Unix, Linux, and so on) into Windows. It can be considered as an intermediate layer
between applicaons originated from Unix/Linux (but navely recompiled on Windows) and
the Windows OS itself.
We have also deployed a Java Development Kit in version 1.6 and checked if it is properly
working from the command line. Because Android SDK uses generics, the JDK in version 1.5
is the least required when developing with Android. JDK is simple to install on Windows but
it is important to make sure a previous installaon, such as JRE (Java Runme Environment,
which aims at execung applicaons but not developing them) is not interfering. This is why
we have dened JAVA_HOME and PATH environment variables to ensure proper JDK is used.
Finally, we have installed Ant ulity that we are going to use in the next chapter to build
projects manually. Ant is not required for Android development but is a very good soluon
to set up a connuous integraon chain.
Where is Java’s home?
Dening the JAVA_HOME environment variable is not required. However,
JAVA_HOME is a popular convenon among Java applicaons, Ant being one
of them. It rst looks for the java command in JAVA_HOME (if dened)
before looking in PATH. If you install an up-to-date JDK in another locaon
later on, do not forget to update JAVA_HOME.
Installing Android development kits on Windows
Once JDK is installed on our system, we can start installing Android SDK and NDK to create,
compile, and debug Android programs.
Chapter 1
[ 13 ]
Time for action – installing Android SDK and NDK on Windows
1. Open your Web browser and go to http://developer.android.com/sdk.
This web page lists all available SDKs, one for each plaorm.
2. Download Android SDK for Windows, packaged as an Exe installer.
3. Then, go to http://developer.android.com/sdk/ndk and download the
Android NDK (not SDK!) for Windows, packaged as a ZIP archive this me.
4. Execute Android SDK installer. Select an appropriate installaon locaon (for example,
C:\Android\android-sdk), knowing that Android SDK and NDK together can take
more than 3 GB of disk space (currently!) with all ocial API versions installed. As a
precauon, avoid leaving any space in the target installaon path.
5. Follow the installaon wizard unl the end. Check the Start SDK Manager:
6. The Android SDK and AVD Manager is launched. The Package installaon window
appears automacally.
Seng Up your Environment
[ 14 ]
7. Check the Accept All opon and click on Install to start the installaon of
Android components:
8. Aer a few minutes, all packages get downloaded and a message asking to restart
ADB service (the Android Debug Bridge) appears. Validate by clicking on Yes.
9. Close the applicaon.
10. Now, unzip Android NDK archive into its nal locaon (for example, C:\Android\
android-ndk). Again, avoid leaving any space in the installaon path (or some
problems could be encountered with Make).
To easily access Android ulies from the command line, let’s dene the
environment variables:
11. Open the Environment Variables system window, as we did in the previous part.
Inside the System variables list, insert the ANDROID_SDK and ANDROID_NDK
variables with the corresponding directories as values.
12. Append %ANDROID_SDK%\tools, %ANDROID_SDK%\platform-tools and
%ANDROID_NDK%, all separated by a semicolon, to your PATH.
Chapter 1
[ 15 ]
13. All the Windows environment variables should be imported automacally by Cygwin
when launched. Lets verify this by opening a Cygwin terminal and checking whether
NDK is available:
$ ndk-build –-version
14. Now, check the Ant version to make sure it is properly working on Cygwin:
$ ant -version
The rst me Cygwin should emit a surprising warning: paths are in MS-DOS style
and not POSIX. Indeed, Cygwin paths are emulated and should look similar to /
cygdrive/<Drive letter>/<Path to your directory with forward
slashes>. For example, if Ant is installed in c:\ant, then the path should be
indicated as /cygdrive/c/ant.
15. Lets x this. Go to your Cygwin directory. There, you should nd a directory named
home/<your user name> containing a .bash_profile. Open it in edion.
16. At the end of the script, translate the Windows environment variables into
Cygwin variables with the cygpath ulity. PATH does not need to be translated as
this essenal variable is processed automacally by Cygwin. Make sure to use the
prime character (`) (to execute a command inside another), which has a dierent
meaning than the apostrophe (‘) (to dene a variable) with Bash. An example
.bash_profile is provided with this book:
export ANT_HOME=`cygpath –u “$ANT_HOME”`
export JAVA_HOME=`cygpath –u “$JAVA_HOME”`
export ANDROID_SDK=`cygpath –u “$ANDROID_SDK”`
export ANDROID_NDK=`cygpath –u “$ANDROID_NDK”`
Seng Up your Environment
[ 16 ]
17. Reopen a Cygwin window and check the Ant version again. No warning is issued
this me:
$ ant -version
What just happened?
We have downloaded and deployed both Android SDK and NDK and made them available
through command line using environment variables.
We have also launched the Android SDK and AVD manager, which aims at managing SDK
components installaon, updates, and emulaon features. This way, new SDK API releases
as well as third-party components (for example, Samsung Galaxy Tablet emulator, and so
on) are made available to your development environment without having to reinstall the
Android SDK.
If you have trouble connecng at step 7, then you may be located behind a proxy. In this
case, Android SDK and AVD manager provide a Sengs secon where you can specify your
proxy sengs.
At step 16, we have converted the Windows paths dened inside the environment variables
into Cygwin paths. This path form, which may look odd at rst, is used by Cygwin to emulate
Windows paths as if they were Unix paths. Cygdrive is similar to a mount or media directory
on Unix and contains every Windows drive as a plugged le system.
Cygwin paths
The rule to remember while using paths with Cygwin is that they must
contain forward slashes only and the drive leer is replaced by /cygdrive/
[Drive Letter]. But beware, le names in Windows and Cygwin are
case-sensive, contrary to real Unix systems.
Chapter 1
[ 17 ]
Like any Unix system, Cygwin has a root directory named slash (/). But since there is no real
root directory in Windows, Cygwin emulates it in its own installaon directory. In a Cygwin
command line, enter the following command to see its content:
$ ls /
These les are the ones located in your Cygwin directory (except /proc, which is an
in-memory directory). This explains why we updated .bash_profile in the home
directory itself, which is located inside the Cygwin directory.
Ulies packaged with Cygwin usually expect Cygwin-style paths, although Windows-style
paths work most of the me. Thus, although we could have avoided the conversion in
.bash_profile (at the price of a warning), the natural way to work with Cygwin and avoid
future troubles is to use Cygwin paths. However, Windows ulies generally do not support
Cygwin paths (for example, java.exe), in which case, an inverse path conversion is required
when calling them. To perform conversion, cygpath ulity provides the following opons:
-u: To convert Windows paths to Unix paths
-w: To convert Unix paths to Windows paths
-p: To convert a list of paths (separated by ; on Windows and : on Unix)
Sll at step 17, you may have some dicules when eding .bash_profile: some weird
square characters may appear and the enre text is on one very long line! This is because it
is encoded using Unix encoding. So use a Unix compable le editor (such as Eclipse, PSPad,
or Notepad++) when eding Cygwin les. If you already got into trouble, you can use either
your editor End-Of-Line conversion feature (Notepad++ and PSPad provide one) or apply
command-line dos2unix ulity (provided with Cygwin) on the incriminated le.
Seng Up your Environment
[ 18 ]
Char return on Cygwin
Unix les use a simple line-feed character (beer known
as \n) to indicate an end of line whereas Windows uses a
carriage return (CR or \r) plus a line feed. MacOS, on the
other hand, uses a carriage return only. Windows newline
markers can cause lots of trouble in Cygwin Shell scripts,
which should be kept in Unix format.
This is the end of the secon dedicated to Windows setup.
If you are not a Mac or Linux user, you can jump to the
Seng up Eclipse development environment secon.
Setting up Mac OS X
Apple computers and Mac OS X have a reputaon for being simple and easy to use. And
honestly, this adage is rather true when it comes to Android development. Indeed, Mac OS X
is based on Unix, well adapted to run the NDK toolchain, and a recent JDK is already installed
by default. Mac OS X comes with almost anything we need with the excepon of Developer
Tools, which need to be installed separately. These Developer Tools include XCode IDE, many
Mac development ulies, and also some Unix ulies, such as Make and Ant.
Time for action – preparing Mac OS X for Android development
All developer tools are included in XCode installaon package (version 4, at the me this
book was wrien). There exist four soluons to get this package, and they are as follows:
If you have Mac OS X installaon media, open it and look for the XCode installaon
package
XCode is also provided on the AppStore for free (but this has changed recently and
may change in the future too)
XCode can also be downloaded from the Apple website with a paying program
subscripon at the address http://developer.apple.com/xcode/
Older version 3, compable with Android development tools, is available for free
as a disc image from the same page with a free Apple Developer account
Using the most appropriate soluon for your case, lets install XCode:
1. Find your XCode installaon package and run it. Select the UNIX Development
opon when the customizaon screen appears. Finish installaon. We are done!
Chapter 1
[ 19 ]
2. To develop with Android NDK, we need the Make build tool for nave code. Open a
terminal prompt and ensure Make correctly works:
$ make --version
3. To run Eclipse and allow compilaon of Android Java code to bytecode, Java
Development Kit is required. Let’s check if the default Mac OS X JDK works ne:
$ java –version
4. To compile projects from the command line, the Android SDK supports Ant,
a Java-based build automaon ulity. Sll in a terminal, ensure Ant is
correctly installed:
$ ant –version
What just happened?
We have prepared our Mac OS X to host Android development tools. And as usual with
Apple, that was rather easy!
We have checked if Java Development Kit in version 1.6 is properly working from the
command line. Because Android SDK uses generics, a JDK in version 1.5 is the least
required for Android development.
We have installed Developer Tools, which include Make—to run the NDK compiler—and
Ant—that we are going to use in the next chapter to build projects manually. Ant is not
required for Android development but is a very good soluon to set up a connuous
integraon chain.
Seng Up your Environment
[ 20 ]
Installing Android development kits on Mac OS X
Once a JDK is installed on your system, we can start installing Android Development SDK
and NDK to create, compile, and debug Android programs.
Time for action – installing Android SDK and NDK on Mac OS X
1. Open your web browser and go to http://developer.android.com/sdk.
This web page lists all available SDKs, one for each plaorm.
2. Download Android SDK for Mac OS X, which is packaged as a ZIP archive.
3. Then, go to http://developer.android.com/sdk/ndk and download the
Android NDK (not SDK!) for Mac OS X, packaged as a Tar/BZ2 archive this me.
4. Uncompress the downloaded archives separately into the directory of your choice
(for example, /Developer/AndroidSDK and /Developer/AndroidNDK).
5. Lets declare these two directories as environment variables. From now on, we will
refer to these directories as $ANDROID_SDK and $ANDROID_NDK throughout this
book. Assuming you use the default Bash command-line shell, create or edit your
.prole le (be careful, this is a hidden le!) in your home directory and add the
following variables:
export ANDROID_SDK=”<path to your Android SDK directory>”
export ANDROID_NDK=”<path to your Android NDK directory>”
export PATH=”$PATH:$ANDROID_SDK/tools:$ANDROID_SDK/platform-
tools:$ANDROID_NDK”
Downloading the example code
You can download the example code les for all Packt books you have
purchased from your account at hp://www.PacktPub.com. If you
purchased this book elsewhere, you can visit hp://www.PacktPub.com/
support and register to have the les e-mailed directly to you.
6. Save the le and log out from your current session.
7. Log in again and open a terminal. Enter the following command:
$ android
8. The Android SDK and AVD Manager window shows up.
9. Go to the Installed packages secon and click on Update All:
Chapter 1
[ 21 ]
10. A package selecon dialog appears. Select Accept All and then Install.
11. Aer few minutes, all packages get downloaded and a message asking to restart
ADB service (the Android Debug Bridge) appears. Validate by clicking on Yes.
12. You can now close the applicaon.
What just happened?
We have downloaded and deployed both Android SDK and NDK and made them available
through the command line using environment variables.
Mac OS X and environment variables
Mac OS X is tricky when it comes to environment variables. They can be easily
declared in a .profile for applicaons launched from a terminal, as we just
did. They can also be declared using an environment.plist le for GUI
applicaons, which are not launched from Spotlight. A more powerful way to
congure them is to dene or update /etc/launchd.conf system le (see
http://developer.apple.com/).
We have also launched the Android SDK and AVD manager, which aims at managing the
installaon, updates, and emulaon features of the SDK components. This way, new SDK API
releases as well as third-party components (for example, Samsung Galaxy Tablet emulator,
and so on) are made available to your development environment without having to reinstall
the Android SDK.
Seng Up your Environment
[ 22 ]
If you have trouble connecng at step 9, then you may be located behind a proxy. In this
case, Android SDK and AVD manager provide a Sengs secon where you can specify your
proxy sengs.
This is the end of the secon dedicated to Mac OS X setup. If you are
not a Linux user, you can jump to the Seng up Eclipse development
environment secon.
Setting up Linux
Although Linux is more naturally suited for Android development, as the Android toolchain is
Linux-based, some setup is necessary as well.
Time for action – preparing Ubuntu Linux for
Android development
To work with Android NDK, we need to check and install some system packages and ulies:
1. First, Glibc (the GNU C standard library, in version 2.7 or later) must be installed. It is
usually shipped with Linux systems by default. Check its version using the following
command:
$ ldd -–version
2. We also need the Make build tool for nave code. Installaon can be performed
using the following command:
$ sudo apt-get install build-essential
Alternavely, Make can be installed through Ubuntu Soware Center. Look for
build-essenal in the dedicated search box and install the packages found:
Chapter 1
[ 23 ]
Package build-essential contains a minimal set of tools for compilaon and
packaging on Linux Systems. It also includes GCC (the GNU C Compiler), which is not
required for standard Android development as Android NDK already packages its
own version.
3. To ensure that Make is correctly installed, type the following command. If correctly
installed, the version will be displayed:
$ make --version
Seng Up your Environment
[ 24 ]
Special note for 64-bit Linux owner
We also need 32-bit libraries installed to avoid compability problems. This can
be done using the following command (to execute in a command-line prompt)
or again the Ubuntu Soware Center:
sudo apt-get install ia32-libs
To run Eclipse and allow compilaon of Android Java code to bytecode, Java Development Kit
is required. We need to download and install Oracle Sun Java Development Kit. On Ubuntu,
this can be performed from the Synapc Package Manager:
1. Open Ubuntu System/Administraon menu and select Synapc Package Manager
(or open your Linux package manager if you use another Linux distros).
2. Go to the Edit | Soware Sources menu.
3. In the Soware Sources dialog, open the Other Soware tab.
4. Check the Canonical Partners line and close the dialog:
Chapter 1
[ 25 ]
5. Package cache synchronizes automacally with the Internet, and aer a few seconds
or minutes some new soware is made available in the Canonical Partners secon.
6. Find Sun Java™ Development Kit (JDK) 6 (or later) and click on Install. You are
also advised to install Lucida TrueType fonts (from the Sun JRE), the Java(TM)
Plug-in packages.
7. Accept the license (aer reading it carefully of course!). Be careful as it may open
in the background.
8. When installaon is nished, close Ubuntu Soware Center.
9. Although Sun JDK is now installed, it is not yet available. Open JDK is sll used by
default. Lets acvate Sun JRE through the command line. First, check available JDK:
$ update-java-alternatives –l
10. Then, acvate the Sun JRE using the idener returned previously:
$ sudo update-java-alternatives –s java-6-sun
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
Seng Up your Environment
[ 26 ]
11. Open a terminal and check that installaon is OK by typing:
$ java –version
The Android SDK supports Ant, a Java-based build automaon ulity, to compile projects
from the command line. Lets install it.
1. Install Ant with the following command or with the Ubuntu Soware Center:
$ sudo apt-get install ant
2. Check whether Ant is properly working:
$ ant --version
What just happened?
We have prepared our Linux operang system with the necessary ulies to host Android
development tools.
We have installed a Java Development Kit in version 1.6 and checked if it is properly working
from the command line. Because Android SDK uses generics, the JDK in version 1.5 is the
least required for Android development.
You may wonder why we bothered with the installaon of Sun JDK while Open JDK is already
ready to use. The reason is simply that Open JDK is not ocially supported by Android SDK.
If you want to avoid any possible interacon with Open JDK, think about removing it enrely
from your system. Go to the Provided by Ubuntu secon in the Ubuntu Soware Center and
click on Remove for each OpenJDK line. For more informaon, look for the ocial Ubuntu
documentaon: http://help.ubuntu.com/community/Java.
Chapter 1
[ 27 ]
Finally, we have installed Ant ulity that we are going to use in the next chapter to build
projects manually. Ant is not required for Android development but is a very good soluon
to set up a connuous integraon chain.
There is no more Sun JDK on Linux repositories since Java 7.
The Open JDK becomes the ocial Java implementaon.
Installing Android development kits on Linux
Once JDK is installed on your system, we can start installing Android Development SDK and
NDK to create, compile, and debug Android programs.
Time for action – installing Android SDK and NDK on Ubuntu
1. Open your web browser and go to http://developer.android.com/sdk.
This web page lists all available SDKs, one for each plaorm.
2. Download Android SDK for Linux, which is packaged as a Tar/GZ archive.
3. Then, go to http://developer.android.com/sdk/ndk and download the
Android NDK (not SDK!) for Linux, packaged as a Tar/BZ2 archive this me.
4. Uncompress the downloaded archives separately into the directories of your choice
(for example, ~/AndroidSDK and ~/AnroidNDK). On Ubuntu, you can use Archive
Manager (right-click on the archive le and Extract Here).
5. Lets declare these two directories as environment variables. From now on, we
will refer to these directories as $ANDROID_SDK and $ANDROID_NDK throughout
this book. Assuming you use a Bash command-line shell, edit your .prole le
(be careful, this is a hidden le!) in your home directory and add the following
variables:
export ANDROID_SDK=”<path to your Android SDK directory>”
export ANDROID_NDK=”<path to your Android NDK directory>”
export PATH=”$PATH:$ANDROID_SDK/tools:$ANDROID_SDK/platform-
tools:$ANDROID_NDK”
6. Save the le and log out from your current session.
7. Log in again and open a terminal. Enter the following command:
$ android
8. The Android SDK and AVD Manager window shows up.
Seng Up your Environment
[ 28 ]
9. Go to the Installed packages secon and click on Update All:
10. A package selecon dialog appears. Select Accept All and then Install.
11. Aer a few minutes, all packages get downloaded and a message asking to restart
ADB service (the Android Debug Bridge) appears. Validate by clicking on Yes.
12. You can now close the applicaon.
What just happened?
We have downloaded and deployed both Android SDK and NDK and made them available
through the command line using environment variables.
We have also launched the Android SDK and AVD manager, which aims at managing the
installaon, updates, and emulaon features of the SDK components. This way, new SDK API
releases as well as third-party components (for example, Samsung Galaxy Tablet emulator,
and so on) are made available to your development environment without having to reinstall
Android SDK.
If you have trouble connecng at step 9, then you may be located behind a proxy. In this
case, Android SDK and AVD manager provide a Sengs secon where you can specify your
proxy sengs.
This is the end of the secon dedicated to the Linux setup.
The following secon is mixed.
Chapter 1
[ 29 ]
Setting up the Eclipse development environment
Command line lovers, vi fanacs, please go to the next chapter or you may feel sick! For most
humans, having a comfortable and visual-friendly IDE is essenal. And hopefully, Android
works with the greatest of all: Eclipse!
Eclipse is the only ocially supported IDE for Android SDK through the Google ocial plugin
named ADT. But ADT is only for Java. Hopefully, Eclipse supports C/C++ as well through CDT,
a general C/C++ plugin. Although not specic to Android, it works well with the NDK. The
version of Eclipse used throughout this book is Helios (3.6).
Time for action – installing Eclipse
1. Open your web browser and go to http://www.eclipse.org/downloads/.
This web page lists all available Eclipse packages: for Java, J2EE, C++.
2. Download Eclipse IDE for Java Developers.
3. Extract the downloaded Tar/GZ le (on Linux and Mac OS X) or ZIP le (on Windows)
with your archive manager.
4. Once extracted, run Eclipse by double-clicking on the eclipse executable inside its
directory. On Mac OS X, make sure to execute eclipse alias and not Eclipse.app or
else environment variables dened earlier in .profile will not be available
to Eclipse.
5. If Eclipse asks for a workspace, dene a custom workspace directory if you want
to (default workspace is ne) and click OK.
6. Aer Eclipse has started, close the Welcome Page.
7. Go to the Help | Install New Soware menu.
If a problem occurs in the next steps while accessing update sites, then check
your Internet connecon. You may be either disconnected or your computer
is behind a proxy. In the laer case, it is possible to download ADT plugin as
an archive le from the ADT web page and install it manually (or congure
Eclipse to connect through a proxy but that is another maer).
Seng Up your Environment
[ 30 ]
8. Enter https://dl-ssl.google.com/android/eclipse/ in the Work with
eld and validate.
9. Aer a few seconds, a Developer Tools plugin appears; select it and click on the
Next buon.
10. Follow the wizard and accept condions when asked. On the last wizard page, click
on Finish.
11. ADT gets installed. A warning may appear indicang that plugin content is unsigned.
Ignore it and click on OK.
Chapter 1
[ 31 ]
12. When nished, restart Eclipse as requested.
13. When Eclipse is restarted, go to menu Window | Preferences (Eclipse | Preferences
on Mac OS X) and go to the Android secon.
14. Click on Browse and select the path to your Android SDK directory.
15. Validate preferences.
16. Go back to the Help | Install New Soware... menu.
17. Open the Work with combobox and select the item containing Eclipse version name
(here Helios).
18. Find Programming Languages in the plugin tree and open it.
Seng Up your Environment
[ 32 ]
19. Select CDT plugins. Incubaon plugins are not essenal. C/C++ Call Graph
Visualizaon is for Linux only and cannot be installed on Windows or Mac OS X:
20. Follow the wizard and accept condions when asked. On the last wizard page,
click on Finish.
21. When nished, restart Eclipse.
What just happened?
Eclipse is now installed and ocial Android development plugin ADT and C/C++ plugin CDT
are installed. ADT refers to the Android SDK locaon.
The main purpose of ADT is to ease integraon of Eclipse with SDK development tools. It
is perfectly possible to develop in Android without an IDE using command line only. But
automac compilaon, packaging, deployment, and debugging are addicve features, which
are hard to get rid of!
Chapter 1
[ 33 ]
You may have noced that no reference to the Android NDK is given to ADT. This is because
ADT works for Java only. Hopefully, Eclipse is exible enough to handle hybrid Java/C++
projects! We will talk about that further when creang our rst Eclipse project.
In the same way, CDT allows easy integraon of C/C++ compilaon features into Eclipse.
We also “silently” installed JDT, the Java plugin for Eclipse. It is embedded in the Eclipse IDE
for Java Developers package. An Eclipse package including only CDT is also available on the
Eclipse Website.
More on ADT
ADT update site given to Eclipse in step 8 comes from the ocial ADT
documentaon that you can nd at http://developer.android.
com/sdk/eclipse-adt.html. This page is the main informaon point
to visit if new versions of Eclipse or Android are released.
Emulating Android
Android SDK provides an emulator to help developers who do not have a device (or are
impaently waing for a new one!) get started quickly. Lets now see how to set it up.
Time for action – creating an Android virtual device
1. Open Android SDK and AVD Manager using either the command line (key in
android) or the Eclipse toolbar buon:
2. Click on the New buon.
3. Give a name to this new emulated device: Nexus_480x800HDPI.
4. Target plaorm is Android 2.3.3.
5. Specify SD card size: 256.
6. Enable snapshot.
7. Set Built-in resoluon WVGA800.
Seng Up your Environment
[ 34 ]
8. Leave the Hardware secon the way it is.
9. Click on Create AVD.
10. The newly created virtual device now appears in the list:
Chapter 1
[ 35 ]
11. Lets check how it works: click on the Start buon.
12. Click on the Launch buon:
13. The emulator starts up and aer a few minutes, your device is loaded:
Seng Up your Environment
[ 36 ]
What just happened?
We have created our Android Virtual Devices which emulate a Nexus One with an HDPI
(High Density) screen of size 3.7 inches and a resoluon of 480x800 pixels. So we are now
able to test applicaons we are going develop in a representave environment. Even beer,
we are now able to test them in several condions and resoluons (also called skins)
without requiring a costly device.
Although this is out of the scope of this book, customizing addional opons, such as the
presence of a GPS, camera, and so on, is also possible when creang an AVD to test an
applicaon in limited hardware condions. And as a nal note, screen orientaon can be
switched with Ctrl + F11 and Ctrl + F12. Check out the Android website for more informaon
on how to use and congure the emulator (http://developer.android.com/guide/
developing/devices/emulator.html).
Emulaon is not simulaon
Although emulaon is a great tool when developing, there are a few
important points to take into account: emulaon is slow, not always perfectly
representave, and some features such as GPS support may be lacking.
Moreover, and this is probably the biggest drawback: Open GL ES is only
parally supported. More specically, only Open GL ES 1 currently works on
the emulator.
Have a go hero
Now that you know how to install and update Android plaorm components and create an
emulator, try to create an emulator for Android Honeycomb Tablets. Using the Android SDK
and AVD Manager, you will need to do the following:
Install Honeycomb SDK components
Create a new AVD which targets Honeycomb plaorm
Start the emulator and use proper screen scaling to match real tablet scale
Depending on your computer resoluon, you may need to tweak AVD display scale. This
can be done by checking Scale display to real size when starng the emulator and entering
your monitor density (use the ? buon to calculate it). If you perform well, you should obtain
the new Honeycomb interface at its real scale (no worries, it is also in Landscape mode on
my computer):
Chapter 1
[ 37 ]
The following secon is dedicated to Windows and Mac OS
X. If you are a Linux user, you can immediately jump to the
Developing with an Android device on Linux secon.
Developing with an Android device on Windows and
Mac OS X
Emulators can be of really good help, but nothing compared to a real device. Hopefully,
Android provides the sucient connecvity to develop on a real device and make the tesng
cycle more ecient. So take your Android in hand, switch it on and lets try to connect it to
Windows or Mac OS X.
Time for action – setting up your Android device on
Windows and Mac OS X
Installaon of a device for development on Windows is manufacturer-specic. More
informaon can be found at http://developer.android.com/sdk/oem-usb.html
with a full list of device manufacturers. If you have got a driver CD with your Android device,
you can use it. Note that the Android SDK also contains some Windows drivers under
$ANDROID_SDK\extras\google\usb_driver. Specic instrucons are available for
Google development phones, Nexus One, and Nexus S at http://developer.android.
com/sdk/win-usb.html.
Seng Up your Environment
[ 38 ]
Mac users should also refer to their Manufacturers instrucons. However, as Mac’s ease of
use is not only a legend, simply connecng an Android device to a Mac should be enough to
get it working! Your device should be recognized immediately without installing anything.
Once the driver (if applicable) is installed on the system, do the following:
1. Go to the home menu, then go to Sengs | Applicaon | Development on your
mobile device (may change depending on your manufacturer).
2. Enable USB debugging and Stay awake.
3. Plug your device into your computer using a data connecon cable (beware some
cables are alimentaon cables only and will not work!). Depending on your device,
it may appear as a USB disk.
4. Launch Eclipse.
5. Open the DDMS perspecve. If working properly, your phone should be listed in the
Devices view:
6. Say cheese and take a screen capture of your own phone by clicking the
corresponding toolbar buon:
Now you are sure your phone is correctly connected!
What just happened?
We have connected an Android device to a computer in development mode and enabled
the Stay awake opon to stop automac screen shutdown when the phone is charging.
If your device is sll not working, go to the Trouble shoong a device connecon secon.
Chapter 1
[ 39 ]
The device and the computer communicate through an intermediate background service: the
Android Debug Bridge (ADB) (more about it in the next chapter). ADB starts automacally the
rst me it is called, when Eclipse ADT is launched or when invoked from the command line.
This is the end of the secon dedicated to Windows and Mac OS X.
If you are not a Linux user, you can jump to the Trouble shoong a
device connecon or the Summary secon.
Developing with an Android device on Linux
Emulators can be of really good help, but it is nothing compared to a real device.
Hopefully, Android provides the sucient connecvity to develop on a real device and
make the tesng cycle more ecient. So take your Android in hand, switch it on and lets
try to connect it to Linux.
Time for action – setting up your Android device on Ubuntu
1. Go to Home | Menu | Sengs | Applicaon | Development on your mobile device
(may change depending on your manufacturer).
2. Enable USB debugging and Stay awake.
3. Plugin your device to your computer using a data connecon cable (beware, some
cables are alimentaon cables only and will not work!). Depending on your device, it
may appear as a USB disk.
4. Try to run ADB and list devices. If you are lucky, your device works out of the box
and the list of devices appears. In that case, you can ignore the following steps:
$ adb devices
Seng Up your Environment
[ 40 ]
5. If ????????? appears instead of your device name (which is likely), then ADB does
not have proper access rights. We need to nd your Vendor ID and Product ID.
Because Vendor ID is a xed value for each manufacturer, you can nd it in the
following list:
Manufacturer USB Vendor ID
Acer 0502
Dell 413c
Foxconn 0489
Garmin-Asus 091E
HTC 0bb4
Huawei 12d1
Kyocera 0482
LG 1004
Motorola 22b8
Nvidia 0955
Pantech 10A9
Samsung 04e8
Sharp 04dd
Sony Ericsson 0fce
ZTE 19D2
The current list of Vendor IDs can be found on the Android website at http://
developer.android.com/guide/developing/device.html#VendorIds.
6. The device Product ID can be found using the lsusb command “greped” with Vendor
ID to nd it more easily. In the following example, the value 0bb4 is the HTC Vendor
ID and 0c87 is the HTC Desire product ID:
$ lsusb | grep 0bb4
Chapter 1
[ 41 ]
7. With the root user, create a le /etc/udev/rules.d/52-android.rules with
your Vendor and Product ID:
$ sudo sh -c ‘echo SUBSYSTEM==\”usb\”, SYSFS{idVendor}==\”<Your
Vendor ID>\”, ATTRS{idProduct}=\”<Your Product ID>\”,
MODE=\”0666\” > /etc/udev/rules.d/52-android.rules’
8. Change le rights to 644:
$ sudo chmod 644 /etc/udev/rules.d/52-android.rules
9. Restart the udev service (the Linux device manager):
$ sudo service udev restart
10. Relaunch the ADB server in the root mode this me:
$ sudo $ANDROID_SDK/tools/adb kill-server
$ sudo $ANDROID_SDK/tools/adb start-server
11. Check whether your device works by lisng the devices again. If ????????? appears,
or worse, nothing appears, then something went wrong in the previous steps:
$ adb devices
What just happened?
We have connected an Android device to a computer in development mode and enabled the
Stay awake opon to stop automac screen shutdown when the phone is charging. If your
device is sll not working, go to the Trouble shoong a device connecon secon.
We have also started the Android Debug Bridge (ADB), which is a background service used as
a mediator for computer/device communicaon (more about it in the next chapter). ADB is
started automacally the rst me it is called, when Eclipse ADT is launched or when invoked
from the command line.
And more important than anything, we have discovered that HTC means High Tech
Computer! Jokes apart, the connecon process can become tricky on Linux. If you belong to
the unlucky group of people who need to launch ADB as the root, you are highly advised to
create a startup script similar to the following one, to launch ADB. You can use it from the
command line or add it to your main menu (Menu | Preferences| Main Menu on Ubuntu):
#!bin/sh
stop_command=”$ANDROID_SDK/platform-tools/adb kill-server”
launch_command=”$ANDROID_SDK/platform-tools/adb start-server”
/usr/bin/gksudo “/bin/bash –c ‘$stop_command; $launch_command’” |
zenity –text-info –title Logs
Seng Up your Environment
[ 42 ]
This script displays daemon startup message in a Zenity window (a Shell toolkit to display
graphical windows using GTK+).
At step 6, if 52-android.rules does not work, then try 50-android.rules or
51-android.rules (or all of them). Although udev (the Linux device manager)
should only use the prex number to order rule les lexicographically, that
somemes seems to do the trick. The magic of Linux!
This is the end of the secon dedicated to Linux setup. The following secon
is mixed.
Troubleshooting a development device
Having trouble connecng an Android development device to a computer can mean any of
the following:
Your host system is not properly set up
Your development device is not working properly
The ADB service is malfunconing
If the problem comes from your host system, check your device manufacturer instrucons
carefully to make sure any needed driver is correctly installed. Check the Hardware
properes to see if it is recognized and turn on the USB storage mode (if applicable) to see
if it is working properly. Indeed, aer geng connected, your device may be visible in your
hardware sengs but not as a disk. A device can be congured as a Disk drive (if a SD-card
or similar is included) or in charge-only mode. This is absolutely ne as the development
mode works perfectly in the charge-only mode.
Disk-drive mode is generally acvated from the Android task bar (USB connected item).
Refer to your device documentaon for the specicies of your device.
Chapter 1
[ 43 ]
SD Card access
When the charge-only mode is acvated, SD card les and directories are
visible to the Android applicaons installed on your phone but not to your
computer. On the opposite side, when Disk drive mode is acvated, those
are visible only from your computer. Check your connecon mode when your
applicaon cannot access its resource les on a SD Card.
If problem comes from your Android device, a possible soluon is to deacvate and
reacvate the Debug mode on your device. This opon can be switched from the Home |
Menu | Sengs | Applicaon | Development screen on your mobile device (which may
change depending on your manufacturer) or accessed more quickly from the Android task
bar (USB debugging connected item). As a last measure, reboot your device.
Problem may also come from the ADB. In that case, check whether the ADB is working by
issuing the following command from a terminal prompt:
$ adb devices
If your device is correctly listed, then ADB is working. This command will launch ADB service
if it was not already. You can also restart it with commands:
$ adb kill-server
$ adb start-server
In any case, to solve a specic connecon problem or get up-to-date informaon, visit the
following web page: http://developer.android.com/guide/developing/device.
html. As a feedback from experience, never neglect hardware. Always check with a second
cable or device if you have one at your disposal. I once purchased a bad quality cable, which
performed badly when some contorons occurred...
Summary
Seng up our Android development plaorm is a bit tedious but is hopefully performed
once and for all! We have installed the necessary ulies using the package system on Linux,
Developer Tools on Mac OS X, and Cygwin on Windows. Then we have deployed the Java and
Android development kits and checked if they are working properly. Finally, we have seen how
to create a phone emulator and connect a real phone for test purposes.
We now have the necessary tools in our hands to shape our mobile ideas. In the next chapter,
we are going to handle them to create, compile, and deploy our rst Android projects!
2
Creating, Compiling, and
Deploying Native Projects
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), Acvity Manager (AM), ndk-build, and so
on. So, since our development environment is set up, we can now get our hands
dirty and start manipulang all these ulies to create, compile, and deploy
projects which include nave code.
In this second chapter, we are going to do the following:
Compile and deploy ocial sample applicaons from the Android NDK
with Ant build tool and nave code compiler ndk-build
Learn in more detail about ADB, the Android Debug Bridge, to control
a development device
Discover addional tools like AM to manage acvies and AAPT to
package applicaons
Create our rst own hybrid mul-language project using Eclipse
Interface Java to C/C++ through Java Nave Interfaces (in short JNI)
By the end of this chapter, you should know how to start up a new Android nave
project on your own.
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
Creang, Compiling, and Deploying Nave Projects
[ 46 ]
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 rst to see it
in acon? To get started, I propose to run HelloJni, a sample applicaon which retrieves a
character string dened inside a nave C library into a Java acvity (an acvity in Android
being more or less equivalent to an applicaon screen).
Time for action – compiling and deploying the hellojni sample
Let's compile and deploy the HelloJni project from command line using Ant:
1. Open a command-line prompt (or Cygwin prompt on Windows)
2. Go to hello-jni sample directory inside the Android NDK. All the following steps
have to performed from this directory:
$ cd $ANDROID_NDK/samples/hello-jni
3. Create Ant build le and all related conguraon les automacally using android
command (android.bat on Windows). These les describe how to compile and
package an Android applicaon:
android update project –p .
4. Build libhello-jni nave library with ndk-build, which is a wrapper Bash
script around Make. Command ndk-build sets up the compilaon toolchain for
nave C/C++ code and calls automacally GCC version featured with the NDK.
$ ndk-build
Chapter 2
[ 47 ]
5. Make sure your Android development device or emulator is connected and running.
6. Compile, package, and install the nal HelloJni APK (an Android applicaon
package). All these steps can be performed in one command, thanks to Ant build
automaon tool. Among other things, Ant runs javac to compile Java code, AAPT
to package the applicaon with its resources, and nally ADB to deploy it on the
development device. Following is only a paral extract of the output:
$ ant install
The result should look like the following extract:
Creang, Compiling, and Deploying Nave Projects
[ 48 ]
7. Launch a shell session using adb (or adb.exe on Windows). ADB shell is similar to
shells that can be found on the Linux systems:
$ adb shell
8. From this shell, launch HelloJni applicaon on your device or emulator. To do so, use
am, the Android Acvity Manager. Command am allows to start Android acvies,
services or sending intents (that is, inter-acvity messages) from command line.
Command parameters come from the Android manifest:
# am start -a android.intent.action.MAIN -n com.example.hellojni/
com.example.hellojni.HelloJni
9. Finally, look at your development device. HelloJni appears on the screen!
What just happened?
We have compiled, packaged, and deployed an ocial NDK sample applicaon with Ant and
SDK command-line tools. We will explore them more in later part. We have also compiled
our rst nave C library (also called module) using the ndk-build command. This library
simply returns a character string to the Java part of the applicaon on request. Both sides
of the applicaon, the nave and the Java one, communicate through Java Nave Interface.
JNI is a standard framework that allows Java code to explicitly call nave C/C++ code with a
dedicated API. We will see more about this at the end of this chapter and in the next one.
Finally, we have launched HelloJni on our device from an Android shell (adb shell) with
the am Acvity 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 Acvity class name concatenated to the main package.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.hellojni"
android:versionCode="1"
android:versionName="1.0">
Chapter 2
[ 49 ]
...
<activity android:name=".HelloJni"
android:label="@string/app_name">
...
Automated build
Because Android SDK, NDK, and their open source bricks are not bound to
Eclipse or any specic IDE, creang an automated build chain or seng up a
connuous integraon server becomes possible. A simple bash script with Ant
is enough to make it work!
HelloJni sample is a lile bit... let's say rusc! 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 compeon. It has been later ported to OpenGL ES and reused
as a sample demonstraon in several languages and systems, including Android. You can
nd more informaon by vising one of the author's page: http://jet.ro/visuals/4k-
intros/san-angeles-observation/.
Have a go hero – compiling san angeles OpenGL demo
To test this demo, you need to follow the same steps:
1. Go to the San Angeles sample directory.
2. Generate project les.
3. Compile and install the nal San Angeles applicaon.
4. Finally run it.
As this applicaon uses OpenGL ES 1, AVD emulaon will work, but may be somewhat slow!
You may encounter some errors while compiling the applicaon with Ant:
Creang, Compiling, and Deploying Nave Projects
[ 50 ]
The reason is simple: in res/layout/ directory, main.xml le is dened. This le usually
denes the main screen layout in Java applicaon—displayed components and how they are
organized. However, when Android 2.2 (API Level 8) was released, the layout_width and
layout_height enumeraons, which describe the way UI components should be sized,
were modied: FILL_PARENT became MATCH_PARENT. But San Angeles uses API Level 4.
There are basically two ways to overcome this problem. The rst one is selecng the right
Android version as the target. To do so, specify the target when creang Ant project les:
$ 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 eding default.properties at the project root
and replacing:
target=android-4
with the following line:
target=android-8
The second way is more straighorward: erase the main.xml le! Indeed, this le is in
fact not used by San Angeles demo, as only an OpenGL screen created programmacally
is displayed, without any UI components.
Target right!
When compiling an Android applicaon, always check carefully if you are
using the right target plaorm, as some features are added or updated
between Android versions. A target can also dramacally change your
audience wideness because of the mulple versions of Android in the wild...
Indeed, targets are moving a lot and fast on Android!
All these eorts are not in vain: it is just a pleasure to see this old-school 3D environment
full of at-shaded polygons running for the rst me. So just stop reading and run it!
Chapter 2
[ 51 ]
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.
Android debug bridge
You may have not noced it specically since the beginning but it has always been there,
over your shoulder. The Android Debug Bridge is a mulfaceted tool used as an intermediary
between development environment and emulators/devices. More specically, ADB is:
A background process running on emulators and devices to receive orders or
requests from an external computer.
A background server on your development computer communicang with
connected devices and emulators. When lisng devices, ADB server is involved.
When debugging, ADB server is involved. When any communicaon with a device
happens, ADB server is involved!
A client running on your development computer and communicang with devices
through ADB server. That is what we have done to launch HelloJni: we got connected
to our device using adb shell before issuing the required commands.
Creang, Compiling, and Deploying Nave Projects
[ 52 ]
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 specic commands are also provided such as:
logcat To display device log messages
dumpsys To dump system state
dmesg To dump kernel messages
ADB shell is a real Swiss Army knife. It also allows manipulang your device in a exible
way, especially with root access. For example, it becomes possible to observe applicaons
deployed in their "sandbox" (see directory /data/data) or to a list and kill currently
running processes.
ADB also oers other interesng opons; some of them are as follows:
pull <device path> <local path> To transfer a le to your computer
push <local path> <device path> To transfer a le to your device or emulator
install <application package> To install an applicaon package
install –r <package to reinstall> To reinstall an applicaon, if already deployed
devices To list all Android devices currently connected,
including emulators
reboot To restart an Android device programmacally
wait-for-device To sleep, unl a device or emulator is connected
to your computer (for example, in a script)
start-server To launch the ADB server communicang with
devices and emulators
kill-server To terminate the ADB server
bugreport To print the whole device state (like dumpsys)
help To get an exhausve help with all opons and
ags available
To ease the wring of issued command, ADB provides facultave ags to specify
before opons:
-s <device id> To target a specic 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)
Chapter 2
[ 53 ]
ADB client and its shell can be used for advanced manipulaon on the system, but most
of the me, it will not be necessary. ADB itself is generally used transparently. In addion,
without root access to your phone, possible acons are limited. For more informaon,
see http://developer.android.com/guide/developing/tools/adb.html.
Root or not root
If you know the Android ecosystem a bit, you may have heard about rooted
phones and non-rooted phones. Roong a phone means geng root access
to it, either "ocially" 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 (opmized
or modied, for example, CyanogenMod). You can also do any possible
(especially dangerous) manipulaons that an Administrator can do (for
example, deploying a custom kernel).
Roong is not an illegal operaon, as you are modifying YOUR device. But not
all manufacturers appreciate this pracce and usually void the warranty.
Have a go hero – transferring a le to SD card from command line
Using the informaon 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 manipulaon
using a shell prompt. I propose you to transfer a resource le 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:
1. Check if your device is available using adb from command line.
2. Connect to your device using the Android Debug Bridge shell prompt.
3. Check the content of your SD card using standard Unix ls command. Please note
that ls on Android has a specic behavior as it dierenates ls mydir from ls
mydir/, when mydir is a symbolic link.
4. Create a new directory on your SD card using the classic command mkdir.
5. Finally, transfer your le by issuing the appropriate adb command.
Creang, Compiling, and Deploying Nave Projects
[ 54 ]
Project conguration tool
The command named android is the main entry point when manipulang not only projects
but also AVDs and SDK updates (as seen in Chapter 1, Seng Up your Environment). There
are few opons available, which are as follows:
create project: This opon is used to create a new Android project
through command line. A few addional opons must be specied to allow
proper generaon:
-p The project path
-n The project name
-t The Android API target
-k The Java package, which contains applicaon's main class
-a The applicaon's main class name (Acvity in Android terms)
For example:
$ android create project –p ./MyProjectDir –n MyProject –t
android-8 –k com.mypackage –a MyActivity
update project: This is what we use to create Ant project les from an exisng
source. It can also be used to upgrade an exisng project to a new version. Main
parameters are as follows:
-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 relave to the project directory).
-t To change the Android API target
There are also opons 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
Chapter 2
[ 55 ]
Command android is a crucial tool to implement a connuous integraon toolchain
in order to compile, package, deploy, and test a project automacally enrely from
command line.
Have a go hero – towards continuous integration
With adb, android, and ant commands, you have enough knowledge to build a minimal
automac compilaon and deployment script to perform some connuous integraon. I
assume here that you have a versioning soware 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 operaons:
1. Create a new project by hand using android command.
2. Then, create a Unix or Cygwin shell script and assign it the necessary execuon
rights (chmod command). All the following steps have to be scribbled in it.
3. In the script, check out sources from your versioning system (for example, using
a svn checkout command) on disk. If you do not have a versioning system, you
can sll copy your own project directory using Unix commands.
4. Build the applicaon using ant.
Do not forget to check command results using $?. If the returned value
is dierent from 0, it means an error occurred. Addionally, you can use
grep or some custom tools to check potenal error messages.
5. If needed, you can deploy resources les using adb.
6. Install it on your device or on the emulator (which you can launch from the script)
using ant as shown previously.
7. You can even try to launch your applicaon automacally and check Android logs
(see logcat opon in adb). Of course, your applicaon needs to make use of logs!
A free monkey to test your App!
In order to automate UI tesng on an Android applicaon, an interesng ulity
that is provided with the Android SDK is MonkeyRunner, which can simulate
user acons on a device to perform some automated UI tesng. Have a look at
http://developer.android.com/guide/developing/tools/
monkeyrunner_concepts.html.
Creang, Compiling, and Deploying Nave Projects
[ 56 ]
To favor automaon, 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 \$?"
Redirecon is necessary to avoid pollung the standard output. The
escape character before $? is required to avoid early interpretaon by the
host shell.
Now you are fully prepared to automate your own build toolchain!
Creating your rst Android project using eclipse
In the rst part of the chapter, we have seen how to use Android command-line tools. But
developing with Notepad or VI is not really aracve. Coding should be fun! And to make
it so, we need our preferred IDE to perform boring or unpraccal tasks. So let's see now
how to create an Android project using Eclipse.
Eclipse views and perspecves
Several mes 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 somemes they are not. In that case, open them through
main menu: Window | Show View | Other….
Views in Eclipse are grouped in perspecves, which basically store your
workspace layout. They can be opened through main menu: Window | Open
Perspecve | Other…. Note that some contextual menus are available only in
some perspecves.
Time for action – initiating a Java project
1. Launch Eclipse.
2. In the main menu, select File | New | Project….
3. In the project wizard, select Android | Android Project and then Next.
Chapter 2
[ 57 ]
4. In the next screen, enter project properes:
In Project name, enter MyProject.
Select Create a new project in workspace.
Specify a new locaon if you want to, or keep the default locaon
(that is, your eclipse workspace locaon).
Set Build Target to Android 2.3.3.
In Applicaon name, enter (which can contain spaces): MyProject.
In Package name, enter com.myproject.
Create a new acvity with the name MyAcvity.
Set Min SDK Version to 10:
Creang, Compiling, and Deploying Nave Projects
[ 58 ]
5. Click on Finish. The project is created. Select it in Package Explorer view.
6. In the main menu, select Run | Debug As | Android Applicaon or click on
the Debug buon in the toolbar.
7. Select applicaon type Android Applicaon and click OK:
8. Your applicaon is launched, as shown in the following screenshot:
Chapter 2
[ 59 ]
What just happened?
We have created our rst Android project using Eclipse. In a few screens and clicks, we have
been able to launch the applicaon instead of wring long and verbose commands. Working
with an IDE like Eclipse really gives a huge producvity 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 me, just
recompiling the project makes this error disappear. But somemes, Eclipse
is recalcitrant and refuses to recompile projects. In that case, a lile-known
trick, which can be applied in many other cases, is to simply open the
Problems view, select these irritang messages, delete them without
mercy (Delete key or right-click and Delete) and nally recompile the
incriminated project.
As you can see, this project targets Android 2.3 Gingerbread because we will access latest
NDK features in the next chapters. However, you will need a proper device which hosts this
OS version else tesng will not be possible. If you cannot get one, then use the emulator set
up in Chapter 1, Seng Up your Environment.
If you look at the project source code, you will noce a Java le and no C/C++ les. Android
projects created with ADT are always Java projects. But thanks to Eclipse exibility, we can
turn them into C/C++ projects too; we are going to see this at the end of this chapter.
Avoiding space in le paths
When creang 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 specically GNU Make may not
really like it.
Introducing Dalvik
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 nave code!). It is at the core of any applicaons running on Android. Dalvik
is conceived to t the constrained requirements of mobile devices. It is specically opmized
to use less memory and CPU. It sits on top of the Android kernel which provides the rst
layer of abstracon over hardware (process management, memory management, and so on).
Creang, Compiling, and Deploying Nave Projects
[ 60 ]
Android has been designed with speed in mind. Because most users do not want to wait for
their applicaon to be loaded while others are sll running, the system is able to instanate
multple Dalvik VMs quickly, thanks to the Zygote process. Zygote, whose name comes from
the very rst 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
applicaons as well as a Dalvik instance. To launch a new applicaon, Zygote is simply forked
and the inial Dalvik instance is copied. Memory consumpon is lowered by sharing as many
libraries as possible between processes.
Dalvik operates on Android bytecode, which is dierent from Java bytecode. Bytecode is
stored in an opmized format called Dex generated by an Android SDK tool named dx. Dex
les are archived in the nal APK with the applicaon manifest and any nave libraries
or addional resources needed. Note that applicaons can get further opmized during
installaon 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 nave code! In this part, we are going to create C/C++ source les, compile
them into a nave library named mylib and let Java run this code.
Time for action – calling C code from Java
The nave library mylib that we are going to create will contain one simple nave method
getMyData() that returns a basic character string. First, let's write the Java code to declare
and run this method.
1. Open MyActivity.java. Inside main class, declare the nave method with the
native keyword and no method body:
public class MyActivity extends Activity {
public native String getMyData();
...
2. Then, load the nave library that contains this method within a stac inializaon
block. This block will be called before Activity instance gets inialized:
...
static {
System.loadLibrary("mylib");
}
...
Chapter 2
[ 61 ]
3. Finally, when Activity instance is created, call the nave method and update the
screen content with its return value. You can refer to the source code provided with
this book for the nal lisng:
...
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
setTitle(getMyData());
}
}
Now, let's prepare the project les required to build the nave code.
4. In Eclipse, create a new directory named jni at the project's root using menu
File | New | Folder.
5. Inside the jni directory, create a new le named Android.mk using menu
File | New | File. If CDT is properly installed, the le should have the following
specic icon in the Package Explorer view.
6. Write the following content into this le. Basically, this describes how to
compile our nave library named mylib which is composed of one source
le the com_myproject_MyActivity.c:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := mylib
LOCAL_SRC_FILES := com_myproject_MyActivity.c
include $(BUILD_SHARED_LIBRARY)
As project les for nave compilaon are ready, we can write the expected nave
source code. Although the C implementaon le must be wrien by hand, the
corresponding header le can be generated with a helper tool provided by the
JDK: javah.
7. In Eclipse, open Run | External Tools | External Tools Conguraons….
Creang, Compiling, and Deploying Nave Projects
[ 62 ]
8. Create a new program conguraon with the following parameters:
Name: MyProject javah.
Locaon refers to javah absolute path, which is OS-specic. In Windows, you
can enter ${env_var:JAVA_HOME}\bin\javah.exe. In Mac OS X and Linux,
it is usually /usr/bin/javah.
Working directory: ${workspace_loc:/MyProject/bin}.
Arguments: –d ${workspace_loc:/MyProject/jni} com.myproject.
MyActivity}.
In Mac OS X, Linux, and Cygwin, you can easily nd the locaon of
an executable available in $PATH, by using the which command.
For example,
$ which javah
9. On the Refresh tab, check Refresh resources upon compleon and select Specic
resources. Using the Specify Resources… buon, select the jni folder.
10. Finally, click on Run to save and execute javah. A new le com_myproject_
MyActivity.h is generated in the jni folder. It contains a prototype for the
method getMyData() expected on the Java side:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
...
JNIEXPORT jstring JNICALL Java_com_myproject_MyActivity_getMyData
(JNIEnv *, jobject);
...
11. We can now create com_myproject_MyActivity.c implementaon inside the
jni directory to return a raw character string. Method signature originates from the
generated header le:
#include "com_myproject_MyActivity.h"
JNIEXPORT jstring Java_com_myproject_MyActivity_getMyData
(JNIEnv* pEnv, jobject pThis)
{
return (*pEnv)->NewStringUTF(pEnv,
"My native project talks C++");
}
Chapter 2
[ 63 ]
Eclipse is not yet congured to compile nave code, only Java code. Unl we do that in
the last part of this chapter, we can try to build nave code by hand.
12. Open a terminal prompt and go inside the MyProject directory. Launch
compilaon of the nave library with the command ndk-build:
$ cd <your project directory>/MyProject
$ ndk-build
The nave library is compiled in the libs/armeabi directory and is named
libmylib.so. Temporary les generated during compilaon are located
in the obj/local directory.
13. From Eclipse, launch MyProject again. You should obtain following result:
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
Creang, Compiling, and Deploying Nave Projects
[ 64 ]
What just happened?
In the previous part, we created an Android Java project. In this second part, we have
interfaced Java code to a nave library compiled with the Android NDK from a C le. This
binding from Java to C allows retrieving through Java Nave Interfaces a simple Java string
allocated in the nave code. The example applicaon shows how Java and C/C++ can
cooperate together:
1. By creang UI components and code on the Java side and dening nave calls.
2. Using javah to generate header le with corresponding C/C++ prototypes.
3. Wring nave code to perform the expected operaon.
Nave 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 nave side. Only
their prototype needs to be dened. Nave methods can have parameters, a return value,
any visibility (private, protected, package protected or public) and can be stac, like
classic Java methods. Of course, they require the nave library with method implementaons
to be loaded before they are called. A way to do that is to invoke System.loadLibrary()
in a stac inializaon block, which is inialized when the containing class is loaded. Failure to
do so results in an excepon of type java.lang.UnsatisfiedLinkError, which is raised
when the nave method is invoked for the rst me.
Although it is not compulsory, javah tool provided by the JDK is extremely useful to
generate nave prototypes. Indeed, JNI convenon is tedious and error-prone. With
generated headers, you immediately know if a nave method expected by the Java side is
missing or has an incorrect signature. I encourage you to use javah systemacally in your
projects, more specically, each me nave method's signature is changed. JNI code is
generated from .class les, which means that your Java code must be rst compiled before
going through javah conversion. Implementaon needs to be provided in a separate C/C++
source le.
How to write JNI code on the nave side is explored in more details in the next chapter. But
remember that a very specic naming convenon, which is summarized by the following
paern, must be followed by nave side methods:
<returnType> Java_<com_mypackage>_<class>_<methodName> (JNIEnv* pEnv,
<parameters>...)
Nave method name is prexed with Java_ and the packages/class name (separated by _)
containing it separated. First argument is always of type JNIEnv (more on this in the next
chapter) and the preceding arguments are the actual parameters given to the Java method.
Chapter 2
[ 65 ]
More on Makeles
Nave library building process is orchestrated by a Makele named Android.mk. By
convenon, Android.mk is in folder jni, which is located inside the project's root. That
way, ndk-build command can nd this le automacally when the command is invoked.
Therefore, C/C++ code is by convenon also located in jni directory (but this can be
changed by conguraon).
Android Makeles are an essenal piece of the NDK building process. Thus, it is important
to understand the way they work to manage a project properly. An Android.mk le is
basically a "baking" le, which denes what to compile and how to compile. Conguraon
is performed using predened variables, among which are: LOCAL_PATH, LOCAL_MODULE
and LOCAL_SRC_FILES. See Chapter 9, Porng Exisng Libraries to Android, for more
explanaon on Makeles.
The Android.mk le presented in MyProject is a very simple Makele example. Each
instrucon serves a specic purpose:
LOCAL_PATH := $(call my-dir)
The preceding code indicates nave source les locaon. Instrucon $(call <function>)
allows evaluang a funcon and funcon my-dir returns the directory path of the last
executed Makele. Thus, as Makeles usually share their directory with source les, this line
is systemacally wrien at the beginning of each Android.mk le to nd their locaon.
include $(CLEAR_VARS)
Makes sure no "parasite" conguraon disrupts compilaon. When compiling an applicaon,
a few LOCAL_XXX variables need to be dened. The problem is that one module may dene
addional conguraon sengs (like a compilaon MACRO or a ag) through these variables,
which may not be needed by another module.
Keep your modules clean
To avoid any disrupon, all necessary LOCAL_XXX variables should be cleared
before any module is congured and compiled. Note that LOCAL_PATH is an
excepon to that rule and is never cleared out.
LOCAL_MODULE := mylib
The preceding line of code denes your module name. Aer compilaon, the output library
is named according to the LOCAL_MODULE variable anked by a lib prex and a .so sux.
This LOCAL_MODULE name is also used when a module depends on another module.
LOCAL_SRC_FILES := com_myproject_MyActivity.c
Creang, Compiling, and Deploying Nave Projects
[ 66 ]
The preceding line of code indicates which source les to compile. File path is expressed
relave to the LOCAL_PATH directory.
include $(BUILD_SHARED_LIBRARY)
This last instrucon nally launches the compilaon 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 stac libraries:
Shared libraries are a piece of executable loaded on demand. These are stored on
disk and loaded to memory as a whole. Only shared libraries can be loaded directly
from Java code.
Stac libraries are embedded in a shared library during compilaon. Binary code
is copied into a nal library, without regards to code duplicaon (if embedded by
several dierent modules).
In contrast with shared libraries, stac libraries can be stripped, which means that
unnecessary symbols (like a funcon which is never called from the embedding library) are
removed from the nal binary. They make shared libraries bigger but "all-inclusive", without
dependencies. This avoids the "DLL not found" syndrome well known on Window.
Shared vs. Stac modules
Whether you should use a stac or shared library depends on the context:
If a library is embedded in several other libraries
If almost all pieces of code are required to run
If a library needs to be selected dynamically at runme
then consider turning it into a shared library because they avoid memory
duplicaon (which is a very sensible issue on mobile devices).
On the other hand:
If it is used in one or only a few places
If only part of its code is necessary to run
If loading it at the beginning of your applicaon is not a concern
then consider turning it into a stac library instead. It can be reduced in size at
compilaon-me at the price of some possible duplicaon.
Chapter 2
[ 67 ]
Compiling native code from Eclipse
You probably agree with me, wring code in Eclipse but compiling it by hand is not very
sasfying. 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.
Time for action – creating a hybrid Java/C/C++ project
To check whether Eclipse compilaon works ne, let's introduce surrepously an error
inside the com_myproject_MyActivity.c le. For example:
#include "com_myproject_MyActivity.h"
private static final String = "An error here!";
JNIEXPORT jstring Java_com_myproject_MyActivity_getMyData
...
Now, let's compile MyProject with Eclipse:
1. Open menu File | New | Other....
2. Under C/C++, select Convert to a C/C++ Project and click on Next.
3. Check MyProject, choose MakeFile project and Other Toolchain and
nally click on Finish.
4. Open C/C++ perspecve when requested.
5. Right-click on MyProject in Project explorer view and select Properes.
Creang, Compiling, and Deploying Nave Projects
[ 68 ]
6. In the C/C++ Build secon, uncheck Use default build command and enter
ndk-build as a Build command. Validate by clicking on OK:
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:
Chapter 2
[ 69 ]
7. Let's x it by removing the incriminated line (underlined in red) and saving the le.
8. Sadly, the error is not gone. This is because auto-build mode does not work. Go back
to project properes, inside C/C++ Sengs and then the Behaviour tab. Check Build
on resource save and leave the value to all.
9. Go to the Builders secon and place CDT Builder right above Android Package
Builder. Validate.
10. Great! Error is gone. If you go to the Console view, you will see the result of
ndk-build execuon like if it was in command line. But now, we noce that
the include statement of jni.h le is underlined in yellow. This is because it
was not found by the CDT Indexer for code compleon. Note that the compiler
itself resolves them since there is no compilaon error. Indeed, the indexer is
not aware of NDK include paths, contrary to the NDK compiler
If warnings about the include le which the CDT Indexer could not nd do not
appear, go to C/C++ perspecve, 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.
11. Let's go back to project properes one last me. Go to secon C/C++ General/Paths
and Symbols and then in Includes tab.
12. Click on Add... and enter the path to the directory containing this include le which
is located inside NDK's platforms directory. In our case, we use Android 2.3.3 (API
level 9), so the path is ${env_var:ANDROID_NDK}/platforms/android-9/
arch-arm/usr/include. Environment variables are authorized and encouraged!
Check Add to all languages and validate:
Creang, Compiling, and Deploying Nave Projects
[ 70 ]
13. Because jni.h includes some "core" include les (for example, stdarg.h),
also add ${env_var:ANDROID_NDK}/toolchains/arm-linux-
androideabi-4.4.3/prebuilt/<your OS>/lib/gcc/arm-linux-
androideabi/4.4.3/include path and close the Properes window. When
Eclipse proposes to rebuild its index, say Yes.
14. Yellow lines are now gone. If you press Ctrl and click simultaneously on string.h,
the le gets automacally opened. Your project is now fully integrated in Eclipse.
What just happened?
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 properes, we managed to launch ndk-build command to produce
the library mylib dened in Android.mk. Aer geng compiled, this nave library is
packaged automacally into the nal Android applicaon by ADT.
Chapter 2
[ 71 ]
Running javah automacally while building
If you do not want to bother execung manually javah each me nave
methods changes, you can create an Eclipse builder:
1. Open your project Properes window and go to the Builder
secon.
2. Click on New… and create a new builder of type Program.
3. Enter conguraon like done at step 8 with the External tool
conguraon.
4. Validate and posion it aer Java Builder in the list (because
JNI les are generated from Java .class les).
5. Finally, move CDT Builder right aer this new builder (and
before Android Package Builder).
JNI header les will now be generated automacally each a me project is
compiled.
In step 8 and 9, we enabled Building on resource save opon. This allows automac
compilaon to occur without human intervenon, for example, when a save operaon is
triggered. This feature is really nice but can somemes 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 deacvate it temporarily or denitely as
soon as you are fed up!
Automac building
Build command invocaon is performed automacally when a le is saved.
This is praccal but can be resource and me consuming and can cause some
build cycle. That is why it is somemes appropriate to deacvate the Build
automacally opon from main menu through Project. A new buon:
appears in the toolbar to trigger a build manually. You can then re-enable
automac building.
Creang, Compiling, and Deploying Nave Projects
[ 72 ]
Summary
Although seng up, packaging, and deploying an applicaon project are not the most
excing tasks, but they cannot be avoided. Mastering them will allow being producve
and focused on the real objecve: producing code.
In this chapter, 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 connuous integraon in
your project. We have also seen how to make both Java and C/C++ talk together in a single
applicaon using JNI. Finally we have created a hybrid Java/C/C++ project using Eclipse to
develop more eciently.
With this rst experiment in mind, you got a good overview of how the NDK works. In the
next chapter, we are going to focus on code and discover in more detail the JNI protocol for
bidireconal Java to C/C++ communicaon.
3
Interfacing Java and C/C++ with JNI
Android is inseparable from Java. Although its kernel and its crical libraries
are nave, the Android applicaon framework is almost enrely wrien in Java
or wrapped inside a thin layer of Java. Obviously, a few libraries are directly
accessible from nave code, such as Open GL (as we will see in Chapter 6,
Rendering Graphics with OpenGL ES). However, most APIs are available only
from Java. Do not expect to build your Android GUI directly in C/C++. Technically
speaking, it is not yet possible to completely get rid of Java in an Android
applicaon. At best, we can hide it under the cover!
Thus, nave C/C++ code on Android would be nonsense if it is was not
possible to e Java and C/C++ together. This role is devoted to the Java Nave
Interface framework, which has been introduced in the previous chapter. JNI
is a specicaon standardized by Sun that is implemented by JVMs with two
purposes in mind: allowing Java to call nave code and nave code to call Java.
It is a two-way bridge between the Java and nave side and the only way to
inject the power of C/C++ into your Java applicaon.
Thanks to JNI, one can call C/C++ funcons from Java like any Java method,
passing Java primives or objects as parameters and receiving them as result.
In turn, nave code can access, inspect, modify, and call Java objects or raise
excepons with a reecon-like API. JNI is a subtle framework which requires
care as any misuse can result in a dramac ending…
Interfacing Java and C/C++ with JNI
[ 74 ]
In this chapter, we are going to learn how to do the following:
Pass and return Java primives, objects, and arrays to/from nave code
Handle Java objects references inside nave code
Raise excepons from nave code
JNI is a vast and highly technical subject, which could require a whole book to be covered
exhausvely. Instead, the present chapter focuses on the essenal knowledge to bridge
the gap between Java and C++.
Working with Java primitives
You are probably hungry to see more than the simple MyProject created in previous chapter:
passing parameters, retrieving results, raising excepons... to pursue this objecve, we will
see through this chapter how to implement a basic key/value store with various data types,
starng with primive types and strings.
A simple Java GUI will allow dening an “entry” composed of a key (a character string),
a type (an integer, a string, and so on), and a value related to the selected type. An entry
is inserted or updated inside the data store which will reside on the nave side (actually
a simple xed-size array of entries). Entries can be retrieved back by the Java client.
The following diagram presents an overall view of how the program will be structured:
Store Wrapper
Functions
Java
StoreActivity
<<user>>
int
StoreType
StoreType
StoreValue Internal Store
structure
<<Union>> StoreEntry
String
Store
Internal Store
Structure
1
<<user>>
1
1
1
1
C
*
com_packtpub_Store
Store
Chapter 3
[ 75 ]
The resulng project is provided with this book under the
name Store_Part3-1.
Time for action – building a native key/value store
Lets take care of the Java side rst:
1. Create a new hybrid Java/C++ project like shown in the previous chapter:
Name it Store.
Its main package is com.packtpub.
Its main acvity is StoreActivity.
Do not forget to create a jni directory at projects root.
Lets work on the Java side rst, which is going to contain three source les:
Store.java, StoreType.java, and StoreActivity.java.
2. Create a new class Store which loads the eponym nave library and denes the
funconalies our key/value store provides. Store is a front-end to our nave code.
To get started, it supports only integers and strings:
public class Store {
static {
System.loadLibrary(“store”);
}
public native int getInteger(String pKey);
public native void setInteger(String pKey, int pInt);
public native String getString(String pKey);
public native void setString(String pKey, String pString);
}
3. Create StoreType.java with an enumeraon specifying supported data types:
public enum StoreType {
Integer, String
}
Interfacing Java and C/C++ with JNI
[ 76 ]
4. Design a Java GUI in res/layout/main.xml similar to the following screenshot.
You can make use of the ADT Graphical Layout designer included in ADT or simply
copy it from project Store_Part3-1. GUI must allow dening an entry with a key
(TextView, id uiKeyEdit), a value (TextView, id uiValueEdit) and a type
(Spinner, id uiTypeSpinner). Entries can be saved or retrieved:
5. Applicaon GUI and Store need to be bound together. That is the role devoted to
the StoreActivity class. When acvity is created, set up GUI components: Type
spinner content is bound to the StoreType enum. Get Value and Set Value buons
trigger private methods onGetValue() and onSetValue() dened in the next
steps. Have a look at nal project Store_Part3-1 if you need some help.
Finally, inialize a new instance of the store:
public class StoreActivity extends Activity {
private EditText mUIKeyEdit, mUIValueEdit;
private Spinner mUITypeSpinner;
private Button mUIGetButton, mUISetButton;
private Store mStore;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Initializes components and binds buttons to handlers.
...
mStore = new Store();
}
Chapter 3
[ 77 ]
6. Dene method onGetValue(), which retrieves an entry from the store according
to type StoreType currently selected in the GUI:
private void onGetValue() {
String lKey = mUIKeyEdit.getText().toString();
StoreType lType = (StoreType) mUITypeSpinner
.getSelectedItem();
switch (lType) {
case Integer:
mUIValueEdit.setText(Integer.toString(mStore
.getInteger(lKey)));
break;
case String:
mUIValueEdit.setText(mStore.getString(lKey));
break;
}
}
7. Add method onSetValue() in StoreActivity to insert or update an entry into
the store. Entry data needs to be parsed according to its type. If value format is
incorrect, an Android Toast message is displayed:
...
private void onSetValue() {
String lKey = mUIKeyEdit.getText().toString();
String lValue = mUIValueEdit.getText().toString();
StoreType lType = (StoreType) mUITypeSpinner
.getSelectedItem();
try {
switch (lType) {
case Integer:
mStore.setInteger(lKey, Integer.parseInt(lValue));
break;
case String:
mStore.setString(lKey, lValue);
break;
}
} catch (NumberFormatException eNumberFormatException) {
displayError(“Incorrect value.”);
}
}
private void displayError(String pError) {
Toast.makeText(getApplicationContext(), pError,
Toast.LENGTH_LONG).show();
}
}
Interfacing Java and C/C++ with JNI
[ 78 ]
The Java side is ready and nave method prototypes dened. We can switch to the
nave side.
8. In the jni directory, create Store.h which denes store data structures. Create a
StoreType enumerate that matches exactly the Java enumeraon. Also create the
main structure Store, which contains a xed size array of entries. A StoreEntry is
composed of a key (a C string), a type, and a value. StoreValue is simply the union
of any of the possible values (that is, an integer or a C string pointer):
#ifndef _STORE_H_
#define _STORE_H_
#include “jni.h”
#include <stdint.h>
#define STORE_MAX_CAPACITY 16
typedef enum {
StoreType_Integer, StoreType_String
} StoreType;
typedef union {
int32_t mInteger;
char* mString;
} StoreValue;
typedef struct {
char* mKey;
StoreType mType;
StoreValue mValue;
} StoreEntry;
typedef struct {
StoreEntry mEntries[STORE_MAX_CAPACITY];
int32_t mLength;
} Store;
...
9. Terminate the Store.h le by declaring ulity methods to create, nd, and destroy
an entry. JNIEnv and jstring types are dened in header jni.h already included
in the previous step:
...
int32_t isEntryValid(JNIEnv* pEnv, StoreEntry* pEntry,
StoreType pType);
StoreEntry* allocateEntry(JNIEnv* pEnv, Store* pStore,
jstring pKey);
Chapter 3
[ 79 ]
StoreEntry* findEntry(JNIEnv* pEnv, Store* pStore, jstring pKey,
int32_t* pError);
void releaseEntryValue(JNIEnv* pEnv, StoreEntry* pEntry);
All these ulity methods are implemented in le jni/Store.c. First,
isEntryValid() simply checks an entry is allocated and has the expected type:
#include “Store.h”
#include <string.h>
int32_t isEntryValid(JNIEnv* pEnv, StoreEntry* pEntry,
StoreType pType) {
if ((pEntry != NULL) && (pEntry->mType == pType)) {
return 1;
}
return 0;
}
...
10. Method findEntry() compares the key passed as parameter with every entry
key currently stored unl it nds a matching one. Instead of working with classic C
strings, it receives directly a jstring parameter, which is the nave representaon
of a Java String.
A jstring cannot be manipulated directly in nave code. Indeed, Java and C
strings are completely dierent beasts. In Java, String is a real object with
member methods whereas in C, strings are raw character arrays.
To recover a C string from a Java String, one can use JNI API method
GetStringUTFChars() to get a temporary character buer. Its content can
then be manipulated using standard C rounes. GetStringUTFChars() must be
systemacally coupled with a call to ReleaseStringUTFChars() to release the
temporary buer:
...
StoreEntry* findEntry(JNIEnv* pEnv, Store* pStore, jstring pKey,
Int32_t* pError) {
StoreEntry* lEntry = pStore->mEntries;
StoreEntry* lEntryEnd = lEntry + pStore->mLength;
const char* lKeyTmp = (*pEnv)->GetStringUTFChars(pEnv, pKey,
NULL);
if (lKeyTmp == NULL) {
if (pError != NULL) {
*pError = 1;
}
Interfacing Java and C/C++ with JNI
[ 80 ]
return;
}
while ((lEntry < lEntryEnd)
&& (strcmp(lEntry->mKey, lKeyTmp) != 0)) {
++lEntry;
}
(*pEnv)->ReleaseStringUTFChars(pEnv, pKey, lKeyTmp);
return (lEntry == lEntryEnd) ? NULL : lEntry;
}
...
11. Sll in Store.c, implement allocateEntry() which either creates a new entry
(that is, increments store length and returns last array element) or returns an
exisng one (aer releasing its previous value) if key already exists. If entry is new,
convert the key to a C string kept in memory outside method scope. Indeed, raw JNI
objects live for the me of a method and cannot be kept outside its scope:
It is a good pracce to check that GetStringUTFChars() does not return
a NULL value which would indicate that the operaon has failed (for example,
if temporary buer cannot be allocated because of memory limitaons). This
should theorecally be checked for malloc too, although not done here for
simplicity purposes.
...
StoreEntry* allocateEntry(JNIEnv* pEnv, Store* pStore, jstring
pKey)
{
Int32_t lError = 0;
StoreEntry* lEntry = findEntry(pEnv, pStore, pKey, &lError);
if (lEntry != NULL) {
releaseEntryValue(pEnv, lEntry);
} else if (!lError) {
if (pStore->mLength >= STORE_MAX_CAPACITY) {
return NULL;
}
lEntry = pStore->mEntries + pStore->mLength;
const char* lKeyTmp = (*pEnv)->GetStringUTFChars
(pEnv, pKey, NULL);
if (lKeyTmp == NULL) {
return;
Chapter 3
[ 81 ]
}
lEntry->mKey = (char*) malloc(strlen(lKeyTmp));
strcpy(lEntry->mKey, lKeyTmp);
(*pEnv)->ReleaseStringUTFChars(pEnv, pKey, lKeyTmp);
++pStore->mLength;
}
return lEntry;
}
...
12. The last method of Store.c is releaseEntryValue(), which frees memory
allocated for a value if needed. Currently, only strings are dynamically allocated
and need to be freed:
...
void releaseEntryValue(JNIEnv* pEnv, StoreEntry* pEntry) {
int i;
switch (pEntry->mType) {
case StoreType_String:
free(pEntry->mValue.mString);
break;
}
}
#endif
13. Generate JNI header le for the class com.packtpub.Store with javah as
seen in Chapter 2, Creang, Compiling, and Deploying Nave Projects. A le jni/
com_packtpub_Store.h should be generated.
14. Now that our ulity methods and JNI header are generated, we need to write the
JNI source le com_packtpub_Store.c. The unique Store instance is saved in
a stac variable which is created when library is loaded:
#include “com_packtpub_Store.h”
#include “Store.h”
#include <stdint.h>
#include <string.h>
static Store gStore = { {}, 0 };
...
15. With the help of the generated JNI header, implement getInteger() and
setInteger() in com_packtpub_Store.c.
Interfacing Java and C/C++ with JNI
[ 82 ]
The rst method looks for the passed key in the store and returns its value (which
needs to be of type integer). If any problem happens, a default value is returned.
The second method allocates an entry (that is, creates a new entry in the store or
reuses an exisng one if it has the same key) and stores the new integer value in it.
Note here how mInteger, which is a C int, can be “casted” directly to a Java jint
primive and vice versa. They are in fact of the same type:
...
JNIEXPORT jint JNICALL Java_com_packtpub_Store_getInteger
(JNIEnv* pEnv, jobject pThis, jstring pKey) {
StoreEntry* lEntry = findEntry(pEnv, &gStore, pKey, NULL);
if (isEntryValid(pEnv, lEntry, StoreType_Integer)) {
return lEntry->mValue.mInteger;
} else {
return 0.0f;
}
}
JNIEXPORT void JNICALL Java_com_packtpub_Store_setInteger
(JNIEnv* pEnv, jobject pThis, jstring pKey, jint pInteger) {
StoreEntry* lEntry = allocateEntry(pEnv, &gStore, pKey);
if (lEntry != NULL) {
lEntry->mType = StoreType_Integer;
lEntry->mValue.mInteger = pInteger;
}
}
...
16. Strings have to be handled with more care. Java strings are not real primives.
Types jstring and char* cannot be used interchangeably as seen in step 11.
To create a Java String object from a C string, use NewStringUTF().
In second method setString(), convert a Java string into a C string with
GetStringUTFChars() and SetStringUTFChars() as seen previously.
...
JNIEXPORT jstring JNICALL Java_com_packtpub_Store_getString
(JNIEnv* pEnv, jobject pThis, jstring pKey) {
StoreEntry* lEntry = findEntry(pEnv, &gStore, pKey, NULL);
if (isEntryValid(pEnv, lEntry, StoreType_String)) {
return (*pEnv)->NewStringUTF(pEnv, lEntry->mValue.mString);
}
else {
return NULL;
}
}
Chapter 3
[ 83 ]
JNIEXPORT void JNICALL Java_com_packtpub_Store_setString
(JNIEnv* pEnv, jobject pThis, jstring pKey, jstring pString) {
const char* lStringTmp = (*pEnv)->GetStringUTFChars(pEnv,
pString, NULL);
if (lStringTmp == NULL) {
return;
}
StoreEntry* lEntry = allocateEntry(pEnv, &gStore, pKey);
if (lEntry != NULL) {
lEntry->mType = StoreType_String;
jsize lStringLength = (*pEnv)->GetStringUTFLength(pEnv,
pString);
lEntry->mValue.mString =
(char*) malloc(sizeof(char) * (lStringLength + 1));
strcpy(lEntry->mValue.mString, lStringTmp);
}
(*pEnv)->ReleaseStringUTFChars(pEnv, pString, lStringTmp);
}
17. Finally, write the Android.mk le as follows. Library name is store and the two C
les are listed. To compile C code, run ndk-build inside projects root:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_CFLAGS := -DHAVE_INTTYPES_H
LOCAL_MODULE := store
LOCAL_SRC_FILES := com_packtpub_Store.c Store.c
include $(BUILD_SHARED_LIBRARY)
What just happened?
Run the applicaon, save a few entries with dierent keys, types, and values and try to get
them back from the nave store. We have managed to pass and retrieve int primives and
strings from Java to C. These values are saved in a data store indexed by a string key. Entries
can be retrieved from the store with respect to their key and type.
Interfacing Java and C/C++ with JNI
[ 84 ]
Integer primives wear several dresses during nave calls: rst an int in Java code, then
a jint during transfer from/to Java code and nally an int/int32_t in nave code.
Obviously, we could have kept the JNI representaon jint in nave code since both types
are equivalent.
Type int32_t is a typedef refering to int introduced by the C99 standard
library with the aim at more portability. More numeric types are available in
stdint.h. to force their use in JNI, declare -DHAVE_INTTYPES_H macro
in Android.mk.
More generally, primive types have all their proper representaons:
Java type JNI type C type Stdint C type
boolean Jboolean unsigned char uint8_t
byte Jbyte signed char int8_t
char Jchar unsigned short uint16_t
double Jdouble double double
oat joat oat oat
int jint Int int32_t
long jlong long long int64_t
short jshort Short int16_t
On the other hand, Java strings need a concrete conversion to C strings to allow processing
using standard C string rounes. Indeed, jstring is not a representaon of a classic char*
array but of a reference to a Java String object, accessible from Java code only.
Conversion is performed with JNI method GetStringUTFChars() which must match with
a call to ReleaseStringUTFChars(). Internally, this conversion allocates a new string
buer. The resulng C string is encoded in modied UTF-8 format (a slightly dierent avor
of UTF-8) that allows processing with standard C roune. Modied UTF-8 can represent
standard ASCII characters (that is, on one byte) and can grow to several bytes for extended
characters. This format is dierent than Java string, which uses an UTF-16 representaon
(which explains why Java characters are 16-bit, as shown in the preceding table). To avoid an
internal conversion when geng nave strings, JNI also provides GetStringChars() and
ReleaseStringChars(), which returns an UTF-16 representaon instead. This format is
not zero-terminated like classic C strings. Thus, it is compulsory to use them in conjuncon
with GetStringLength() (whereas GetStringUTFLength() can be replaced by a classic
strlen() with modied UTF-8).
Chapter 3
[ 85 ]
See JNI specicaon at http://java.sun.com/docs/books/jni/html/jniTOC.
html for more details on the subject. Refer to http://java.sun.com/docs/books/
jni/html/types.html for details to know more about JNI types and to http://java.
sun.com/developer/technicalArticles/Intl/Supplementary for an interesng
discussion about strings in Java.
Have a go hero – passing and returning other primitive types
The current store deals only with integers and strings. Based on this model, try to implement
store methods for other primive types: boolean, byte, char, double, float, long,
and short.
Project Store_Part3-Final provided with this book implements these cases.
Referencing Java objects from native code
We know from a previous part that a string is represented in JNI as a jstring, which is in
fact a Java object which means that it is possible to exchange Java objects through JNI! But
because nave code cannot understand or access Java directly, all Java objects have the
same representaon: a jobject.
In this part, we are going to focus on how to save an object on the nave side and how
to send it back to Java. In the next project, we are going to work with colors, although any
other type of object would work.
Project Store_Part3-1 can be used as a starng point for this part. The
resulng project is provided with this book under the name Store_Part3-2.
Time for action – saving a reference to an object in the Store
First, lets append the Color data type to the Java client:
1. In package com.packtpub, create a new class Color that contains an integer
representaon of a color. This integer is parsed from a String (HTML codes such
as #FF0000) thanks to the Android android.graphics.Color class:
public class Color {
private int mColor;
Interfacing Java and C/C++ with JNI
[ 86 ]
public Color(String pColor) {
super();
mColor = android.graphics.Color.parseColor(pColor);
}
@Override
public String toString() {
return String.format(“#%06X”, mColor);
}
}
2. Change StoreType enumeraon to include the new Color data type:
public enum StoreType {
Integer, String, Color
}
3. Open the Store.java le created in the previous part and add two new methods
to retrieve and save a Color object in the nave store:
public class Store {
static {
System.loadLibrary(“store”);
}
...
public native Color getColor(String pKey);
public native void setColor(String pKey, Color pColor);
}
4. Open the exisng le StoreActivity.java and update methods onGetValue()
and onSetValue() to display and parse Color instances. Note that color parsing
can generate an IllegalArgumentException if color code is incorrect:
public class StoreActivity extends Activity {
...
private void onGetValue() {
String lKey = mUIKeyEdit.getText().toString();
StoreType lType = (StoreType) mUITypeSpinner
.getSelectedItem();
switch (lType) {
...
case Color:
mUIValueEdit.setText(mStore.getColor(lKey).toString());
break;
}
}
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
Chapter 3
[ 87 ]
private void onSetValue() {
String lKey = mUIKeyEdit.getText().toString();
String lValue = mUIValueEdit.getText().toString();
StoreType lType = (StoreType) mUITypeSpinner
.getSelectedItem();
try {
switch (lType) {
...
case Color:
mStore.setColor(lKey, new Color(lValue));
break;
}
}
catch (NumberFormatException eNumberFormatException) {
displayError(“Incorrect value.”);
} catch (IllegalArgumentException eIllegalArgumentException)
{
displayError(“Incorrect value.”);
}
}
...
}
The Java side is now ready. Lets write the necessary code to retrieve and store a
Color entry inside nave code.
5. In jni/Store.h, append the new color type to the StoreType enumeraon and
add a new member to the StoreValue union. But what type to use, since Color
is an object known only from Java? In JNI, all java objects have the same type:
jobject, an (indirect) object reference:
...
typedef enum {
StoreType_Integer, StoreType_String, StoreType_Color
} StoreType;
typedef union {
int32_t mInteger;
char* mString;
jobject mColor;
} StoreValue;
...
Interfacing Java and C/C++ with JNI
[ 88 ]
6. Re-generate JNI header le jni/com_packtpub_Store.h with javah.
7. Two new method prototypes getColor() and setColor() have been freshly
generated. We have to implement them. First one simply returns the Java Color
object kept in the store entry. No dicules here.
The real subtlees are introduced in the second method setColor(). Indeed, at
rst sight, simply saving the jobject value in the store entry would seem sucient.
But this assumpon is wrong. Objects passed in parameters or created inside a JNI
method are local references. Local references cannot be kept in nave code outside
method scope.
To be allowed to keep a Java object reference in nave code aer method returns,
they must be turned into global references to inform the Dalvik VM that they
cannot be garbage collected. To do so, JNI API provides NewGlobalRef()and
its counterpart DeleteGlobalRef(). Here, global reference is deleted if entry
allocaon fails:
#include “com_packtpub_Store.h”
#include “Store.h”
...
JNIEXPORT jobject JNICALL Java_com_packtpub_Store_getColor
(JNIEnv* pEnv, jobject pThis, jstring pKey) {
StoreEntry* lEntry = findEntry(pEnv, &gStore, pKey, NULL);
if (isEntryValid(pEnv, lEntry, StoreType_Color)) {
return lEntry->mValue.mColor;
} else {
return NULL;
}
}
JNIEXPORT void JNICALL Java_com_packtpub_Store_setColor
(JNIEnv* pEnv, jobject pThis, jstring pKey, jobject pColor) {
jobject lColor = (*pEnv)->NewGlobalRef(pEnv, pColor);
if (lColor == NULL) {
return;
}
StoreEntry* lEntry = allocateEntry(pEnv, &gStore, pKey);
if (lEntry != NULL) {
lEntry->mType = StoreType_Color;
lEntry->mValue.mColor = lColor;
} else {
(*pEnv)->DeleteGlobalRef(pEnv, lColor);
}
}
...
Chapter 3
[ 89 ]
8. A call to NewGlobalRef() must always match with a call to DeleteGlobalRef().
In our example, global reference should be deleted when entry is replaced
by a new one (removal is not implemented). Do it in Store.c by updang
releaseEntryValue():
...
void releaseEntryValue(JNIEnv* pEnv, StoreEntry* pEntry) {
switch (pEntry->mType) {
...
case StoreType_Color:
(*pEnv)->DeleteGlobalRef(pEnv, pEntry->mValue.mColor);
break;
}
}
What just happened?
Run the applicaon, enter and save a color value such as #FF0000 or red (which is a
predened value allowed by the Android color parser) and get the entry back from the store.
We have managed to store a Java object on the nave side.
All objects coming from Java are represented by a jobject. Even jstring, which is in fact
a typedef over jobject, can be used as such. Because nave code invocaon is limited to
method boundaries, JNI keeps object references local to this method by default. This means
that a jobject can only be used safely inside the method it was transmied to. Indeed, the
Dalvik VM is in charge of invoking nave methods and can manage Java object references
before and aer method is run. But a jobject is just a “pointer” without any smart or
garbage collecon mechanism (aer all, we want to get rid of Java, at least parally). Once
nave method returns, the Dalvik VM has no way to know if nave code sll holds object
references and can decide to collect them at any me.
Global references are also the only way to share variables between threads
because JNI contexts are always thread local.
To be able to use an object reference outside its scope, reference must be made global with
NewGlobalRef() and “unreferenced” with DeleteGlobalRef(). Without the laer, the
Dalvik VM would consider objects to sll be referenced and would never collect them.
Have a look at JNI specicaon at http://java.sun.com/docs/books/jni/html/
jniTOC.html for more informaon on the subject.
Interfacing Java and C/C++ with JNI
[ 90 ]
Local and global JNI references
When geng an object reference from JNI, this reference is said to be Local. It is
automacally freed (the reference not the object) when nave method returns to
allow proper garbage collecon later in the Java code. Thus, by default, an object
reference cannot be kept outside the lifeme of a nave call. For example:
static jobject gMyReference;
JNIEXPORT void JNICALL Java_MyClass_myMethod(JNIEnv* pEnv,
jobject pThis, jobject pRef) {
gMyReference = pRef;
}
The piece of code above should be strictly prohibited. Keeping such a reference outside
JNI method will eventually lead to a disaster (memory corrupon or a crash).
Local references can be deleted when they are no longer used:
pEnv->DeleteLocalRef(lReference);
A JVM is required to store at least 16 references at the same me and can refuse to
create more. To do so, explicitly inform it, for example:
pEnv->EnsureLocalCapacity(30)
It is a rather good pracce to eliminate references when they are no longer
needed. There are two benets to act as such:
Because the number of local references in a method is nite. When a piece
of code contains and manipulates many objects such as an array, keep your
number of simultaneous local references low by deleng them as soon as
possible.
Because released local references can be garbage collected immediately and
memory freed if no other references exist.
To keep object references for a longer period of me, one needs to create a global reference:
JNIEXPORT void JNICALL Java_MyClass_myStartMethod (JNIEnv* pEnv,
jobject pThis, jobject pRef) {
...
gMyReference = pEnv->NewGlobalRef(pEnv, pRef<);
...
}
Chapter 3
[ 91 ]
And then delete it for proper garbage collecon:
JNIEXPORT void JNICALL Java_MyClass_myEndMethod (JNIEnv* pEnv,
jobject pThis, jobject pRef) {
...
gMyReference = pEnv->DeleteGlobalRef(gMyReference)
...
}
Global reference can now be safely shared between two dierent JNI calls or threads.
Throwing exceptions from native code
Error handling in the Store project is not really sasfying. If the requested key cannot
be found or if the retrieved value type does not match the requested type, a default
value is returned. We denitely need a way to indicate an error happened! And what
beer (note that I do not say faster...) to indicate an error than an excepon?
Store Wrapper
Functions
int
Color
StoreActivity
<<user>>
StoreType
StoreType
StoreValue Internal Store
structure
<<Union>> StoreEntry
String
Store
Internal Store
Structure
1
<<user>>
1
1
1
1
Java
C
*
com_packtpub_Store
Store
InvalidTypeException
NotExistingKeyException
StoreFullException
<<throws>>
Project Store_Part3-2 can be used as a starng point for this part. The
resulng project is provided with this book under the name Store_Part3-3.
Interfacing Java and C/C++ with JNI
[ 92 ]
Time for action – raising exceptions from the Store
Lets start by creang and catching excepons on the Java side:
1. Create a new excepon class InvalidTypeException of type Exception in
package com.packtpub.exception as follows:
public class InvalidTypeException extends Exception {
public InvalidTypeException(String pDetailMessage) {
super(pDetailMessage);
}
}
2. Repeat the operaon for two other excepons: NotExistingKeyException of
type Exception and StoreFullException of type RuntimeException instead.
3. Open exisng le Store.java and declare thrown excepons on geer
prototypes only (StoreFullException is a RuntimeException and does
not need declaraon):
public class Store {
static {
System.loadLibrary(“store”);
}
public native int getInteger(String pKey)
throws NotExistingKeyException, InvalidTypeException;
public native void setInteger(String pKey, int pInt);
public native String getString(String pKey)
throws NotExistingKeyException, InvalidTypeException;
public native void setString(String pKey, String pString);
public native Color getColor(String pKey)
throws NotExistingKeyException, InvalidTypeException;
public native void setColor(String pKey, Color pColor);
}
4. Excepons need to be caught. Catch NotExistingKeyException and
InvalidTypeException in onGetValue(). Catch StoreFullException in
onSetValue() in case entry cannot be inserted:
public class StoreActivity extends Activity {
...
private void onGetValue() {
Chapter 3
[ 93 ]
String lKey = mUIKeyEdit.getText().toString();
StoreType lType = (StoreType) mUITypeSpinner
.getSelectedItem();
try {
switch (lType) {
...
}
}
catch (NotExistingKeyException eNotExistingKeyException) {
displayError(“Key does not exist in store”);
} catch (InvalidTypeException eInvalidTypeException) {
displayError(“Incorrect type.”);
}
}
private void onSetValue() {
String lKey = mUIKeyEdit.getText().toString();
String lValue = mUIValueEdit.getText().toString();
StoreType lType = (StoreType) mUITypeSpinner
.getSelectedItem();
try {
switch (lType) {
...
}
}
catch (NumberFormatException eNumberFormatException) {
displayError(“Incorrect value.”);
} catch (IllegalArgumentException eIllegalArgumentException)
{
displayError(“Incorrect value.”);
} catch (StoreFullException eStoreFullException) {
displayError(“Store is full.”);
}
}
...
}
Lets throw these excepons from nave code. As excepons are not part of the
C language, JNI excepons cannot be declared on C method prototypes (the same
goes for C++ which has a dierent excepon model than Java). Thus, there is no
need to re-generate JNI header.
Interfacing Java and C/C++ with JNI
[ 94 ]
5. Open jni/Store.h created in previous parts and dene three new helper methods
to throw excepons:
#ifndef _STORE_H_
#define _STORE_H_
...
void throwInvalidTypeException(JNIEnv* pEnv);
void throwNotExistingKeyException(JNIEnv* pEnv);
void throwStoreFullException(JNIEnv* pEnv);
#endif
6. NotExistingKeyException and InvalidTypeException are only thrown
when geng a value from the store. A good place to raise them is when checking an
entry with isEntryValid(). Open and change the jni/Store.c le accordingly:
#include “Store.h”
#include <string.h>
int32_t isEntryValid(JNIEnv* pEnv, StoreEntry* pEntry,
StoreType pType) {
if (pEntry == NULL) {
throwNotExistingKeyException(pEnv);
} else if (pEntry->mType != pType) {
throwInvalidTypeException(pEnv);
} else {
return 1;
}
return 0;
}
...
7. StoreFullException is obviously raised when a new entry is inserted. Modify
allocateEntry()in the same le to check entry inserons:
...
StoreEntry* allocateEntry(JNIEnv* pEnv, Store* pStore, jstring
pKey){
StoreEntry* lEntry = findEntry(pEnv, pStore, pKey);
if (lEntry != NULL) {
releaseEntryValue(pEnv, lEntry);
} else {
if (pStore->mLength >= STORE_MAX_CAPACITY) {
throwStoreFullException(pEnv);
return NULL;
}
Chapter 3
[ 95 ]
// Initializes and insert the new entry.
...
}
return lEntry;
}
...
8. We must implement throwNotExistingException(). To throw a Java excepon,
the rst task is to nd the corresponding class (like with the Java Reecon API).
A Java class reference is represented in JNI with the specic type jclass. Then,
raise the excepon with ThrowNew(). Once we no longer need the excepon class
reference, we can get rid of it with DeleteLocalRef():
...
void throwNotExistingKeyException(JNIEnv* pEnv) {
jclass lClass = (*pEnv)->FindClass(pEnv,
“com/packtpub/exception/NotExistingKeyException”);
if (lClass != NULL) {
(*pEnv)->ThrowNew(pEnv, lClass, “Key does not exist.”);
}
(*pEnv)->DeleteLocalRef(pEnv, lClass);
}
9. Repeat the operaon for the two other excepons. The code is idencal (even to
throw a runme excepon), only the class name changes.
What just happened?
Launch the applicaon and try to get an entry with a non-exisng key. Repeat the operaon
with an entry which exists in the store but with a dierent type then the one selected in the
GUI. In both cases, there is an error message because of the raised excepon. Try to save
more than 16 references in the store and you will get an error again.
Raising excepon is a not a complex task. In addion, it is a good introducon to the Java
call-back mechanism provided by JNI. An excepon is instanated with a class descriptor of
type jclass (which is also a jobject behind the scenes). Class descriptor is searched in
the current class loader according to its complete name (package path included).
Do not forget about return codes
FindClass() and JNI methods in general can fail for several reasons (not
enough memory is available, class not found, and so on). Thus checking their
result is highly advised.
Interfacing Java and C/C++ with JNI
[ 96 ]
Once an excepon is raised, do not make further call to JNI except cleaning methods
(DeleteLocalRef(), DeleteGlobalRef(), and so on). Nave code should clean its
resources and give control back to Java, although it is possible to connue “pure” nave
processing if no Java is invoked. When nave method returns, excepon is propagated
by the VM to Java.
We have also deleted a local reference, the one poinng to the class descriptor because
it was not needed any more aer its use (step 8). When JNI lends you something, do not
forget to give it back!
JNI in C++
C is not an object-oriented language but C++ is. This is why you do not write JNI in C like
in C++.
In C, JNIEnv is in fact a structure containing funcon pointer. Of course, when a JNIEnv is
given to you, all these pointers are inialized so that you can call them a bit like an object.
However, the this parameter, which is implicit in an object-oriented language, is given as
rst parameter in C (pJNIEnv in the following code). Also, JNIEnv needs to be dereferenced
the rst me to run a method:
jclass ClassContext = (*pJNIEnv)->FindClass(pJNIEnv,
“android/content/Context”);
C++ code is more natural and simple. The this parameter is implicit and there is no need to
dereference JNIEnv, as methods are not declared as funcon pointer anymore but as real
member methods:
jclass ClassContext = lJNIEnv->FindClass(
“android/content/Context”);
Handling Java arrays
There is one type we have not talked about yet: arrays. Arrays have a specic place in JNI
like in Java. They have their proper types and their proper API, although Java arrays are also
objects at their root. Lets improve the Store project by leng users enter a set of values
simultaneously in an entry. Then, this set is going to be communicated to the nave backend
in a Java array which is then going to be stored as a classic C array.
Project Store_Part3-3 can be used as a starng point for this part. The
resulng project is provided with this book under the name Store_Part3-4.
Chapter 3
[ 97 ]
Time for action – saving a reference to an object in the Store
Lets start again with the Java code:
1. To help us handling operaons on arrays, lets download a helper library: Google
Guava (release r09 in this book) at http://code.google.com/p/guava-
libraries. Guava oers many useful methods to deal primives and arrays and
perform “pseudo-funconal” programming. Copy guava-r09 jar contained in
the downloaded ZIP in libs.
2. Open project Properties and go to the Java Build Path secon. In the
Libraries tab, reference Guava jar by clicking on the Add JARs... buon. Validate.
3. Edit StoreType enumeraon iniated in previous parts and add two new values
the IntegerArray and ColorArray:
public enum StoreType {
Integer, String, Color,
IntegerArray, ColorArray
}
4. Open Store.java and add new methods to retrieve and save int and Color
arrays:
public class Store {
static {
System.loadLibrary(“store”);
}
...
public native int[] getIntegerArray(String pKey)
throws NotExistingKeyException;
public native void setIntegerArray(String pKey,
int[] pIntArray);
public native Color[] getColorArray(String pKey)
throws NotExistingKeyException;
public native void setColorArray(String pKey,
Color[] pColorArray);
}
Interfacing Java and C/C++ with JNI
[ 98 ]
5. Finally, connect nave methods to the GUI in le StoreActivity.java. First,
onGetValue() retrieves an array from the store, concatenates its values with a
semicolon separator thanks to Guava joiners (more informaon can be found in
Guava Javadoc at http://guava-libraries.googlecode.com/svn) and
nally displays them:
public class StoreActivity extends Activity {
...
private void onGetValue() {
String lKey = mUIKeyEdit.getText().toString();
StoreType lType = (StoreType) mUITypeSpinner
.getSelectedItem();
try {
switch (lType) {
...
case IntegerArray:
mUIValueEdit.setText(Ints.join(“;”,
mStore.getIntegerArray(lKey)));
break;
case ColorArray:
mUIValueEdit.setText(Joiner.on(“;”).join(
mStore.getColorArray(lKey)));
break;
}
}
catch (NotExistingKeyException eNotExistingKeyException) {
displayError(“Key does not exist in store”);
} catch (InvalidTypeException eInvalidTypeException) {
displayError(“Incorrect type.”);
}
}
...
6. In StoreActivity.java, improve onSetValue() to convert a list of user
entered values into an array before sending it to the Store. Use the Guava
transformaon feature to accomplish this task: a Function object (or functor)
converng a string value into the target type is passed to the helper method
stringToList(). The laer splits the user string on the semicolon separator
before running transformaons:
...
private void onSetValue() {
String lKey = mUIKeyEdit.getText().toString();
Chapter 3
[ 99 ]
String lValue = mUIValueEdit.getText().toString();
StoreType lType = (StoreType) mUITypeSpinner
.getSelectedItem();
try {
switch (lType) {
...
case IntegerArray:
mStore.setIntegerArray(lKey,
Ints.toArray(stringToList(
new Function<String, Integer>() {
public Integer apply(String pSubValue) {
return Integer.parseInt(pSubValue);
}
}, lValue)));
break;
case ColorArray:
List<Color> lIdList = stringToList(
new Function<String, Color>() {
public Color apply(String pSubValue) {
return new Color(pSubValue);
}
}, lValue);
Color[] lIdArray = lIdList.toArray(
new Color[lIdList.size()]);
mStore.setColorArray(lKey, lIdArray);
break;
}
}
catch (NumberFormatException eNumberFormatException) {
displayError(“Incorrect value.”);
} catch (IllegalArgumentException eIllegalArgumentException)
{
displayError(“Incorrect value.”);
} catch (StoreFullException eStoreFullException) {
displayError(“Store is full.”);
}
}
private <TType> List<TType> stringToList(
Function<String, TType> pConversion,
String pValue) {
String[] lSplitArray = pValue.split(“;”);
List<String> lSplitList = Arrays.asList(lSplitArray);
return Lists.transform(lSplitList, pConversion);
}
}
Switch back to the nave side.
Interfacing Java and C/C++ with JNI
[ 100 ]
7. In jni/Store.h, add the new array types to the enumeraon StoreType.
Also declare two new elds mIntegerArray and mColorArray in StoreValue
union. Store arrays are represented as raw C arrays (that is, a pointer).
We also need to remember the length of these arrays. Put this informaon in a new
eld mLength in StoreEntry.
#ifndef _STORE_H_
#define _STORE_H_
#include “jni.h”
#include <stdint.h>
#define STORE_MAX_CAPACITY 16
typedef enum {
StoreType_Integer, StoreType_String, StoreType_Color,
StoreType_IntegerArray, StoreType_ColorArray
} StoreType;
typedef union {
int32_t mInteger;
char* mString;
jobject mColor;
int32_t* mIntegerArray;
jobject* mColorArray;
} StoreValue;
typedef struct {
char* mKey;
StoreType mType;
StoreValue mValue;
int32_t mLength;
} StoreEntry;
...
8. Open jni/Store.c and insert new cases in releaseEntryValue() for
arrays. Array allocated memory has to be freed when corresponding entry is
released. As colors are Java objects, delete global references or garbage
collecon will never happen:
...
void releaseEntryValue(JNIEnv* pEnv, StoreEntry* pEntry) {
int32_t i;
switch (pEntry->mType) {
Chapter 3
[ 101 ]
...
case StoreType_IntegerArray:
free(pEntry->mValue.mIntegerArray);
break;
case StoreType_ColorArray:
for (i = 0; i < pEntry->mLength; ++i) {
(*pEnv)->DeleteGlobalRef(pEnv,
pEntry->mValue.mColorArray[i]);
}
free(pEntry->mValue.mColorArray);
break;
}
}
...
9. Re-generate JNI header jni/com_packtpub_Store.h.
10. Implement all these new store methods in com_packtpub_Store.c, starng with
getIntegerArray(). A JNI array of integers is represented with type jintArray.
If an int is equivalent to a jint, an int* array is absolutely not equivalent to
a jintArray. The rst is a pointer to a memory buer whereas the second is a
reference to an object.
Thus, to return a jintArray here, instanate a new Java integer array with JNI API
method NewIntArray(). Then, use SetIntArrayRegion() to copy the nave
int buer content into the jintArray.
SetIntArrayRegion() performs bound checking to prevent buer overows
and can return an ArrayIndexOutOfBoundsException(). However, there is no
need to check it since there is no statement further in the method to be executed
(excepons will be propagated automacally by the JNI framework):
#include “com_packtpub_Store.h”
#include “Store.h”
...
JNIEXPORT jintArray JNICALL Java_com_packtpub_Store_
getIntegerArray
(JNIEnv* pEnv, jobject pThis, jstring pKey) {
StoreEntry* lEntry = findEntry(pEnv, &gStore, pKey, NULL);
if (isEntryValid(pEnv, lEntry, StoreType_IntegerArray)) {
jintArray lJavaArray = (*pEnv)->NewIntArray(pEnv,
lEntry->mLength);
if (lJavaArray == NULL) {
return;
}
Interfacing Java and C/C++ with JNI
[ 102 ]
(*pEnv)->SetIntArrayRegion(pEnv, lJavaArray, 0,
lEntry->mLength, lEntry->mValue.mIntegerArray);
return lJavaArray;
} else {
return NULL;
}
}
...
11. To save a Java array in nave code, the inverse operaon GetIntArrayRegion()
exists. The only way to allocate a suitable target memory buer is to measure
array size with GetArrayLength(). GetIntArrayRegion() also performs
bound checking and can raise an excepon. So method ow needs to be
stopped immediately when detecng one with ExceptionCheck(). Although
GetIntArrayRegion() is not the only method to raise excepons, it has the
parcularity with SetIntArrayRegion() to return void. There is no way to check
return code. Hence the excepon check:
...
JNIEXPORT void JNICALL Java_com_packtpub_Store_setIntegerArray
(JNIEnv* pEnv, jobject pThis, jstring pKey, jintArray
pIntegerArray) {
jsize lLength = (*pEnv)->GetArrayLength(pEnv, pIntegerArray);
int32_t* lArray = (int32_t*) malloc(lLength *
sizeof(int32_t));
(*pEnv)->GetIntArrayRegion(pEnv, pIntegerArray, 0, lLength,
lArray);
if ((*pEnv)->ExceptionCheck(pEnv)) {
free(lArray);
return;
}
StoreEntry* lEntry = allocateEntry(pEnv, &gStore, pKey);
if (lEntry != NULL) {
lEntry->mType = StoreType_IntegerArray;
lEntry->mLength = lLength;
lEntry->mValue.mIntegerArray = lArray;
} else {
free(lArray);
return;
}
}
...
Chapter 3
[ 103 ]
12. Object arrays are dierent than primive arrays. They are instanated with a Class
type (here com/packtpub/Color) because Java arrays are mono-type. Object
arrays are represented with type jobjectArray.
On the opposite of primive arrays, it is not possible to work on all elements at the
same me. Instead, objects are set one by one with SetObjectArrayElement().
Here, array is lled with Color objects stored on the nave side, which keeps global
references to them. So there is no need to delete or create any reference here
(except the class descriptor).
Remember that an object array keep references to the objects it holds.
Thus, local as well as global references can be inserted in an array and
deleted safely right aer.
...
JNIEXPORT jobjectArray JNICALL Java_com_packtpub_Store_
getColorArray
(JNIEnv* pEnv, jobject pThis, jstring
pKey) {
StoreEntry* lEntry = findEntry(pEnv, &gStore, pKey, NULL);
if (isEntryValid(pEnv, lEntry, StoreType_ColorArray)) {
jclass lColorClass = (*pEnv)->FindClass(pEnv,
“com/packtpub/Color”);
if (lColorClass == NULL) {
return NULL;
}
jobjectArray lJavaArray = (*pEnv)->NewObjectArray(
pEnv, lEntry->mLength, lColorClass, NULL);
(*pEnv)->DeleteLocalRef(pEnv, lColorClass);
if (lJavaArray == NULL) {
return NULL;
}
int32_t i;
for (i = 0; i < lEntry->mLength; ++i) {
(*pEnv)->SetObjectArrayElement(pEnv, lJavaArray, i,
lEntry->mValue.mColorArray[i]);
if ((*pEnv)->ExceptionCheck(pEnv)) {
return NULL;
}
}
return lJavaArray;
} else {
return NULL;
} }
...
Interfacing Java and C/C++ with JNI
[ 104 ]
13. In setColorArray(), array elements are also retrieved one by one with
GetObjectArrayElement(). Returned references are local and should be
made global to store them safely in a memory buer. If a problem happens,
global references must be carefully destroyed to allow garbage collecon, as
we decide to interrupt processing.
...
JNIEXPORT void JNICALL Java_com_packtpub_Store_setColorArray
(JNIEnv*
pEnv, jobject pThis, jstring pKey, jobjectArray
pColorArray) {
jsize lLength = (*pEnv)->GetArrayLength(pEnv, pColorArray);
jobject* lArray = (jobject*) malloc(lLength *
sizeof(jobject));
int32_t i, j;
for (i = 0; i < lLength; ++i) {
jobject lLocalColor = (*pEnv)->GetObjectArrayElement(pEnv,
pColorArray, i);
if (lLocalColor == NULL) {
for (j = 0; j < i; ++j) {
(*pEnv)->DeleteGlobalRef(pEnv, lArray[j]);
}
free(lArray);
return;
}
lArray[i] = (*pEnv)->NewGlobalRef(pEnv, lLocalColor);
if (lArray[i] == NULL) {
for (j = 0; j < i; ++j) {
(*pEnv)->DeleteGlobalRef(pEnv, lArray[j]);
}
free(lArray);
return;
}
(*pEnv)->DeleteLocalRef(pEnv, lLocalColor);
}
StoreEntry* lEntry = allocateEntry(pEnv, &gStore, pKey);
if (lEntry != NULL) {
lEntry->mType = StoreType_ColorArray;
lEntry->mLength = lLength;
lEntry->mValue.mColorArray = lArray;
} else {
for (j = 0; j < i; ++j) {
Chapter 3
[ 105 ]
(*pEnv)->DeleteGlobalRef(pEnv, lArray[j]);
}
free(lArray);
return;
}
}
What just happened?
We have transmied Java arrays from nave to C code and vice versa. Java arrays are objects
which cannot be manipulated navely in C code but only through a dedicated API.
Primives array types available are jbooleanArray, jbyteArray, jcharArray,
jdoubleArray, jfloatArray, jlongArray, and jshortArray. These arrays are
manipulated “by set, that is, several elements at a me. There are several ways to
access array content:
Get<Primitive>ArrayRegion() and
Set<Primitive>ArrayRegion()
Copy the content of a Java array into a nave
array or reciprocally. This is the best soluon
when a local copy is necessary to nave code.
Get<Primitive>ArrayElements(),
Set<Primitive>ArrayElements(),
and Release<Primitive>ArrayEle
ments()
These methods are similar but work on a buer
either temporarily allocated by them or poinng
directly on the target array. This buer must be
released aer use. These are interesng to use if
no local data copy is needed.
Get<Primitive>ArrayCritical()
and Release<Primitive>ArrayCri
tical()
These are more likely to provide a direct access
to the target array (instead of a copy). However,
their usage is restricted: JNI funcons and Java
callbacks must not be performed..
The nal project Store provides an example of
Get<Primitives>ArrayElements() usage for setBooleanArray().
Objects arrays are specic because on the opposite of primive arrays each array element
is a reference which can be garbage collected. As a consequence, a new reference is
automacally registered when inserted inside the array. That way, even if calling code
removes its references, array sll references them. Object arrays are manipulated with
GetObjectArrayElement() and SetObjectArrayElement().
See http://download.oracle.com/javase/1.5.0/docs/guide/jni/spec/
functions.html for a more exhausve list of JNI funcons.
Interfacing Java and C/C++ with JNI
[ 106 ]
Checking JNI exceptions
In JNI, methods which can raise an excepon (most of them actually) should be carefully
checked. If a return code or pointer is given back, checking it is sucient to know if something
happened. But somemes, with Java callbacks or methods like GetIntArrayRegion(),
we have no return code. In that case, excepons should be checked systemacally with
ExceptionOccured() or ExceptionCheck(). The rst returns a jthrowable type
containing a reference to the raised excepon whereas the laer just returns a
Boolean indicator.
When an excepon is raised, any subsequent call fails unl either:
method returns and excepon is propagated.
or excepon is cleared. Clearing an excepon mean that the excepon is handled
and thus not propagated to Java. For example:
Jthrowable lException;
pEnv->CallObjectMethod(pJNIEnv, ...);
lException = pEnv->ExceptionOccurred(pEnv);
if (lException) {
// Do something...
pEnv->ExceptionDescribe();
pEnv->ExceptionClear();
(*pEnv)->DeleteLocalRef(pEnv, lException);
}
Here, ExceptionDescribe() is a ulity roune to dump excepon content like done by
printStackTrace() in Java. Only a few JNI methods are sll safe to call when handling
an excepon:
DeleteLocalRef() PushLocalFrame()
DeleteGlobalRef() PopLocalFrame()
ExceptionOccured() ReleaseStringChars()
ExceptionDescribe() ReleaseStringUTFChars()
ExceptionOccured() ReleaseStringCritical()
ExceptionDescribe() Release<Primitive>ArrayElements()
MonitorExit() ReleasePrimitiveArrayCritical()
Chapter 3
[ 107 ]
Have a go hero – handling other array types
With the knowledge freshly acquired, implement store methods for other array types:
jbooleanArray, jbyteArray, jcharArray, jdoubleArray, jfloatArray,
jlongArray, and jshortArray. When you are done, write operaons for string arrays.
The nal project Store implemenng these cases
is provided with this book.
Summary
In this chapter, we have seen how to make Java communicate with C/C++. Android is now
almost bilingual! Java can call C/C++ code with any type of data or objects. More specically,
we have seen how to call nave code with primive types. These primives have their C/
C++ equivalent type they can can be casted to. Then, we have passed objects and handled
their references. References are local to a method by default and should not be shared
outside method scope. They should be managed carefully as their number is limited (this
limit can sll be manually increased). Aer that, we have shared and stored objects with
global references. Global references need to be carefully deleted to ensure proper garbage
collecon. We have also raised excepons from nave code to nofy Java if a problem
occurred and check excepons occurring in JNI. When an excepon occurs, only a few
cleaning JNI methods are safe to call. Finally, we have manipulated primive and objects
arrays. Arrays may or may not be copied by the VM when manipulated in nave code. The
performance penalty has to be taken into account.
But there is sll more to come: how to call Java from C/C++ code. We got a paral overview
with excepons. But actually, any Java object, method, or eld can be handled by nave
code. Let’s see this in the next chapter.
4
Calling Java Back from Native Code
To reach its full potenal, JNI allows calling Java code from C/C++. This is oen
referred to as a callback since nave code is itself invoked from Java. Such calls
are performed through a reecve API, which allows doing almost anything
that can be done directly in Java. Another important maer to consider with
JNI is threading. Nave code can be run on a Java thread, managed by
the Dalvik VM, and also from a nave thread created with standard POSIX
primives. Obviously, a nave thread cannot call JNI code unless it is turned
into a managed thread! Programming with JNI necessitates knowledge of all
these subtlees. This chapter will guide you through the main ones.
Since version R5, the Android NDK also proposes a new API to access navely
an important type of Java objects: bitmaps. This bitmap API is Android-specic
and aims at giving full processing power to graphics applicaons running on
these ny (but powerful) devices. To illustrate this topic, we will see how to
decode a camera feed directly inside nave code.
To summarize, in this chapter, we are going to learn how to:
Aach a JNI context to a nave thread
Handle synchronizaon with Java threads
Call Java back from nave code
Process Java bitmaps in nave code
By the end of the chapter, you should be able to make Java and C/C++ communicate
together in both direcons.
Calling Java Back from Nave Code
[ 110 ]
Synchronizing Java and native threads
In this part, we are going to create a background thread, the watcher, which keeps an eye
constantly on what is inside the data store. It iterates through all entries and then sleeps
for a xed amount of me. When the watcher thread nds a specic key, value, or type
predened in code, it acts accordingly. For this rst part, we are just going to increment a
watcher counter each me the watcher thread iterates over entries. In next part, we will
see how to react by calling back Java.
Of course, threads also needs synchronizaon. The nave thread will be allowed to access
and update the store only when a user (understand the UI thread) is not modifying it. The
nave thread is in C but the UI thread in Java. Thus, we have two opons here:
Use nave mutexes as our UI thread makes nave calls when geng and seng
values anyway
Use Java monitors and synchronize nave thread with JNI
Of course, in a chapter dedicated to JNI, we can only choose the second opon! The nal
applicaon structure will look as follows:
Internal Store
structure
Internal Store
Structure
Store Wrapper
Functions
int
Color
StoreActivity
<<user>>
StoreType
StoreType
StoreValue
<<Union>>
StoreEntry
String
Store
1
<<user>>
1
1
1
1
Java
C
*
com_packtpub_Store
Store InvalidTypeException
NotExistingKeyException
StoreFullException
<<throws>>
StoreWatcher
1
Project Store_Part3-4 can be used as a starng point for this part. The resulng
project is provided with this book under the name Project Store_Part4-1.
Chapter 4
[ 111 ]
Time for action – running a background thread
Let's add some synchronizaon capabilies on the Java rst:
1. Open Store.java created in the previous chapter. Create two new nave methods,
initializeStore() and finalizeStore(), to start/stop the watcher thread and
inialize/destroy the store when acvity is started and stopped, respecvely.
Make every Store class's geer and seer synchronized, as they are not allowed
to access and modify store entries while the watcher thread iterates through them:
public class Store {
static {
System.loadLibrary("store");
}
public native void initializeStore();
public native void finalizeStore();
public native synchronized int getInteger(String pKey)
throws NotExistingKeyException, InvalidTypeException;
public native synchronized void setInteger(String pKey,
int pInt);
// Other getters and setters are synchronized too.
...
}
2. Call inializaon and nalizaon methods when acvity is started and stopped.
Create a watcherCounter entry of type integer when store is inialized.
This entry will be updated automacally by the watcher:
public class StoreActivity extends Activity {
private EditText mUIKeyEdit, mUIValueEdit;
private Spinner mUITypeSpinner;
private Button mUIGetButton, mUISetButton;
private Store mStore;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Initializes components and binds buttons to handlers.
...
// Initializes the native side store.
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
Calling Java Back from Nave Code
[ 112 ]
mStore = new Store();
}
@Override
protected void onStart() {
super.onStart();
mStore.initializeStore();
mStore.setInteger("watcherCounter", 0);
}
@Override
protected void onStop() {
super.onStop();
mStore.finalizeStore();
}
...
}
The Java side is ready to inialize and destroy the nave thread... Let's switch to the
nave side to implement it:
3. Create a new le StoreWatcher.h in folder jni. Include Store, JNI, and nave
threads headers.
The watcher works on a Store instance updated at regular intervals of me
(three seconds here). It needs:
A JavaVM, which is the only object safely shareable among threads from
which a JNI environment can be safely retrieved.
A Java object to synchronize on, here the Java Store frontend object
because it has synchronized methods.
Variables dedicated to thread management.
4. Finally, dene two methods to start the nave thread aer inializaon and stop it:
#ifndef _STOREWATCHER_H_
#define _STOREWATCHER_H_
#include "Store.h"
#include <jni.h>
#include <stdint.h>
#include <pthread.h>
#define SLEEP_DURATION 5
#define STATE_OK 0
Chapter 4
[ 113 ]
#define STATE_KO 1
typedef struct {
// Native variables.
Store* mStore;
// Cached JNI references.
JavaVM* mJavaVM;
jobject mStoreFront;
// Thread variables.
pthread_t mThread;
int32_t mState;
} StoreWatcher;
void startWatcher(JNIEnv* pEnv, StoreWatcher* pWatcher,
Store* pStore, jobject pStoreFront);
void stopWatcher(JNIEnv* pEnv, StoreWatcher* pWatcher);
#endif
5. Create jni/StoreWatcher.h and declare addional private methods:
runWatcher(): This represents the nave thread main loop.
processEntry(): This is invoked while a watcher iterates through entries.
getJNIEnv(): This retrieves a JNI environment for the current thread.
deleteGlobalRef(): This helps delete global references previously
created.
#include "StoreWatcher.h"
#include <unistd.h>
void deleteGlobalRef(JNIEnv* pEnv, jobject* pRef);
JNIEnv* getJNIEnv(JavaVM* pJavaVM);
void* runWatcher(void* pArgs);
void processEntry(JNIEnv* pEnv, StoreWatcher* pWatcher,
StoreEntry* pEntry);
...
6. In jni/StoreWatcher.c, implement startWatcher(), invoked from the UI
thread, that set up the StoreWatcher structure and starts the watcher thread,
thanks to POSIX primives.
Calling Java Back from Nave Code
[ 114 ]
7. Because the UI thread may access store content at the same me the watcher
thread checks entries, we need to keep an object to synchronize on. Let's use Store
class itself since its geers and seers are synchronized:
In Java, synchronizaon is always performed on an object. When a Java method
is dened with the synchronized keyword, then Java synchronizes on
this (the current object) behind the scene: synchronized(this) {
doSomething(); ... }.
...
void startWatcher(JNIEnv* pEnv, StoreWatcher* pWatcher,
Store* pStore, jobject pStoreFront) {
// Erases the StoreWatcher structure.
memset(pWatcher, 0, sizeof(StoreWatcher));
pWatcher->mState = STATE_OK;
pWatcher->mStore = pStore;
// Caches the VM.
if ((*pEnv)->GetJavaVM(pEnv, &pWatcher->mJavaVM) != JNI_OK) {
goto ERROR;
}
// Caches objects.
pWatcher->mStoreFront = (*pEnv)->NewGlobalRef
(pEnv, pStoreFront);
if (pWatcher->mStoreFront == NULL) goto ERROR;
// Initializes and launches the native thread. For simplicity
// purpose, error results are not checked (but we should...).
pthread_attr_t lAttributes;
int lError = pthread_attr_init(&lAttributes);
if (lError) goto ERROR;
lError = pthread_create(&pWatcher->mThread, &lAttributes,
runWatcher, pWatcher);
if (lError) goto ERROR;
return;
ERROR:
stopWatcher(pEnv, pWatcher);
return;
}
...
Chapter 4
[ 115 ]
8. In StoreWatcher.c, implement helper method getJNIEnv(), which is called
when the thread starts. The watcher thread is nave, which means that:
No JNI environment is aached. Thus, JNI is not acvated by default for the
thread.
It is not instanated by Java and has no "Java root", that is, if you look at the
call stack, you never nd a Java method.
Having no Java root is an important property of nave threads because it
impacts directly the ability of JNI to load Java classes. Indeed, it is not possible
from a nave thread to access the Java applicaon class loader. Only a
bootstrap class loader with system classes is available. A Java thread on the
opposite always has a Java root and thus can access the applicaon class
loader with its applicaon classes.
A soluon to that problem is to load classes in an appropriate Java thread and
to share them later with nave threads.
9. The nave thread is aached to the VM with AttachCurrentThread() in order to
retrieve a JNIEnv. This JNI environment is specic to the current thread and cannot
be shared with others (he opposite of a JavaVM object which can be shared safely).
Internally, the VM builds a new Thread object and adds it to the main thread group,
like any other Java thread:
...
JNIEnv* getJNIEnv(JavaVM* pJavaVM) {
JavaVMAttachArgs lJavaVMAttachArgs;
lJavaVMAttachArgs.version = JNI_VERSION_1_6;
lJavaVMAttachArgs.name = "NativeThread";
lJavaVMAttachArgs.group = NULL;
JNIEnv* lEnv;
if ((*pJavaVM)->AttachCurrentThread(pJavaVM, &lEnv,
&lJavaVMAttachArgs) != JNI_OK) {
lEnv = NULL;
}
return lEnv;
}
...
10. The most important method is runWatcher(), the main thread loop. Here, we are
not anymore on the UI thread but on the watcher thread. Thus we need to aach it
to the VM in order to get a working JNI environment.
Calling Java Back from Nave Code
[ 116 ]
11. The thread works only at regular intervals of me and sleeps meanwhile. When
it leaves its nap, the thread starts looping over each entry individually in a crical
secon (that is, synchronized) to access them safely. Indeed, the UI thread (that is,
the user) may change an entry value at any me.
12. Crical secon is delimited with a JNI monitor which has exactly the same properes
as the synchronized keyword in Java. Obviously, MonitorEnter() and
MonitorExit() have to lock/unlock on the object mStoreFront to synchronize
properly with its geers and seers. These instrucons ensure that the rst thread
to reach a monitor/synchronized block will enter the secon while the other will
wait in front of the door unl the rst has nished.
13. Thread leaves the loop and exits when state variable is changed by the UI thread
(in stopWatcher()). An aached thread which dies must eventually detach from
the VM so that the laer can release resources properly:
...
void* runWatcher(void* pArgs) {
StoreWatcher* lWatcher = (StoreWatcher*) pArgs;
Store* lStore = lWatcher->mStore;
JavaVM* lJavaVM = lWatcher->mJavaVM;
JNIEnv* lEnv = getJNIEnv(lJavaVM);
if (lEnv == NULL) goto ERROR;
int32_t lRunning = 1;
while (lRunning) {
sleep(SLEEP_DURATION);
StoreEntry* lEntry = lWatcher->mStore->mEntries;
int32_t lScanning = 1;
while (lScanning) {
// Critical section begining, one thread at a time.
// Entries cannot be added or modified.
(*lEnv)->MonitorEnter(lEnv, lWatcher->mStoreFront);
lRunning = (lWatcher->mState == STATE_OK);
StoreEntry* lEntryEnd = lWatcher->mStore->mEntries
+ lWatcher->mStore->mLength;
lScanning = (lEntry < lEntryEnd);
if (lRunning && lScanning) {
processEntry(lEnv, lWatcher, lEntry);
}
// Critical section end.
Chapter 4
[ 117 ]
(*lEnv)->MonitorExit(lEnv, lWatcher->mStoreFront);
// Goes to next element.
++lEntry;
}
}
ERROR:
(*lJavaVM)->DetachCurrentThread(lJavaVM);
pthread_exit(NULL);
}
...
14. In StoreWatcher, write processEntry() which detects the watcherCounter
entry and increment its value. Thus, watcherCounter contains how many
iteraons the watcher thread has performed since the beginning:
...
void processEntry(JNIEnv* pEnv, StoreWatcher* pWatcher,
StoreEntry* pEntry) {
if ((pEntry->mType == StoreType_Integer)
&& (strcmp(pEntry->mKey, "watcherCounter") == 0) {
++pEntry->mValue.mInteger;
}
}
...
15. To nish with jni/StoreWatcher.c, write stopWatcher(), also executed on the
UI thread, which terminates the watcher thread and releases all global references.
To help releasing them, implement deleteGlobalRef() helper ulity which will
help us make the code more consise in the next part. Note that mState is a shared
variable among threads and need to be accessed inside a crical secon:
...
void deleteGlobalRef(JNIEnv* pEnv, jobject* pRef) {
if (*pRef != NULL) {
(*pEnv)->DeleteGlobalRef(pEnv, *pRef);
*pRef = NULL;
}
}
void stopWatcher(JNIEnv* pEnv, StoreWatcher* pWatcher) {
if (pWatcher->mState == STATE_OK) {
// Waits for the watcher thread to stop.
(*pEnv)->MonitorEnter(pEnv, pWatcher->mStoreFront);
pWatcher->mState = STATE_KO;
Calling Java Back from Nave Code
[ 118 ]
(*pEnv)->MonitorExit(pEnv, pWatcher->mStoreFront);
pthread_join(pWatcher->mThread, NULL);
deleteGlobalRef(pEnv, &pWatcher->mStoreFront);
}
}
16. Generate JNI header le with javah.
17. Finally, open exisng le jni/com_packtpub_Store.c, declare a stac Store
variable containing store content and dene initializeStore() to create and
run the watcher thread and finalizeStore() to stop it and release entries:
#include "com_packtpub_Store.h"
#include "Store.h"
#include "StoreWatcher.h"
#include <stdint.h>
#include <string.h>
static Store mStore;
static StoreWatcher mStoreWatcher;
JNIEXPORT void JNICALL Java_com_packtpub_Store_initializeStore
(JNIEnv* pEnv, jobject pThis) {
mStore.mLength = 0;
startWatcher(pEnv, &mStoreWatcher, &mStore, pThis);
}
JNIEXPORT void JNICALL Java_com_packtpub_Store_finalizeStore
(JNIEnv* pEnv, jobject pThis) {
stopWatcher(pEnv, &mStoreWatcher);
StoreEntry* lEntry = mStore.mEntries;
StoreEntry* lEntryEnd = lEntry + mStore.mLength;
while (lEntry < lEntryEnd) {
free(lEntry->mKey);
releaseEntryValue(pEnv, lEntry);
++lEntry;
}
mStore.mLength = 0;
}
...
Chapter 4
[ 119 ]
18. Do not forget to add StoreWatcher.c to the Android.mk le as usual.
19. Compile and run the applicaon.
What just happened?
We have created a background nave thread and managed to aach it to the Dalvik VM,
allowing us to get a JNI environment. Then we have synchronized Java and nave threads
together to handle concurrency issues properly. Store is inialized when applicaon starts
and when it stops.
On the nave side, synchronizaon is performed with a JNI monitor equivalent to the
synchronized keyword. Because Java threads are based on POSIX primives internally,
it would also be possible to implement thread synchronizaon completely navely
(that is, without relying on Java primive) with POSIX mutexes:
pthread_mutex_t lMutex;
pthread_cond_t lCond;
// Initializes synchronization variables
pthread_mutex_init(&lMutex, NULL);
pthread_cond_init(&lCond, NULL);
// Enters critical section.
pthread_mutex_lock(&lMutex);
// Waits for a condition
While (needToWait)
pthread_cond_wait(&lCond, &lMutex);
// Does something...
// Wakes-up other threads.
pthread_cond_broadcast(&lCond);
// Leaves critical section.
pthread_mutex_unlock(&lMutex);
Depending on the plaorm, mixing Java thread synchronizaon and nave
synchronizaon based on dierent models is considered as a harmful pracce
(for example, plaorms which implement green threads). Android is not
concerned by this problem but keep it in mind if you plan to write portable
nave code.
Calling Java Back from Nave Code
[ 120 ]
As a last note I would like to point out that Java and C/C++ are dierent languages, with
similar but somewhat dierent semancs. Thus, always be careful not to expect C/C++ to
behave like Java. As an example, the volatile has a dierent semanc in Java and C/C++
since both follow a dierent memory model.
Attaching and detaching threads
A good place to get JavaVM instance is from JNI_OnLoad(), a callback that a nave library
can declare and implement to get noed when library is loaded in memory (when System.
loadLibrary() is called from Java). This is also a good place to do some JNI descriptor
caching as we will see in next part:
JavaVM* myGlobalJavaVM;
jint JNI_OnLoad(JavaVM* pVM, void* reserved) {
myGlobalJavaVM = pVM;
JNIEnv *lEnv;
if (pVM->GetEnv((void**) &lEnv, JNI_VERSION_1_6) != JNI_OK) {
// A problem occured
return -1;
}
return JNI_VERSION_1_6;
}
An aached thread like the watcher thread must be eventually unaached before acvity is
destroyed. Dalvik detects threads which are not detached and reacts by aborng and leaving
a dirty crash dump in your logs! When geng detached, any monitor held is released and
any waing thread is noed.
Since Android 2.0, a technique to make sure a thread is systemacally detached is to
bind a destructor callback to the nave thread with pthread_key_create() and
DetachCurrentThread(). A JNI environment can be saved into thread local storage
with pthread_setspecific() to pass it as an argument to the destructor.
Although aaching/detaching can be performed at any me, these
operaons are expensive and should be performed once or punctually
rather than constantly.
Chapter 4
[ 121 ]
More on Java and native code lifecycles
If you compare Store_Part3-4 and Store_Part4-1, you will discover that values remain
between execuons in the rst one. This is because nave libraries have a dierent lifecycle
than usual Android acvies. When an acvity is destroyed and recreated for any reason
(for example, screen reorientaon), any data is lost in the Java acvity.
But nave library and its global data are likely to remain in memory! Data persists between
execuons. This has implicaons in terms of memory management. Carefully release
memory when an applicaon is destroyed if you do not want to keep it between execuons.
Take care with create and destroy events
In some conguraons, onDestroy() event has the reputaon of
somemes being executed aer an acvity instance is recreated. This means
that destrucon of an acvity may occur unexpectedly aer the second
instance is recreated. Obvisously, this can lead to memory corrupon or leak.
Several strategies exist to overcome this problem:
Create and destroy data in other events if possible (like onStart() and onStop()).
But you will probably need to persist your data somewhere meanwhile (Java le),
which may impact responsiveness.
Destroy data only in onCreate(). This has the major inconvenience of not releasing
memory while an applicaon is running in the background.
Never allocate global data on the nave side (that is, stac variables) but save
the pointer to your nave data on the Java side: allocate memory when acvity is
created and send back your pointer to Java casted as an int (or even beer a long
for future compability reasons). Any futher JNI call must be performed with this
pointer as parameter.
Use a variable on the Java side to detect the case where destrucon of an acvity
(onDestroy()) happens aer a new instance has been recreated (onCreate()).
Do not cache JNIEnv between execuons!
Android applicaons can be destroyed and recreated at any me. If a JNIEnv
is cached on the nave side and the applicaon gets closed meanwhile, then
its reference may become invalid. So get back a new reference each me an
applicaon is recreated.
Calling Java Back from Nave Code
[ 122 ]
Calling Java back from native code
In the previous chapter, we have discovered how to get a Java class descriptor with JNI
method FindClass(). But we can get much more! Actually, if you are a regular Java
developer, this should remind you of something: the Java reecon API. Similarly, JNI can
modify Java object elds, run Java methods, access stac members... but from nave
code. This is oen referred to as a Java callback, because Java code is run from nave code
which descends itself from Java. But this is the simple case. Since JNI is ghtly coupled with
threads, calling Java code from nave threads is slightly more dicult. Aaching a thread
to a VM is only part of the soluon.
For this last part with the Store project, let's enhance the watcher thread so that it warns
the Java acvity when it detects a value it does not like (for example, an integer outside a
dened range). We are going to use JNI callback capabilies to iniate communicaon from
nave code to Java.
Project Store_Part4-1 can be used as a starng point for this part. The resulng
project is provided with this book under the name Project Store_Part4-2.
Time for action – invoking Java code from a native thread
Let's make a few changes on the Java side:
1. Create a StoreListener interface as follows to dene methods through which
nave code is going to communicate with Java code:
public interface StoreListener {
public void onAlert(int pValue);
public void onAlert(String pValue);
public void onAlert(Color pValue);
}
2. Open Store.java and make a few changes:
Declare one Handler member. A Handler is a message queue associated
with the thread it was created on (here, it will be the UI thread). Any message
posted from whatever thread is received in an internal queue processed
magically on the inial thread. Handlers are a popular and easy inter-thread
communicaon technique on Android.
Chapter 4
[ 123 ]
Declare a delegate StoreListener to which messages (that is, a method call)
received from the watcher thread are going to be posted. This will be the
StoreActivity.
Change Store constructor to inject the target delegate listener.
Implement StoreListener interface and its corresponding methods.
Alert messages are recorded as Runnable tasks and posted to the target
thread, on which the nal listener works safely.
public class Store implements StoreListener {
static {
System.loadLibrary("store");
}
private Handler mHandler;
private StoreListener mDelegateListener;
public Store(StoreListener pListener) {
mHandler = new Handler();
mDelegateListener = pListener;
}
public void onAlert(final int pValue) {
mHandler.post(new Runnable() {
public void run() {
mDelegateListener.onAlert(pValue);
}
});
}
public void onAlert(final String pValue) {
mHandler.post(new Runnable() {
public void run() {
mDelegateListener.onAlert(pValue);
}
});
}
public void onAlert(final Color pValue) {
mHandler.post(new Runnable() {
public void run() {
mDelegateListener.onAlert(pValue);
}
});
}
...
}
Calling Java Back from Nave Code
[ 124 ]
3. Update the exisng class Color and add methods to check equality. This will later
allow the watcher thread to compare an entry to a reference color:
public class Color {
private int mColor;
public Color(String pColor) {
super();
mColor = android.graphics.Color.parseColor(pColor);
}
@Override
public String toString() {
return String.format("#%06X", mColor);
}
@Override
public int hashCode() {
return mColor;
}
@Override
public boolean equals(Object pOther) {
if (this == pOther) { return true; }
if (pOther == null) { return false; }
if (getClass() != pOther.getClass()) { return false; }
Color pColor = (Color) pOther;
return (mColor == pColor.mColor);
}
}
4. Open StoreActivity.java and implement StoreListener interface. When
an alert is received, a simple toast message is raised. Change Store constructor
call accordingly. Note that this is the moment where the thread on which the
internal Handler processes messages is determined:
public class StoreActivity extends Activity implements
StoreListener{
private EditText mUIKeyEdit, mUIValueEdit;
private Spinner mUITypeSpinner;
private Button mUIGetButton, mUISetButton;
private Store mStore;
@Override
Chapter 4
[ 125 ]
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Initializes components and binds buttons to handlers.
...
// Initializes the native side store.
mStore = new Store(this);
}
...
public void onAlert(int pValue) {
displayError(String.format("%1$d is not an allowed integer",
pValue));
}
public void onAlert(String pValue) {
displayError(String.format("%1$s is not an allowed string",
pValue));
}
public void onAlert(Color pValue) {
displayError(String.format("%1$s is not an allowed color",
pValue.toString()));
}
}
The Java side is ready to receive callbacks. Let's go back to nave code to emit them:
5. Open exisng le jni/StoreWatcher.c. The StoreWatcher structure already
has access to the Java Store frontend. But to call its methods (for example, Store.
onAlert()), we need a few more items: declare the appropriate class and method
descriptors like if you were working with the reecon API. Do the same for Color.
equals().
6. In addion, declare a a reference to a Color object which is going to be used as a
base for color comparison by the watcher. Any idencal color will be considered as
an alert:
Calling Java Back from Nave Code
[ 126 ]
What we do here is cache references so that we do not have to nd
them again for each JNI call. Caching has two main benets: it improves
performances (JNI lookups are quite expensive compare to a cached access)
and readability.
Caching is also the only way to provide JNI references to nave threads as
they do not have access to the applicaon class loader (only the system one).
#ifndef _STOREWATCHER_H_
#define _STOREWATCHER_H_
...
typedef struct {
// Native variables.
Store* mStore;
// Cached JNI references.
JavaVM* mJavaVM;
jobject mStoreFront;
jobject mColor;
// Classes.
jclass ClassStore;
jclass ClassColor;
// Methods.
jmethodID MethodOnAlertInt;
jmethodID MethodOnAlertString;
jmethodID MethodOnAlertColor;
jmethodID MethodColorEquals;
// Thread variables.
pthread_t mThread;
int32_t mState;
} StoreWatcher;
...
7. In jni directory, open implementaon le StoreWatcher.c. Declare helper
methods to create a global reference and process entries.
8. Implement makeGlobalRef(), which turns a local into a global reference. This is
a "shortcut" to ensure proper deleon of local references and NULL value handling
(if an error occurs in a previous instrucon):
#include "StoreWatcher.h"
Chapter 4
[ 127 ]
#include <unistd.h>
void makeGlobalRef(JNIEnv* pEnv, jobject* pRef);
void deleteGlobalRef(JNIEnv* pEnv, jobject* pRef);
JNIEnv* getJNIEnv(JavaVM* pJavaVM);
void* runWatcher(void* pArgs);
void processEntry(JNIEnv* pEnv, StoreWatcher* pWatcher,
StoreEntry* pEntry);
void processEntryInt(JNIEnv* pEnv, StoreWatcher* pWatcher,
StoreEntry* pEntry);
void processEntryString(JNIEnv* pEnv, StoreWatcher* pWatcher,
StoreEntry* pEntry);
void processEntryColor(JNIEnv* pEnv, StoreWatcher* pWatcher,
StoreEntry* pEntry);
void makeGlobalRef(JNIEnv* pEnv, jobject* pRef) {
if (*pRef != NULL) {
jobject lGlobalRef = (*pEnv)->NewGlobalRef(pEnv, *pRef);
// No need for a local reference any more.
(*pEnv)->DeleteLocalRef(pEnv, *pRef);
// Here, lGlobalRef may be null.
*pRef = lGlobalRef;
}
}
void deleteGlobalRef(JNIEnv* pEnv, jobject* pRef) {
if (*pRef != NULL) {
(*pEnv)->DeleteGlobalRef(pEnv, *pRef);
*pRef = NULL;
}
}
...
9. Here is the big piece, sll in StoreWatcher.c. If you remember the previous part,
method startWatcher() is called from the UI thread to inialize and start the
watcher. Thus, this is a perfect place to cache JNI descriptors. Actually, this is almost
one of the only places because as the UI thread is a Java thread, we have total
access to the applicaon class loader. But if we were trying to cache them inside
the nave thread, the laer would have access only to the system class loader and
nothing else!
Calling Java Back from Nave Code
[ 128 ]
10. One can nd a class descriptor thanks to its absolute package path (for example,
com./packtpub/Store). Because classes are objects, the only way to share them
safely with the nave thread is to turn them into global references. This is not the
case for "IDs" such as jmethodID and jfieldID which are in now way references:
...
void startWatcher(JNIEnv* pEnv, StoreWatcher* pWatcher,
Store* pStore, jobject pStoreFront) {
// Erases the StoreWatcher structure.
memset(pWatcher, 0, sizeof(StoreWatcher));
pWatcher->mState = STATE_OK;
pWatcher->mStore = pStore;
// Caches the VM.
if ((*pEnv)->GetJavaVM(pEnv, &pWatcher->mJavaVM) != JNI_OK) {
goto ERROR;
}
// Caches classes.
pWatcher->ClassStore = (*pEnv)->FindClass(pEnv,
"com/packtpub/Store");
makeGlobalRef(pEnv, &pWatcher->ClassStore);
if (pWatcher->ClassStore == NULL) goto ERROR;
pWatcher->ClassColor = (*pEnv)->FindClass(pEnv,
"com/packtpub/Color");
makeGlobalRef(pEnv, &pWatcher->ClassColor);
if (pWatcher->ClassColor == NULL) goto ERROR;
...
11. In start_watcher(), method descriptors are retrieved with JNI from a class
descriptor. To dierenate dierent overloads with the same name, a descripon
of the method with a simple predened formalism is necessary. For example, (I)
V which means an integer is expected and a void returned or (Ljava/lang/
String;)V which means a String is passed in parameter). Constructor descriptors
are retrieved in the same way except that their name is always <init> and they do
not return a value:
...
// Caches Java methods.
pWatcher->MethodOnAlertInt = (*pEnv)->GetMethodID(pEnv,
pWatcher->ClassStore, "onAlert", "(I)V");
if (pWatcher->MethodOnAlertInt == NULL) goto ERROR;
pWatcher->MethodOnAlertString = (*pEnv)->GetMethodID(pEnv,
Chapter 4
[ 129 ]
pWatcher->ClassStore, "onAlert", "(Ljava/lang/String;)V");
if (pWatcher->MethodOnAlertString == NULL) goto ERROR;
pWatcher->MethodOnAlertColor = (*pEnv)->GetMethodID(pEnv,
pWatcher->ClassStore, "onAlert","(Lcom/packtpub/Color;)V");
if (pWatcher->MethodOnAlertColor == NULL) goto ERROR;
pWatcher->MethodColorEquals = (*pEnv)->GetMethodID(pEnv,
pWatcher->ClassColor, "equals", "(Ljava/lang/Object;)Z");
if (pWatcher->MethodColorEquals == NULL) goto ERROR;
jmethodID ConstructorColor = (*pEnv)->GetMethodID(pEnv,
pWatcher->ClassColor, "<init>", "(Ljava/lang/String;)V");
if (ConstructorColor == NULL) goto ERROR;
...
12. Again in the same method start_watcher(), cache object instances with a global
reference. Do not use makeGlobalRef() ulity on the Java store frontend because
local reference is actually a parameter and does not need to be released.
13. The color is not an outside object referenced and cached like others. It is instanated
with JNI by a call to NewObject(), which takes a constructor descriptor in parameter.
...
// Caches objects.
pWatcher->mStoreFront = (*pEnv)->NewGlobalRef(pEnv, pStoreFront);
if (pWatcher->mStoreFront == NULL) goto ERROR;
// Creates a new white color and keeps a global reference.
jstring lColor = (*pEnv)->NewStringUTF(pEnv, "white");
if (lColor == NULL) goto ERROR;
pWatcher->mColor = (*pEnv)->NewObject(pEnv,pWatcher->ClassColor,
ConstructorColor, lColor);
makeGlobalRef(pEnv, &pWatcher->mColor);
if (pWatcher->mColor == NULL) goto ERROR;
// Launches the native thread.
...
return;
ERROR:
stopWatcher(pEnv, pWatcher);
return;
}
...
Calling Java Back from Nave Code
[ 130 ]
14. In the same le, rewrite processEntry() to process each type of entry separately.
Check that integers are in the range [-1000, 1000] and send an alert if that is not
the case. To invoke a Java method on a Java object, simply use CallVoidMethod()
on a JNI environment. This means that the called Java method returns void. If Java
method was returning an int, we would call CallIntMethod(). Like with the
reecon API, invoking a Java method requires:
An object instance (except for stac methods, in which case we would
provide a class instance and use CallStaticVoidMethod()).
A method descriptor.
Parameters (if applicable, here an integer value).
...
void processEntry(JNIEnv* pEnv, StoreWatcher* pWatcher,
StoreEntry* pEntry) {
switch (pEntry->mType) {
case StoreType_Integer:
processEntryInt(pEnv, pWatcher, pEntry);
break;
case StoreType_String:
processEntryString(pEnv, pWatcher, pEntry);
break;
case StoreType_Color:
processEntryColor(pEnv, pWatcher, pEntry);
break;
}
}
void processEntryInt(JNIEnv* pEnv,StoreWatcher* pWatcher,
StoreEntry* pEntry) {
if(strcmp(pEntry->mKey, "watcherCounter") == 0) {
++pEntry->mValue.mInteger;
} else if ((pEntry->mValue.mInteger > 1000) ||
(pEntry->mValue.mInteger < -1000)) {
(*pEnv)->CallVoidMethod(pEnv,
pWatcher->mStoreFront,pWatcher->MethodOnAlertInt,
(jint) pEntry->mValue.mInteger);
}
}
...
Chapter 4
[ 131 ]
15. Repeat the operaon for strings. Strings require allocang a new Java string. We do
not need to generate a global reference as it is used immediately in the Java callback.
But if you have kept in mind previous lessons, you know we can release the local
reference right aer it is used. Indeed, we are in a ulity method and we do not always
know the context they may be used in. In addion, whereas in a classic JNI method,
local references are deleted when method returns, here we are in an aached nave
thread. Thus, local references would get deleted only when thread is detached
(that is, when acvity leaves). JNI memory would leak meanwhile:
...
void processEntryString(JNIEnv* pEnv, StoreWatcher* pWatcher,
StoreEntry* pEntry) {
if (strcmp(pEntry->mValue.mString, "apple")) {
jstring lValue = (*pEnv)->NewStringUTF(
pEnv, pEntry->mValue.mString);
(*pEnv)->CallVoidMethod(pEnv,
pWatcher->mStoreFront, pWatcher->MethodOnAlertString,
lValue);
(*pEnv)->DeleteLocalRef(pEnv, lValue);
}
}
16. Finally, process colors. To check if a color is idencal to the reference color, invoke
the equality method provided by Java and reimplemented in our Color class.
Because it returns a Boolean value, CallVoidMethod() is inappropriate for the
rst test. But CallBooleanMethod() is:
void processEntryColor(JNIEnv* pEnv, StoreWatcher* pWatcher,
StoreEntry* pEntry) {
jboolean lResult = (*pEnv)->CallBooleanMethod(
pEnv, pWatcher->mColor,
pWatcher->MethodColorEquals, pEntry->mValue.mColor);
if (lResult) {
(*pEnv)->CallVoidMethod(pEnv,
pWatcher->mStoreFront, pWatcher->MethodOnAlertColor,
pEntry->mValue.mColor);
}
}
...
Calling Java Back from Nave Code
[ 132 ]
17. We are almost done. Do not forget to release global references when a thread exits!
...
void stopWatcher(JNIEnv* pEnv, StoreWatcher* pWatcher) {
if (pWatcher->mState == STATE_OK) {
// Waits for the watcher thread to stop.
...
deleteGlobalRef(pEnv, &pWatcher->mStoreFront);
deleteGlobalRef(pEnv, &pWatcher->mColor);
deleteGlobalRef(pEnv, &pWatcher->ClassStore);
deleteGlobalRef(pEnv, &pWatcher->ClassColor);
}
}
18. Compile and run.
What just happened?
Launch the applicaon and create a string entry with the value apple. Then try to create
an entry with white color. Finally, enter an integer value outside the [-1000, 1000] range.
In each case, a message should be raised on screen (every me the watcher iterates).
In this part, we have seen how to cache JNI descriptors and perform callbacks to Java. We
have also introduced a way to send messages between threads with handlers, invoked
indirectly in Java. Android features several other communicaon means, such as AsyncTask.
Have a look at http://developer.android.com/resources/articles/painless-
threading.html for more informaon.
Java callbacks are not only useful to execute a Java piece of code, they are also the only way
to analyze jobject parameters passed to a nave method. But if calling C/C++ code from
Java is rather easy, performing Java operaons from C/C++ is bit more involving! Performing
a single Java call that holds in one single line of Java code requires lots of work! Why? Simply
because JNI is a reecve API.
To get a eld value, one needs to get its containing class descriptor and its eld descriptor
before actually retrieving its value. To call a method, one needs to retrieve class descriptor
and method descriptor before calling the method with the necessary parameters. The
process is always the same.
Chapter 4
[ 133 ]
Caching denions
Retrieving all these element denions is not only tedious, it is absolutely not
opmal in terms of performance. Thus, JNI denions used frequently should
be cached for reuse. Cached elements can be kept safely for the lifeme of
an acvity (not of the nave library) and shared between threads with global
references (for example, for class descriptors).
Caching is the only soluon to communicate with nave threads, which do not have access
to the applicaon class loader. But there is a way to limit the amount of denions to
prepare: instead of caching classes, methods, and elds, simply cache the applicaon class
loader itself.
Do not call back in callbacks!
Calling nave code from Java through JNI works perfectly. Calling Java code
from nave works perfect too. However, interleaving several levels of Java and
nave calls should be avoided.
More on callbacks
The central object in JNI is JNIEnv. It is provided systemacally as rst parameter to
JNI C/C++ methods called from Java. We have seen:
jclass FindClass(const char* name);
jclass GetObjectClass(jobject obj);
jmethodID GetMethodID(jclass clazz, const char* name,
const char* sig) ;
jfieldID GetStaticFieldID(jclass clazz, const char* name,
const char* sig);
but also:
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig);
jmethodID GetStaticMethodID(jclass clazz, const char* name,
const char* sig);
These allow retrieving JNI descriptors: classes, methods, and elds, stac and instance
members having dierent accessors. Note that FindClass() and GetObjectClass()
have the same purpose except that FindClass nds class denions according to their
absolute path whereas the other nds the class of an object directly.
Calling Java Back from Nave Code
[ 134 ]
There is a second set of methods to actually execute methods or retrieve eld values.
There is one method per primive types plus one for objects.
jobject GetObjectField(jobject obj, jfieldID fieldID);
jboolean GetBooleanField(jobject obj, jfieldID fieldID);
void SetObjectField(jobject obj, jfieldID fieldID, jobject value);
void SetBooleanField(jobject obj, jfieldID fieldID, jboolean value);
The same goes for methods according to their return values:
jobject CallObjectMethod(JNIEnv*, jobject, jmethodID, ...)
jboolean CallBooleanMethod(JNIEnv*, jobject, jmethodID, ...);
Variants of call methods exist, with an A and V posix. Behavior is idencal except that
arguments are specied using a va_list (that is, variable argument list) or a jvalue array
(jvalue being an union of all JNI types):
jobject CallObjectMethodV(JNIEnv*, jobject, jmethodID, va_list);
jobject CallObjectMethodA(JNIEnv*, jobject, jmethodID, jvalue*);
Parameters passed to a Java method through JNI must use the available JNI type: jobject
for any object, jboolean for a boolean value, and so on. See the following table for a more
detailed list.
Look for jni.h in the Android NDK include directory to feel all the possibilies by JNI
reecve API.
JNI method denitions
Methods in Java can be overloaded. That means that there can be two methods with
the same name but dierent parameters. This is why a signature needs to be passed
to GetMethodID() and GetStaticMethodID().
Formally speaking, a signature is declared in the following way:
(<Parameter 1 Type Code>[<Parameter 1 Class>];...)<Return Type Code>
For example:
(Landroid/view/View;I)Z
Chapter 4
[ 135 ]
The following table summarizes the various types available in JNI with their code:
Java type Nave type Nave array type Type code Array type
code
boolean jboolean jbooleanArray Z [Z
byte jbyte jbyteArray B[B
char jchar jcharArray C [C
double jdouble jdoubleArray D[D
oat joat joatArray F[F
int jint jintArray I[I
long jlong jlongArray J [J
short jshort jshortArray S [S
Object jobject jobjectArray L [L
String jstring N/A L [L
Class jclass N/A L [L
Throwable jthrowable N/A L [L
void void N/A VN/A
Processing bitmaps natively
Android NDK proposes an API dedicated to bitmap processing which allows accessing
bitmap surface directly. This API is specic to Android and is not related to the JNI
specicaon. However, bitmaps are Java objects and will need to be treated as such
in nave code.
To see more concretely how bitmaps can be modied from nave code, let's try to
decode a camera feed from nave code. Android already features a Camera API on the
Java side to display a video feed. However, there is absolutely no exibility on how the feed is
displayed—it is drawn directly on a GUI component. To overcome this problem, snapshots can
be recorded into a data buer encoded in a specic format, YUV, which is not compable with
classic RGB images! This is a situaon where nave code comes to the rescue and can help us
improve performances.
The nal project is provided with this book under the
name LiveCamera.
Calling Java Back from Nave Code
[ 136 ]
Time for action – decoding camera feed from native code
1. Create a new hybrid Java/C++ project like shown in Chapter 2, Creang, Compiling,
and Deploying Nave Projects:
Name it LiveCamera.
Its main package is com.packtpub.
Its main acvity is LiveCameraActivity.
Get rid of res/main.xml as we will not create a GUI this me.
Do not forget to create a jni directory at project's root.
2. In the applicaon manifest, set the acvity style to fullscreen and its orientaon to
landscape. Landscape orientaon avoids most camera orientaon problems that can
be met on Android devices. Also request acces permission to the Android camera:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/
android"
package="com.packtpub" android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="10" />
<application android:icon="@drawable/icon"
android:label="@string/app_name">
<activity android:name=".LiveCameraActivity"
android:label="@string/app_name"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:screenOrientation="landscape">
...
</activity>
</application>
<uses-permission android:name="android.permission.CAMERA" />
</manifest>
Let's take care of the Java side. We need to create a component to display the
camera feed captured from the Android system class android.hardware.Camera.
3. Create a new class CameraView which extends andoid.View.SurfaceView
and implements Camera.PreviewCallback and SurfaceHolder.Callback.
SurfaceView is a visual component provided by Android to perform
custom rendering.
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
Chapter 4
[ 137 ]
Give CameraView the responsibility to load livecamera library, the nave
video decoding library we are about to create. This library will contain one
method decode() which will take raw video feed data in input and decode
it into a target Java bitmap:
public class CameraView extends SurfaceView implements
SurfaceHolder.Callback, Camera.PreviewCallback {
static {
System.loadLibrary("livecamera");
}
public native void decode(Bitmap pTarget, byte[] pSource);
...
4. Inialize CameraView component.
In its constructor, register it as a listener of its own surface events, that is, surface
creaon, destrucon, and change. Disable the willNotDraw ag to ensure its
onDraw() event is triggered as we are going to render the camera feed from the
main UI thread.
Render a SurfaceView from the main UI thread only if a
rendering operaon is not too me consuming or for prototyping
purposes. This can simplify code and avoid synchronizaon
concerns. However, SurfaceView is designed to be rendered
from a separate thread and should be generally used that way.
...
private Camera mCamera;
private byte[] mVideoSource;
private Bitmap mBackBuffer;
private Paint mPaint;
public CameraView(Context context) {
super(context);
getHolder().addCallback(this);
setWillNotDraw(false);
}
...
Calling Java Back from Nave Code
[ 138 ]
5. When surface is created, acquire the default camera (there can be a front
and rear camera, for example) and set its orientaon to landscape (like the
acvity). To draw the camera feed ourself, deacvate automac preview (that is,
setPreviewDisplay(), which causes the video feed to be automacally drawn
into SurfaceView) and request the use of data buers for recording instead:
...
public void surfaceCreated(SurfaceHolder holder) {
try {
mCamera = Camera.open();
mCamera.setDisplayOrientation(0);
mCamera.setPreviewDisplay(null);
mCamera.setPreviewCallbackWithBuffer(this);
} catch (IOException eIOException) {
mCamera.release();
mCamera = null;
throw new IllegalStateException();
}
}
...
6. Method surfaceChanged() is triggered (potenally several mes) aer surface
is created and, of course, before it is destroyed. This is the place where surface
dimensions and pixel format get known.
First, nd the resoluon that is closest to the surface. Then create a byte buer to
capture a raw camera snapshot and a backbuer bitmap to store the conversion
result. Set up camera parameters: the selected resoluon and the video format
(YCbCr_420_SP, which is the default on Android) and nally, start the recording.
Before a frame is recorded, a data buer must be enqueued to capture a snapshot:
...
public void surfaceChanged(SurfaceHolder pHolder, int pFormat,
int pWidth, int pHeight) {
mCamera.stopPreview();
Size lSize = findBestResolution(pWidth, pHeight);
PixelFormat lPixelFormat = new PixelFormat();
PixelFormat.getPixelFormatInfo(mCamera.getParameters()
.getPreviewFormat(), lPixelFormat);
int lSourceSize = lSize.width * lSize.height
* lPixelFormat.bitsPerPixel / 8;
mVideoSource = new byte[lSourceSize];
mBackBuffer = Bitmap.createBitmap(lSize.width,
lSize.height,Bitmap.Config.ARGB_8888);
Chapter 4
[ 139 ]
Camera.Parameters lParameters = mCamera.getParameters();
lParameters.setPreviewSize(lSize.width, lSize.height);
lParameters.setPreviewFormat(PixelFormat.YCbCr_420_SP);
mCamera.setParameters(lParameters);
mCamera.addCallbackBuffer(mVideoSource);
mCamera.startPreview();
}
...
7. An Android camera can support various resoluons which are highly dependent on
the device. As there is no rule on what could be the default resoluon, we need to
look for a suitable one. Here, we select the biggest resoluon that ts the display
surface or the default one if none can be found.
...
private Size findBestResolution(int pWidth, int pHeight) {
List<Size> lSizes = mCamera.getParameters()
.getSupportedPreviewSizes();
Size lSelectedSize = mCamera.new Size(0, 0);
for (Size lSize : lSizes) {
if ((lSize.width <= pWidth)
&& (lSize.height <= pHeight)
&& (lSize.width >= lSelectedSize.width)
&& (lSize.height >= lSelectedSize.height)) {
lSelectedSize = lSize;
}
}
if ((lSelectedSize.width == 0)
|| (lSelectedSize.height == 0)) {
lSelectedSize = lSizes.get(0);
}
return lSelectedSize;
}
...
8. In CameraView.java, release camera when surface is destroyed as it is a
shared resource. In memory, buers can also be nullied to facilitate garbage
collector work:
...
public void surfaceDestroyed(SurfaceHolder holder) {
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
Calling Java Back from Nave Code
[ 140 ]
mCamera = null;
mVideoSource = null;
mBackBuffer = null;
}
}
...
9. Now that surface is set up, decode video frames in onPreviewFrame() and store
the result in the backbuer bitmap. This handler is triggered by the Camera class
when a new frame is ready. Once decoded, invalidate the surface to redraw it.
To draw a video frame, override onDraw() and draw the backbuer into the target
canvas. Once done, we can re-enqueue the raw video buer to capture a new image.
The Camera component can enqueue several buers to
process a frame while others are geng captured. Although
this approach is more complex as it implies threading and
synchronizaon, it can achieve beer performance and can
handle punctual slow down. The single-threaded capture
algorithm shown here is simpler but much less ecient since a
new frame can only be recorded aer the previous one is drawn.
...
public void onPreviewFrame(byte[] pData, Camera pCamera) {
decode(mBackBuffer, pData);
invalidate();
}
@Override
protected void onDraw(Canvas pCanvas) {
if (mCamera != null) {
pCanvas.drawBitmap(mBackBuffer, 0, 0, mPaint);
mCamera.addCallbackBuffer(mVideoSource);
}
}
}
Chapter 4
[ 141 ]
10. Open the LiveCameraActivity.java le, which should have been
created by the Android project creaon wizard. Inialize the GUI with
a new CameraView instance.
public class LiveCameraActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new CameraView(this));
}
}
Now that the Java side is ready, we can write the decode() method on the
nave side.
11. Generate JNI header le with javah.
12. Create corresponding implementaon le com_packtpub_CameraView.c. Include
android/bitmap.h, which denes the NDK bitmap processing API. The following
are a few ulity methods to help decode video:
toInt(): This converts a jbyte to an integer, erasing all useless bits
with a mask.
max(): This gets the maximum between two values.
clamp(): This method is used to clamp a value inside a dened interval.
color(): This method builds an ARGB color from its component.
13. Make them inline to gain a bit of performance:
#include "com_packtpub_CameraView.h"
#include <android/bitmap.h>
inline int32_t toInt(jbyte pValue) {
return (0xff & (int32_t) pValue);
}
inline int32_t max(int32_t pValue1, int32_t pValue2) {
if (pValue1 < pValue2) {
return pValue2;
} else {
return pValue1;
}
}
Calling Java Back from Nave Code
[ 142 ]
inline int32_t clamp(int32_t pValue, int32_t pLowest, int32_t
pHighest) {
if (pValue < 0) {
return pLowest;
} else if (pValue > pHighest) {
return pHighest;
} else {
return pValue;
}
}
inline int32_t color(pColorR, pColorG, pColorB) {
return 0xFF000000 | ((pColorB << 6) & 0x00FF0000)
| ((pColorG >> 2) & 0x0000FF00)
| ((pColorR >> 10) & 0x000000FF);
}
...
14. Sll in the same le, implement decode(). First, retrieve bitmap informaon
and lock it for drawing with the AndroidBitmap_* API.
Then, gain access to the input Java byte array with
GetPrimitiveArrayCritical(). This JNI method is similar to
Get<Primitive>ArrayElements() except that the acquired array is less likely
to be a temporary copy. In return, no JNI or thread-blocking calls can be performed
unl the array is released.
...
JNIEXPORT void JNICALL Java_com_packtpub_CameraView_decode
(JNIEnv * pEnv, jclass pClass, jobject pTarget, jbyteArray
pSource) {
AndroidBitmapInfo lBitmapInfo;
if (AndroidBitmap_getInfo(pEnv, pTarget, &lBitmapInfo) < 0) {
return;
}
if (lBitmapInfo.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
return;
}
uint32_t* lBitmapContent;
if (AndroidBitmap_lockPixels(pEnv, pTarget,
(void**)&lBitmapContent) < 0) {
return;
}
Chapter 4
[ 143 ]
jbyte* lSource = (*pEnv)->GetPrimitiveArrayCritical(pEnv,
pSource, 0);
if (lSource == NULL) {
return;
}
...
15. Connue decode()method. We have access to the input video buer with a video
frame inside and to the backbuer bitmap surface. So we can decode the video feed
into the output backbuer.
The video frame is encoded in the YUV format, which is quite dierent from RGB.
YUV format encodes a color in three components:
One luminance component, that is, the grayscale representaon of a color.
Two chrominance components which encode the color informaon (also
called Cb and Cr as they represent the blue-dierence and red-dierence).
16. There are many frames available whose format is based on YUV colors. Here,
we convert frames following the YCbCr 420 SP (or NV21) format. This kind of
image frame is composed of a buer of 8 bits Y luminance samples followed by a
second buer of interleaved 8 bits V and U chrominance samples. The VU buer
is subsampled, which means that there are less U and V samples compared to Y
samples (1 U and 1 V for 4 Y). The following algorithm processes each pixel and
converts each YUV pixel to RGB using the appropriate formula (see http://www.
fourcecc.org/fccyvrgb.php for more informaon).
17. Terminate decode() method by unlocking the backbuer bitmap and releasing the
Java array acquired earlier:
...
int32_t lFrameSize = lBitmapInfo.width * lBitmapInfo.height;
int32_t lYIndex, lUVIndex;
int32_t lX, lY;
int32_t lColorY, lColorU, lColorV;
int32_t lColorR, lColorG, lColorB;
int32_t y1192;
// Processes each pixel and converts YUV to RGB color.
for (lY = 0, lYIndex = 0; lY < lBitmapInfo.height; ++lY) {
lColorU = 0; lColorV = 0;
// Y is divided by 2 because UVs are subsampled vertically.
// This means that two consecutives iterations refer to the
Calling Java Back from Nave Code
[ 144 ]
// same UV line (e.g when Y=0 and Y=1).
lUVIndex = lFrameSize + (lY >> 1) * lBitmapInfo.width;
for (lX = 0; lX < lBitmapInfo.width; ++lX, ++lYIndex) {
// Retrieves YUV components. UVs are subsampled
// horizontally too, hence %2 (1 UV for 2 Y).
lColorY = max(toInt(lSource[lYIndex]) - 16, 0);
if (!(lX % 2)) {
lColorV = toInt(lSource[lUVIndex++]) - 128;
lColorU = toInt(lSource[lUVIndex++]) - 128;
}
// Computes R, G and B from Y, U and V.
y1192 = 1192 * lColorY;
lColorR = (y1192 + 1634 * lColorV);
lColorG = (y1192 - 833 * lColorV - 400 * lColorU);
lColorB = (y1192 + 2066 * lColorU);
lColorR = clamp(lColorR, 0, 262143);
lColorG = clamp(lColorG, 0, 262143);
lColorB = clamp(lColorB, 0, 262143);
// Combines R, G, B and A into the final pixel color.
lBitmapContent[lYIndex] = color(lColorR,lColorG,lColorB);
}
}
(*pEnv)-> ReleasePrimitiveArrayCritical(pEnv,pSource,lSource,0);
AndroidBitmap_unlockPixels(pEnv, pTarget);
}
18. Write livecamera library Android.mk. Link it to jnigraphics NDK module:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := livecamera
LOCAL_SRC_FILES := com_packtpub_CameraView.c
LOCAL_LDLIBS := -ljnigraphics
include $(BUILD_SHARED_LIBRARY)
19. Compile and run the applicaon.
Chapter 4
[ 145 ]
What just happened?
Right aer starng the applicaon, the camera feed should appear on your device screen.
Video is decoded in nave code into a Java bitmap which is then drawn into the display
surface. Accessing the video feed navely allow much faster processing than what could
be done with classic Java code (see Chapter 11, Debugging and Troubleshoong for further
opmizaons with the NEON instrucon set). It opens many new possibilies: image
processing, paern recognion, augmented reality, and so on.
Bitmap surface is accessed directly by nave code thanks to the Android NDK Bitmap library
dened in library in jnigraphics. Drawing occurs in three steps:
1. Bitmap surface is acquired.
2. Video pixels are converted to RGB and wrien to bitmap surface.
3. Bitmap surface is released.
Bitmaps must be systemacally locked and then released to access them
navely. Drawing operaons must occur between a lock/release pair.
Video decoding and rendering is performed with with a non-threaded SurfaceView,
although this process could be made more ecient with a second thread. Multhreading
can be introduced thanks to the buer queue system introduced in latest releases of the
Android Camera component. Do not forget that YUV to RGB is an expensive operaon that is
likely to remain a point of contenon in your program.
Adapt snapshot size to your needs. Indeed, beware of the surface to process
quadruple when snapshot's size doubles. If feedback is not too important,
snapshot size can be parally reduced (for example, for paern recognion in
Augmented Reality). If you can, draw directly to the display window surface
instead of going through a temporary buer.
The video feed is encoded in the YUV NV21 format. YUV is a color format originally invented
in the old days of electronics to make black-and-white video receivers compable with color
transmissions and sll commonly used nowadays. Default frame format is guaranteed by the
Android specicaon to be YCbCr 420 SP (or NV21) on Android. The algorithm used to decode
the YUV frame originates from the Ketai open source project, an image and sensor processing
library for Android. See http://ketai.googlecode.com/ for more informaon.
Calling Java Back from Nave Code
[ 146 ]
Although YCbCr 420 SP is the default video format on Android, the emulator
only supports YCbCr 422 SP. This defect should not cause much trouble as it
basically swaps colors. This problem should not occur on real devices.
Summary
We have seen more in-depth how to make Java and C/C++ communicate together. Android is
now fully bilingual! Java can call C/C++ code with any type of data or object and nave code
can call Java back. We have discovered, in more detail, how to aach and detach a thread to
the VM and synchronize Java and nave threads together with JNI monitors. Then we saw how
to call Java code from nave code with the JNI Reecon API. Praccally any Java operaon
can be performed from nave code thanks to it. However, for best performance, class, method,
or elds descriptor must be cached. Finally, we have processed bitmaps navely thanks to JNI
and decoded a video feed manually. But an expensive conversion is needed from default YUV
format (which should be supported on every device according to Android specicaon) to RGB.
When dealing with nave code on Android, JNI is almost always somewhere in the way.
Sadly, it is a verbose and cumbersome API which requires lot of setup and care. JNI is full of
subtlees and would require a whole book for an in-depth understanding. This chapter gave
you the essenal knowledge to get started. In the next chapter, we are going to see how to
create a fully nave applicaon, which gets completely rid of JNI.
5
Writing a Fully-native Application
In previous chapters, we have breached Android NDK's surface using JNI. But
there is much more to nd inside! NDK R5 is a major release which has seen
several long-awaited features nally delivered, one of them is nave acvies.
Nave acvies allow creang applicaons based only on nave code, without
a single line of Java. No more JNI! No more references! No more Java!
In addion to nave acvies, NDK R5 has brought some APIs for nave
access to some Android resources such as display windows, assets, device
conguraon… These APIs help dismantle the JNI bridge, oen necessary to
develop nave applicaons opened to their host environment. Although sll a
lot is missing and is not likely to be available (Java remains the main plaorm
language for GUIs and most frameworks), mulmedia applicaons are a
perfect target to apply them.
I propose now to enter into the heart of the Android NDK by:
Creang a fully nave acvity
Handling main acvity events
Accessing display window navely
Retrieving me and calculang delays
The present chapter iniates a nave C++ project developed progressively throughout this
book: DroidBlaster. Based on a top-down viewpoint, this sample scrolling shooter will
feature 2D graphics, and later on 3D graphics, sound, input, and sensor management.
In this chapter, we are going to create its base structure.
Wring a Fully-nave Applicaon
[ 148 ]
Creating a native activity
The class NativeActivity provides a facility to minimize the work necessary to create
a nave applicaon. It lets the developer get rid of all the boilerplate code to inialize and
communicate with nave code and concentrate on core funconalies. In this rst part,
we are going to see how to create a minimal nave acvity that runs an event loop.
The resulng project is provided with this book under the
name DroidBlaster_Part5-1.
Time for action – creating a basic native activity
First, let's create DroidBlaster project:
1. In Eclipse, create a new project Android project with the following sengs:
Enter Eclipse project name: DroidBlaster.
Set Build target to Android 2.3.3.
Enter Applicaon name: DroidBlaster.
Enter Package name: com.packtpub.droidblaster.
Uncheck Create Acvity.
Set Min SDK Version to 10.
2. Once the project is created, go to the res/layout directory and remove main.
xml. This UI descripon le is not needed in our nave applicaon. You can also
remove src directory as DroidBlaster will not contain even a piece of Java code.
3. The applicaon is compilable and deployable, but not runnable simply because
we have not created an acvity yet. Let's declare NativeActivity in the
AndroidManifest.xml le at the project's root. The declared nave acvity refers
to a nave module named droidblaster (property android.app.lib_name):
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/
android"
package="com.packtpub.droidblaster" android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="10"/>
<application android:icon="@drawable/icon"
android:label="@string/app_name">
Chapter 5
[ 149 ]
<activity android:name="android.app.NativeActivity"
android:label="@string/app_name">
<meta-data android:name="android.app.lib_name"
android:value="droidblaster"/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
Let's set up the Eclipse project to compile nave code:
4. Convert the project to a hybrid C++ project (not C) using Convert C/C++
Project wizard.
5. Then, go to project, select Properes in C/C++ Build secon and change default
build command to ndk-build.
6. In the Path and Symbols/Includes secon, add Android NDK include directories to all
languages as seen in Chapter 2, Creang, Compiling, and Deploying Nave Projects:
${env_var:ANDROID_NDK}/platforms/android-9/arch-arm/usr/include
${env_var:ANDROID_NDK}/toolchains/arm-linux-androideabi-4.4.3/
prebuilt/<your OS>/lib/gcc/arm-linux-androideabi/4.4.3/include
7. Sll in the same secon, add nave app glue directory to all languages. Validate and
close the project Properes dialog:
${env_var:ANDROID_NDK}/sources/android/native_app_glue
8. Create directory jni at the project's root containing the following Android.mk le.
It describes the C++ les to compile and the native_app_glue module to link to.
The nave glue binds together nave code and NativeActivity:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := droidblaster
LOCAL_SRC_FILES := Main.cpp EventLoop.cpp Log.cpp
LOCAL_LDLIBS := -landroid -llog
LOCAL_STATIC_LIBRARIES := android_native_app_glue
Wring a Fully-nave Applicaon
[ 150 ]
include $(BUILD_SHARED_LIBRARY)
$(call import-module,android/native_app_glue)
Now we can start wring some nave code that runs inside the nave acvity.
Let's begin with some ulity code:
9. In jni directory, create a le Types.hpp. This header will contain common types
and the header stdint.h:
#ifndef _PACKT_TYPES_HPP_
#define _PACKT_TYPES_HPP_
#include <stdint.h>
#endif
10. To sll get some feedback without the ability to input or output anything from or
to the screen, let's write a logging class. Create Log.hpp and declare a new class
Log. You can dene packt_Log_debug macro to acvate debug messages with
a simple ag:
#ifndef PACKT_LOG_HPP
#define PACKT_LOG_HPP
namespace packt {
class Log {
public:
static void error(const char* pMessage, ...);
static void warn(const char* pMessage, ...);
static void info(const char* pMessage, ...);
static void debug(const char* pMessage, ...);
};
}
#ifndef NDEBUG
#define packt_Log_debug(...) packt::Log::debug(__VA_ARGS__)
#else
#define packt_Log_debug(...)
#endif
#endif
Chapter 5
[ 151 ]
By default, NDEBUG macro is dened by the NDK compilaon toolchain.
To undened it, the applicaon has to be made debuggable in its
manifest: <application android:debuggable="true" …>
11. Create Log.cpp le and implement method info(). To write messages to Android
logs, the NDK provides a dedicated logging API in header android/log.h. which
can be used similarly to printf() and vprintf() (with varargs) in C:
#include "Log.hpp"
#include <stdarg.h>
#include <android/log.h>
namespace packt {
void Log::info(const char* pMessage, ...) {
va_list lVarArgs;
va_start(lVarArgs, pMessage);
__android_log_vprint(ANDROID_LOG_INFO, "PACKT", pMessage,
lVarArgs);
__android_log_print(ANDROID_LOG_INFO, "PACKT", "\n");
va_end(lVarArgs);
}
}
12. Other log methods are almost idencal. The only piece of code which changes
between each method is the level macro: ANDROID_LOG_ERROR, ANDROID_LOG_
WARN, and ANDROID_LOG_DEBUG instead.
Finally, we can write the code to poll acvity events:
13. Applicaon events have to be processed in an event loop. To do so, sll in jni
directory, create EventLoop.hpp dening the eponym class with a unique
method run().
Included header android_native_app_glue.h denes android_app structure,
which represents what could be called an "applicave context", with all informaon
related to the nave acvity: its state, its window, its event queue, and so on:
#ifndef _PACKT_EVENTLOOP_HPP_
#define _PACKT_EVENTLOOP_HPP_
#include "Types.hpp"
Wring a Fully-nave Applicaon
[ 152 ]
#include <android_native_app_glue.h>
namespace packt {
class EventLoop {
public:
EventLoop(android_app* pApplication);
void run();
private:
android_app* mApplication;
};
}
#endif
14. Create EventLoop.cpp and implement acvity event loop in method run
()as follows. Include a few log events to get some feedback in Android log.
During the whole acvity lifeme, run() loops connuously over events
unl it is requested to terminate. When an acvity is about to be destroyed,
destroyRequested value in android_app structure is changed internally
to nofy the event loop:
#include "EventLoop.hpp"
#include "Log.hpp"
namespace packt {
EventLoop::EventLoop(android_app* pApplication) :
mApplication(pApplication)
{}
void EventLoop::run() {
int32_t lResult;
int32_t lEvents;
android_poll_source* lSource;
app_dummy();
packt::Log::info("Starting event loop");
while (true) {
while ((lResult = ALooper_pollAll(-1, NULL, &lEvents,
(void**) &lSource)) >= 0)
{
if (lSource != NULL) {
packt::Log::info("Processing an event");
Chapter 5
[ 153 ]
lSource->process(mApplication, lSource);
}
if (mApplication->destroyRequested) {
packt::Log::info("Exiting event loop");
return;
}
}
}
}
}
15. Finally, create the main entry point running the event loop in a new le Main.cpp:
#include "EventLoop.hpp"
void android_main(android_app* pApplication) {
packt::EventLoop lEventLoop(pApplication);
lEventLoop.run();
}
16. Compile and run the applicaon.
What just happened?
Of course, you will not see anything tremendous when starng this applicaon. Actually,
you will just see a black screen! But if you look carefully at the LogCat view in Eclipse
(or command adb logcat), you will discover a few interesng messages that have
been emied by your nave applicaon in reacon to acvity events:
We have iniated a Java Android project without a single line of Java code! Instead of a new
Java Activity child class, in AndroidManifest.xml, we have referenced the android.
app.NativeActivity class, which is launched like any other Android acvity.
Wring a Fully-nave Applicaon
[ 154 ]
NativeActivity is a Java class. Yes, a Java class. But we never confronted to it directly.
NativeActivity is in fact a helper class provided with Android SDK and which contains
all the necessary glue code to handle applicaon lifecycle and events and broadcast them
transparently to nave code. Being a Java class, NativeActivity runs, of course, on the
Dalvik Virtual Machine and is interpreted like any Java class.
A nave acvity does not eliminate the need for JNI. In fact, it just hides it!
Hopefully, we never face NativeActivity directly. Even beer, the C/C++
module run by a NativeActivity runs outside Dalvik boundaries in its
own thread… enrely navely!
NativeActivity and nave code are connected together through the native_app_glue
module. Nave glue has the responsibility of:
launching the nave thread which runs our own nave code
receiving events from NativeActivity
roung these events to the nave thread event loop for further processing
Our own nave code entry point is declared at step 15 with an android_main() method
similar to main methods in desktop applicaons. It is called once when a nave applicaon
is launched and loops over applicaon events unl NativeActivity is terminated by user
(for example, when pressing device back buon). The android_main() method runs the
nave event loop, which is itself composed of two nested while loops. The outer one is an
innite loop, terminated only when applicaon destrucon is requested. Destrucon request
ag can be found in android_app "applicaon context" provided as an argument to the
android_main() method by the nave glue.
Inside the main loop is an inner loop which processes all pending events with a call to
ALooper_pollAll(). This method is part of the ALooper API which is a general-purpose
event loop manager provided by Android. When meout is -1 like at step 14, ALooper_
pollAll() remains blocked while waing for events. When at least one is received,
ALooper_pollAll() returns and code ow connues. The android_poll_source
structure describing the event is lled and used for further processing.
If an event loop was a heart, then event polling would be a heartbeat. In
other words, polling makes your applicaon alive and reacve to the outside
world. It is not even possible to leave a nave acvity without polling events;
destrucon is itself an event!
Chapter 5
[ 155 ]
Handling activity events
In the rst part, we have run a nave event loop which ushes events without really
processing them. In this second part, we are going to discover more about these events
occurring during acvity lifecycle. Let's extend the previous example to log all events
that a nave acvity is confronted to.
EventLoop DroidBlaster
ActivityHandler
Log
Project DroidBlaster_Part5-1 can be used as a starng point for this
part. The resulng project is provided with this book under the name
DroidBlaster_Part5-2.
Time for action – handling activity events
Let's improve the code created in the previous part:
1. Open Types.hpp and dene a new type status to represent return codes:
#ifndef _PACKT_TYPES_HPP_
#define _PACKT_TYPES_HPP_
#include <stdint.h>
typedef int32_t status;
const status STATUS_OK = 0;
const status STATUS_KO = -1;
const status STATUS_EXIT = -2;
#endif
2. Create ActivityHandler.hpp in jni directory. This header denes an interface
to observe nave acvity events. Each possible event has its own handler method:
onStart(), onResume(), onPause(), onStop(), onDestroy(), and so on.
However, we are generally interested in three specic moments in the acvity lifecycle:
onActivate(): This method is invoked when acvity is resumed and its
window is available and focused.
Wring a Fully-nave Applicaon
[ 156 ]
onDeactivate(): This acvity is invoked when acvity is paused or the
display window loses its focus or is destroyed.
onStep(): This acvity is invoked when no event has to be processed
and computaons can take place.
#ifndef _PACKT_EVENTHANDLER_HPP_
#define _PACKT_EVENTHANDLER_HPP_
#include "Types.hpp"
namespace packt {
class EventHandler {
public:
virtual status onActivate() = 0;
virtual void onDeactivate() = 0;
virtual status onStep() = 0;
virtual void onStart() {};
virtual void onResume() {};
virtual void onPause() {};
virtual void onStop() {};
virtual void onDestroy() {};
virtual void onSaveState(void** pData,
int32_t* pSize) {};
virtual void onConfigurationChanged() {};
virtual void onLowMemory() {};
virtual void onCreateWindow() {};
virtual void onDestroyWindow() {};
virtual void onGainFocus() {};
virtual void onLostFocus() {};
};
}
#endif
All these events have to be triggered from the acvity event loop.
3. Open exisng le EventLoop.hpp. Although its public face is conserved,
EventLoop class is enhanced with two internal methods (activate() and
deactivate()) and two state variables (mEnabled and mQuit) to save acvity
acvaon state. Real acvity events are handled in processActivityEvent()
and its corresponding callback activityCallback(). These events are routed
to mActivityHandler event observer:
Chapter 5
[ 157 ]
#ifndef _PACKT_EVENTLOOP_HPP_
#define _PACKT_EVENTLOOP_HPP_
#include "EventHandler.hpp"
#include "Types.hpp"
#include <android_native_app_glue.h>
namespace packt {
class EventLoop {
public:
EventLoop(android_app* pApplication);
void run(EventHandler& pEventHandler);
protected:
void activate();
void deactivate();
void processActivityEvent(int32_t pCommand);
private:
static void activityCallback(android_app* pApplication,
int32_t pCommand);
private:
bool mEnabled; bool mQuit;
ActivityHandler* mActivityHandler;
android_app* mApplication;
};
}
#endif
4. Open and edit EventLoop.cpp. Constructor inializaon list is trivial to implement.
However, the android_app applicaon context needs to be lled with some
addional informaon:
onAppCmd: This points to an internal callback triggered each me an
event occurs. In our case, this is the role devoted to the stac method
activityCallback.
userData: This is a pointer in which you can assign any data you want.
This piece of data is the only informaon accessible from the callback
declared previously (except global variables). In our case, this is the
EventLoop instance (this).
Wring a Fully-nave Applicaon
[ 158 ]
#include "EventLoop.hpp"
#include "Log.hpp"
namespace packt {
EventLoop::EventLoop(android_app* pApplication) :
mEnabled(false), mQuit(false),
mApplication(pApplication),
mActivityHandler(NULL) {
mApplication->onAppCmd = activityCallback;
mApplication->userData = this;
}
...
5. Update the run() main event loop to stop blocking while polling events. Indeed,
ALooper_pollAll() behavior is dened by its rst parameter, meout:
When meout is -1 like at step 14, call is blocking unl events are received.
When meout is 0, call is non-blocking so that if nothing remains in the
queue, program ow connues (inner while loop is terminated) and makes
it possible to perform recurrent processing.
When meout is greater than 0, then we have a blocking call which remains
unl an event is received or the duraon is elapsed.
Here, we want to step the acvity (that is, perform computaons)
when it is in acve state (mEnabled is true): in that case, meout is 0.
When acvity is in deacvated state (mEnabled is false), events are sll
processed (for example, to resurrect the acvity) but nothing needs to
get computed. The thread has to be blocked to avoid consuming baery
and processor me uselessly: meout is -1.
To leave the applicaon programmacally, NDK API provides
ANativeActivity_finish()method to request acvity terminaon.
Terminaon does not occur immediately but aer a few events (pause,
stop, and so on)!
...
void EventLoop::run(ActivityHandler& pActivityHandler)
{
int32_t lResult;
int32_t lEvents;
android_poll_source* lSource;
app_dummy();
mActivityHandler = &pActivityHandler;
Chapter 5
[ 159 ]
packt::Log::info("Starting event loop");
while (true) {
while ((lResult = ALooper_pollAll(mEnabled ? 0 : -1,
NULL, &lEvents, (void**) &lSource)) >= 0) {
if (lSource != NULL) {
packt::Log::info("Processing an event");
lSource->process(mApplication, lSource);
}
if (mApplication->destroyRequested) {
packt::Log::info("Exiting event loop");
return;
}
}
if ((mEnabled) && (!mQuit)) {
if (mActivityHandler->onStep() != STATUS_OK) {
mQuit = true;
ANativeActivity_finish(mApplication->activity);
}
}
}
}
...
6. Sll in EventLoop.cpp, implement activate() and deactivate(). Both check
acvity state before nofying the observer (to avoid unmely triggering). As stated
earlier, acvaon requires a window to be available before going further:
...
void EventLoop::activate() {
if ((!mEnabled) && (mApplication->window != NULL)) {
mQuit = false; mEnabled = true;
if (mActivityHandler->onActivate() != STATUS_OK) {
mQuit = true;
ANativeActivity_finish(mApplication->activity);
}
}
}
void EventLoop::deactivate()
{
if (mEnabled) {
mActivityHandler->onDeactivate();
mEnabled = false;
}
}
...
Wring a Fully-nave Applicaon
[ 160 ]
7. Finally, implement processActivityEvent() and its companion callback
activityCallback(). Do you remember the onAppCmd and userData elds
from android_app structure that we inialized in the constructor? They are used
internally by the nave glue to trigger the right callback (here activityCallback())
when an event occurs. The EventLoop object is goen back thanks to the userData
pointer (this being unavailable from a stac method). Eecve event processing is
then delegated to processActivityEvent(), which brings us back into the
object-oriented world.
Parameter pCommand contains an enumeraon value (APP_CMD_*) which describes
the occurring event (APP_CMD_START, APP_CMD_GAINED_FOCUS, and so on). Once
an event is analyzed, acvity is acvated or deacvated depending on the event and
the observer is noed.
A few events such as APP_CMD_WINDOW_RESIZED are available but never
triggered. Do not listen to them unless you are ready to sck your hands in
the glue…
Acvaon occurs when acvity gains focus. This event is always the last event that
occurs aer acvity is resumed and window is created. Geng focus means that
acvity can receive input events. Thus, it would be possible to acvate the event
loop as soon as window is created.
Deacvaon occurs when window loses focus or applicaon is paused (both can occur
rst). By security, deacvaon is also performed when window is destroyed although
this should always occur aer focus is lost. Losing focus means that applicaon does
not receive input events anymore. Thus, it would also be possible to deacvate the
event loop only when window is destroyed instead:
To make your acvity lose and gain focus easily, just press your device
home buon to display the Recent applicaons pop up (which may be
manufacturer specic). If acvaon and deacvaon occur on a focus
change, acvity pauses immediately. Otherwise, it would keep working in the
background unl another acvity is selected (which could be desirable).
...
void EventLoop::processActivityEvent(int32_t pCommand) {
switch (pCommand) {
case APP_CMD_CONFIG_CHANGED:
mActivityHandler->onConfigurationChanged();
break;
case APP_CMD_INIT_WINDOW:
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
Chapter 5
[ 161 ]
mActivityHandler->onCreateWindow();
break;
case APP_CMD_DESTROY:
mActivityHandler->onDestroy();
break;
case APP_CMD_GAINED_FOCUS:
activate();
mActivityHandler->onGainFocus();
break;
case APP_CMD_LOST_FOCUS:
mActivityHandler->onLostFocus();
deactivate();
break;
case APP_CMD_LOW_MEMORY:
mActivityHandler->onLowMemory();
break;
case APP_CMD_PAUSE:
mActivityHandler->onPause();
deactivate();
break;
case APP_CMD_RESUME:
mActivityHandler->onResume();
break;
case APP_CMD_SAVE_STATE:
mActivityHandler->onSaveState(&mApplication->savedState,
&mApplication->savedStateSize);
break;
case APP_CMD_START:
mActivityHandler->onStart();
break;
case APP_CMD_STOP:
mActivityHandler->onStop();
break;
case APP_CMD_TERM_WINDOW:
mActivityHandler->onDestroyWindow();
deactivate();
break;
default:
break;
}
}
void EventLoop::activityCallback(android_app* pApplication,
int32_t pCommand)
Wring a Fully-nave Applicaon
[ 162 ]
{
EventLoop& lEventLoop = *(EventLoop*) pApplication->userData;
lEventLoop.processActivityEvent(pCommand);
}
}
Finally, we can implement applicaon-specic code.
8. Create a DroidBlaster.hpp le which implements ActivityHandler interface:
#ifndef _PACKT_DROIDBLASTER_HPP_
#define _PACKT_DROIDBLASTER_HPP_
#include "ActivityHandler.hpp"
#include "Types.hpp"
namespace dbs {
class DroidBlaster : public packt::ActivityHandler {
public:
DroidBlaster();
virtual ~DroidBlaster();
protected:
status onActivate();
void onDeactivate();
status onStep();
void onStart();
void onResume();
void onPause();
void onStop();
void onDestroy();
void onSaveState(void** pData; int32_t* pSize);
void onConfigurationChanged();
void onLowMemory();
void onCreateWindow();
void onDestroyWindow();
void onGainFocus();
void onLostFocus();
};
}
#endif
Chapter 5
[ 163 ]
9. Create DroidBlaster.cpp implementaon. To keep this introducon to the
acvity lifecycle simple, we are just going to log events for each occurring event.
Computaons are limited to a simple thread sleep:
#include "DroidBlaster.hpp"
#include "DroidBlaster.hpp"
#include "Log.hpp"
#include <unistd.h>
namespace dbs {
DroidBlaster::DroidBlaster() {
packt::Log::info("Creating DroidBlaster");
}
DroidBlaster::~DroidBlaster() {
packt::Log::info("Destructing DroidBlaster");
}
status DroidBlaster::onActivate() {
packt::Log::info("Activating DroidBlaster");
return STATUS_OK;
}
void DroidBlaster::onDeactivate() {
packt::Log::info("Deactivating DroidBlaster");
}
status DroidBlaster::onStep() {
packt::Log::info("Starting step");
usleep(300000);
packt::Log::info("Stepping done");
return STATUS_OK;
}
void DroidBlaster::onStart() {
packt::Log::info("onStart");
}
...
}
Wring a Fully-nave Applicaon
[ 164 ]
10. Let's not forget to inialize our acvity and its new event handler DroidBlaster:
#include "DroidBlaster.hpp"
#include "EventLoop.hpp"
void android_main(android_app* pApplication) {
packt::EventLoop lEventLoop(pApplication);
dbs::DroidBlaster lDroidBlaster;
lEventLoop.run(lDroidBlaster);
}
11. Update the Android.mk Makele to include all the new .cpp les created
in the present part. Then compile and run the applicaon.
What just happened?
If you like black screen, you are served! Again, everything happens in the Eclipse LogCat
view. All messages that have been emied by your nave applicaon in reacon to
applicaon events are displayed there:
We have created a minimalist framework which handles applicaon events in the nave
thread using an event-driven approach. These events are redirected to an observer object
which performs its own specic computaons. Nave acvity events correspond mostly
to Java acvity events. Following is an important schemac inspired from ocial Android
documentaon showing events that can happen during an acvity lifecycle:
Chapter 5
[ 165 ]
Activity is running
Another activity comes
in front of the activity
Other applications
need memory
Activity is shut down
User navigates
back to the
activity
Process is killed
The activity
comes to the
foreground
The activity
comes to the
foreground
onRestart()
onCreate()
onStart()
onResume()
onCreatewindow()
onGainFocus()
onSaveInstanceState()
onLoseFocus()
onDestroyWindow()
onPause()
The activity
in no longer visible
onStop()
onDestroy()
Activity starts
See http://developer.android.com/reference/android/app/Activity.
html for more informaon.
Wring a Fully-nave Applicaon
[ 166 ]
Events are a crical point that any applicaon needs to handle properly. Although event
pairs, that is, start/stop, resume/pause, create/destroy window, and gain/lose focus occur
most of the me in a predetermined order, some specic cases generate dierent behaviors,
for example:
Pressing for a long me the device home buon and then geng back should cause
a loss and gain of focus only
Shung down phone screen and switching it back on should cause window to
be terminated and reinialized immediately right aer acvity is resumed
When changing screen orientaon, the whole acvity may not lose its focus
although it will regain it aer acvity is recreated
Choice has been made to use a simplied event handling model in
DroidBlaster, with only three main events occurring in the applicaon
lifecycle (acvaon, deacvaon, and stepping). However, an applicaon can be
made more ecient by performing more subtle event handling. For example,
pausing an acvity may not release resources whereas a stop event should.
Have a look at the NVIDIA developer site where you will nd interesng documents
about Android events and even more: http://developer.nvidia.com/content/
resources-android-native-game-development-available.
More on Native App Glue
You may sll wonder what the nave glue framework does exactly behind your back and
how. The truth is android_main() is not the real nave applicaon entry point. The real
entry point is ANativeActivity_onCreate() method hidden in the android_native_
app_glue module. The event loop we have seen unl now is in fact a delegate event loop
launched in its own nave thread by the glue code so that your android_main() is not
correlated anymore to NativeActivity on the Java side. Thus, even if your code takes a
long me to handle an event, NativeActivity is not blocked and your Android device sll
remains responsive. Nave glue module code is located in ${ANDROID_NDK}/sources/
android/native_app_glue and can be modied or forked at will (see Chapter 9, Porng
Exisng Libraries to Android).
android_nave_app_glue ease your life
The nave glue really simplies code by handling inializaon and
system-related stu that most applicaons do not need to worry about
(synchronizaon with mutexes, pipe communicaon, and so on). It frees
the UI thread from its load to keep device ability to handle unexpected events
such as a sudden phone call.
Chapter 5
[ 167 ]
UI thread
The following call hierarchy is an overview of how Nave App Glue proceeds internally
on the UI thread (that is, on the Java side):
Main Thread
NativeActivity
+___ANativeActivity_onCreate(ANativeActivity, void*, size_t)
+___android_app_create(ANativeActivity*, void*, size_t)
ANativeActivity_onCreate() is the real nave-side entry point and is executed on
the UI thread. The given ANativeActivity structure is lled with event callbacks used in
the nave glue code: onDestroy, onStart, onResume, and so on. So when something
happens in NativeActivity on the Java side, callback handlers are immediately triggered
on the nave side but sll on the UI thread. Processing performed by these handlers is very
simple: they nofy the nave thread by calling internal method android_app_write_
cmd(). Here is a list of some of the occurring events:
onStart, onResume,
onPause, onStop
changes the applicaon state by seng
android_app.activityState with the
appropriate APP_CMD_* value.
onSaveInstance sets the applicaon state to APP_CMD_SAVE_
STATE and waits for the nave applicaon
to save its state. Custom saving has to be
implemented by Nave App Glue client in its own
command callback.
onDestroy noes the nave thread that destrucon is
pending, and then frees memory when nave
thread acknowledges (and does what it needs
to frees resources!). Structure android_app
is not useable anymore and applicaon itself
terminates.
onConfigurationChanged,
onWindowFocusedChanged,
onLowMemory
noes the nave-side thread of the event (APP_
CMD_GAINED_FOCUS, APP_CMD_LOST_
FOCUS, and so on).
onNativeWindowCreated and
onNativeWindowDestroyed
calls funcon android_app_set_window()
which provides and requests the nave thread to
change its display window.
onInputQueueCreated and
onInputQueueDestoyed
uses a specc method android_app_set_
input() to register an input queue. Input
queue comes from NativeActivity and is
usually provided aer nave thread loop has
started.
Wring a Fully-nave Applicaon
[ 168 ]
ANativeActivity_onCreate() also allocates memory and inializes the applicaon
context android_app and all the synchronizaon stu. Then the nave thread itself is
"forked", so that it can live its life. Thread is created with entry point android_app_entry.
Main UI thread and nave thread communicates via Unix pipes and mutexes to ensure
proper synchronizaon.
Native thread
The nave thread call tree is a bit harsher! If you plan to create your own glue code,
you will probably need to implement something similar:
+___android_app_entry(void*)
+___AConfiguration_new()
+___AConfiguration_fromAssetManager(AConfiguration*,
| AAssetManager*)
+___print_cur_config(android_app*)
+___process_cmd(android_app*, android_poll_source*)
| +___android_app_read_cmd(android_app*)
| +___android_app_pre_exec_cmd(android_app*, int8_t)
| | +___AInputQueue_detachLooper(AInputQueue*)
| | +___AInputQueue_attachLooper(AInputQueue*,
| | | ALooper*, int, ALooper_callbackFunc, void*)
| | +___AConfiguration_fromAssetManager(AConfiguration*,
| | | AAssetManager*)
| | +___print_cur_config(android_app*)
| +___android_app_post_exec_cmd(android_app*, int8_t)
+___process_input(android_app*, android_poll_source*)
| +___AInputQueue_getEvent(AInputQueue*, AInputEvent**)
| +___AInputEvent_getType(const AInputEvent*)
| +___AInputQueue_preDispatchEvent(AInputQueue*,
| | AInputEvent*)
| +___AInputQueue_finishEvent(AInputQueue*,
| AInputEvent*, int)
+___ALooper_prepare(int)
+___ALooper_addFd(ALooper*, int, int, int,
| ALooper_callbackFunc, void*)
+___android_main(android_app*)
+___android_app_destroy(android_app*)
+___AInputQueue_detachLooper(AInputQueue*)
+___AConfiguration_delete(AConfiguration*)
Chapter 5
[ 169 ]
Let's see in detail what this means. Method android_app_entry() is executed exclusively
on the nave thread and performs several tasks. First, it creates the Looper, which processes
the event queue by reading data coming into the pipe (idened by a Unix File Descriptor).
Creaon of the command queue Looper is performed by ALooper_prepare() when nave
thread starts (something similar exists in Java in the equivalent class Looper). Aachment of
the Looper to the pipe is performed by ALooper_addFd().
Queues are processed by Nave App Glue internal methods process_cmd() and
process_input() for the command and input queue, respecvely. However both
are triggered by your own code when you write lSource->process() in your
android_main(). Then, internally, process_cmd() and process_input() calls
itself your own callback, the one we created in Activity.cpp. So nally we know
what is happening when we receive an event in our main loop!
The input queue is also aached to the looper, but not immediately inside thread entry
point. Instead, it is sent in diered-me from the main UI thread to the nave thread using
the pipe mechanism explained before. That explains why command queue is aached to the
looper and not the input queue. Input queue is aached to the looper through a specic API:
AInputQueue_attachLooper() and AInputQueue_detachLooper().
We have not talked about it yet but a third queue, the user queue, can be aached to the
looper. This queue is a custom one, unused by default and which can be used for your own
purpose. More generally, your applicaon can use the same ALooper to listen to addional
le-descriptors.
Now, the big part: android_main(). Our method! Our code! As you now know, it is
executed on the nave thread and loops innitely unl destrucon is requested. Destrucon
requests as well as all others events are detected by polling them, hence the method
ALooper_pollAll()used in DroidBlaster. We need to check each event that happens
unl nothing remains in the queue, then we can do whatever we want, like redrawing the
window surface, and then we go back to the wait state unl new events arrive.
Wring a Fully-nave Applicaon
[ 170 ]
Android_app structure
The nave event loop receives an android_app structure in parameter. This structure,
described in android_native_app_glue.h, contains some contextual informaon
such as:
void* userData: This is a pointer to any data you want. This is essenal to give
some contextual informaon to the acvity event callback.
void (*pnAppCmd)(…) int32_t (*onInputEvent)(…): These are callbacks
triggered respecvely when an acvity and an input event occur. We will see input
events in Chapter 8, Handling Input Devices and Sensors.
ANativeActivity* activity: This describes the Java nave acvity (its class as
a JNI object, its data directories, and so on) and gives the necessary informaon to
retrieve a JNI context.
AConfiguration* config: This contains informaon about current hardware
and system state, such as the current language and country, the current screen
orientaon, density, size, and so on This is a place of choice to learn more about
the host device.
void* savedState size_t savedStateSize: This is used to save a buer of
data when an acvity (and thus its nave thread) is destroyed and restored later.
AInputQueue* inputQueue: This handles input events (used internally by the
nave glue). We will see input events in Chapter 8.
ALooper* looper: This allows aaching and detaching event listeners (used
internally by the nave glue). The listeners poll and wait for events represented as
data on a Unix le descriptor.
ANativeWindow* window ARect contentRect: This represents the "drawable"
area, in which graphics can be drawn. The ANativeWindow API declared in
native_window.h allows retrieving window width, height and pixel format and
changing these sengs.
int activityState: This describes the current acvity state, that is, APP_CMD_
START, APP_CMD_RESUME, APP_CMD_PAUSE, and so on.
int destroyRequested: This is a ag when equals to 1, indicates that
applicaon is about to be destroyed and nave thread must be terminated
immediately. This ag has to be checked in the event loop.
The android_app structure also contains some internal data that should not be changed.
Chapter 5
[ 171 ]
Have a go hero – saving activity state
It is very surprising for many new Android developers, but when screen orientaon changes,
an Android acvity needs to be completely recreated. Nave acvies and their nave
thread are no excepon. To handle this case properly, the nave glue triggers an APP_CMD_
SAVE_STATE event to leave you a chance to save your acvity state before it is destroyed.
Based on DroidBlaster current code, the challenge is to track the number of mes acvity
is recreated by:
1. Creang a state structure to save the acvaon counter.
2. Saving the counter when acvity requests it. A new state structure will need
to be allocated each me with malloc() (memory is released with free())
and returned via savedState and savedStateSize elds in the
android_app structure.
3. Restoring the counter when acvity is recreated. State will need to be checked:
if it is NULL, then the acvity is created for the rst me. If it is not, then acvity
is recreated.
Because the state structure is copied and freed internally by the nave glue, no pointers can
be saved in the structure.
Project DroidBlaster_Part5-2 can be used as a starng point for this part.
The resulng project project is provided with this book under the name
DroidBlaster_Part5-SaveState.
Accessing window and time natively
Applicaon events are essenal to understand. But they are only a part of the puzzle and
will not get your users much excited. An interesng feature of the Android NDK is the ability
to access display window navely to draw graphics. But who talks about graphics talks also
about ming. Indeed, Android devices have dierent capabilies. Animaons should be
adapted to their speed. To help us in this task, Android gives access to me primives thanks
to its good support of Posix APIs.
Wring a Fully-nave Applicaon
[ 172 ]
We are now going to exploit these features to get a graphic feedback in our applicaon: a red
square moving on the screen. This square is going to be animated according to me to get a
reproducible result.
EventLoop DroidBlaster
ActivityHandler
Log
TimeService
Project DroidBlaster_Part5-2 can be used as a starng point for this
part. The resulng project project is provided with this book under
the name DroidBlaster_Part5-3.
Time for action – displaying raw graphics and
implementing a timer
First, let's implement a mer in a dedicated module:
Throughout this book, we will implement several modules named with the
posix Service. These services are purely design concepts and are not
related to Android services.
1. In the jni directory, create TimeService.hpp which includes time.h
Posix header.
It contains methods reset() and update() to manage mer state and two
interrogaon methods to read current me (method now()) and the me
elapsed in seconds between the last two updates (method elapsed()):
#ifndef _PACKT_TIMESERVICE_HPP_
#define _PACKT_TIMESERVICE_HPP_
#include "Types.hpp"
#include <time.h>
namespace packt {
class TimeService {
public:
Chapter 5
[ 173 ]
TimeService();
void reset();
void update();
double now();
float elapsed();
private:
float mElapsed;
double mLastTime;
};
}
#endif
2. Create a new TimeService.cpp le in jni. Use Posix primive clock_gettime()
to retrieve current me in now() method implementaon. A monotonic clock is
essenal to ensure me always goes forward and is not subject to system changes
(for example, if user change its sengs).
To accommodate the need of graphics applicaons, dene method elapsed()
to check elapsed me since last update. This allows adapng applicaon behavior
according to device speed. It is important to work on doubles when manipulang
absolute me to avoid losing accuracy. Then the resulng delay can be converted
back to oat:
#include "TimeService.hpp"
#include "Log.hpp"
namespace packt {
TimeService::TimeService() :
mElapsed(0.0f),
mLastTime(0.0f)
{}
void TimeService::reset() {
Log::info("Resetting TimeService.");
mElapsed = 0.0f;
mLastTime = now();
}
void TimeService::update() {
double lCurrentTime = now();
mElapsed = (lCurrentTime - mLastTime);
mLastTime = lCurrentTime;
Wring a Fully-nave Applicaon
[ 174 ]
}
double TimeService::now() {
timespec lTimeVal;
clock_gettime(CLOCK_MONOTONIC, &lTimeVal);
return lTimeVal.tv_sec + (lTimeVal.tv_nsec * 1.0e-9);
}
float TimeService::elapsed() {
return mElapsed;
}
}
3. Create a new header le Context.hpp. Dene Context helper structure to hold
and share all DroidBlaster modules, starng with TimeService. This structure
is going to be enhanced throughout the next chapters:
#ifndef _PACKT_CONTEXT_HPP_
#define _PACKT_CONTEXT_HPP_
#include "Types.hpp"
namespace packt
{
class TimeService;
struct Context {
TimeService* mTimeService;
};
}
#endif
The me module can now be embedded in the applicaon code:
4. Open already exisng le DroidBlaster.hpp. Dene two internal methods
clear() and draw() to erase the screen and draw the square cursor on it.
Declare a few member variables to store acvity and display state as well as
cursor posion, size, and speed:
#ifndef _PACKT_DROIDBLASTER_HPP_
#define _PACKT_DROIDBLASTER_HPP_
#include "ActivityHandler.hpp"
#include "Context.hpp"
#include "TimeService.hpp"
#include "Types.hpp"
Chapter 5
[ 175 ]
#include <android_native_app_glue.h>
namespace dbs {
class DroidBlaster : public packt::ActivityHandler {
public:
DroidBlaster(packt::Context& pContext,
android_app* pApplication);
~DroidBlaster();
protected:
status onActivate();
void onDeactivate();
status onStep();
...
private:
void clear();
void drawCursor(int pSize, int pX, int pY);
private:
android_app* mApplication;
ANativeWindow_Buffer mWindowBuffer;
packt::TimeService* mTimeService;
bool mInitialized;
float mPosX;
float mPosY;
const int32_t mSize;
const float mSpeed;
};
}
#endif
5. Now, open DroidBlaster.cpp implementaon le. Update its constructor
and destructor. Cursor is 24 pixels large and moves at 100 pixels per second.
TimeService (and in near future all other services) is transmied in the
Context structure:
#include "DroidBlaster.hpp"
#include "Log.hpp"
#include <math.h>
Wring a Fully-nave Applicaon
[ 176 ]
namespace dbs {
DroidBlaster::DroidBlaster(packt::Context& pContext,
android_app* pApplication) :
mApplication(pApplication),
mTimeService(pContext.mTimeService),
mInitialized(false),
mPosX(0), mPosY(0), mSize(24), mSpeed(100.0f) {
packt::Log::info("Creating DroidBlaster");
}
DroidBlaster::~DroidBlaster() {
packt::Log::info("Destructing DroidBlaster");
}
...
6. Sll in DroidBlaster.cpp, re-implement acvaon handler to:
Inialize the mer.
Force the window format in 32-bit with ANativeWindow_
setBuffersGeometry(). The two zeros passed in parameters are the
wanted window width and height. They are ignored unless inialized with
a posive value. Note that window area dened by width and height is
scaled to match screen size.
Retrieve all the necessary window informaon in an ANativeWindow_
Buffer structure to allow drawing. To ll this structure, window must
be locked.
Inialize cursor posion the rst me acvity is launched.
...
status DroidBlaster::onActivate() {
packt::Log::info("Activating DroidBlaster");
mTimeService->reset();
// Forces 32 bits format.
ANativeWindow* lWindow = mApplication->window;
if (ANativeWindow_setBuffersGeometry(lWindow, 0,
0,
WINDOW_FORMAT_RGBX_8888) < 0) {
return STATUS_KO;
}
// Needs to lock the window buffer to get its
properties.
Chapter 5
[ 177 ]
if (ANativeWindow_lock
(lWindow, &mWindowBuffer, NULL) >= 0) {
ANativeWindow_unlockAndPost(lWindow);
} else {
return STATUS_KO;
}
// Position the mark in the center.
if (!mInitialized) {
mPosX = mWindowBuffer.width / 2;
mPosY = mWindowBuffer.height / 2;
mInitialized = true;
}
return STATUS_OK;
}
...
7. Connue with DroidBlaster.cpp and step the applicaon by moving the cursor
at a constant rate (here 100 pixels per second). The window buer has to be locked
to draw on it (method ANativeWindow_lock()) and unlocked when drawing is
nished (method ANativeWindow_unlockAndPost()):
...
status DroidBlaster::onStep() {
mTimeService->update();
// Moves the mark at 100 pixels per second.
mPosX = fmod(mPosX + mSpeed * mTimeService->elapsed(),
mWindowBuffer.width);
// Locks the window buffer and draws on it.
ANativeWindow* lWindow = mApplication->window;
if (ANativeWindow_lock(lWindow, &mWindowBuffer, NULL) >= 0) {
clear();
drawCursor(mSize, mPosX, mPosY);
ANativeWindow_unlockAndPost(lWindow);
return STATUS_OK;
} else {
return STATUS_KO;
}
}
...
Wring a Fully-nave Applicaon
[ 178 ]
8. Finally, implement the drawing methods. Clear the screen with a brute-force
approach using memset(). This operaon is supported by display window surface
which is in fact just a big connuous memory buer.
Drawing the cursor is not much more dicult Like for bitmaps processed navely,
display window surface is directly accessible via the bits eld (only when surface
is locked!) and can be modied pixel by pixel. Here, a red square is rendered line by
line at the requested posion. The stride allows jumping directly from one line
to another.
Note that no boundary check is performed. This is not a
problem for such a simple example but a memory overow
can happen really quickly and cause a violent crash.
...
void DroidBlaster::clear() {
memset(mWindowBuffer.bits, 0, mWindowBuffer.stride
* mWindowBuffer.height * sizeof(uint32_t*));
}
void DroidBlaster::drawCursor(int pSize, int pX, int pY) {
const int lHalfSize = pSize / 2;
const int lUpLeftX = pX - lHalfSize;
const int lUpLeftY = pY - lHalfSize;
const int lDownRightX = pX + lHalfSize;
const int lDownRightY = pY + lHalfSize;
uint32_t* lLine =
reinterpret_cast<uint32_t*> (mWindowBuffer.bits)
+ (mWindowBuffer.stride * lUpLeftY);
for (int iY = lUpLeftY; iY <= lDownRightY; iY++) {
for (int iX = lUpLeftX; iX <= lDownRightX; iX++) {
lLine[iX] = 255;
}
lLine = lLine + mWindowBuffer.stride;
}
}
}
The test code must be launched from the main entry point.
Chapter 5
[ 179 ]
9. Update android_main in le Main.cpp to launch the DroidBlaster acvity
handler. You can temporarily comment DroidBlaster declaraon:
#include "Context.hpp"
#include "DroidBlaster.hpp"
#include "EventLoop.hpp"
#include "TimeService.hpp"
void android_main(android_app* pApplication) {
packt::TimeService lTimeService;
packt::Context lContext = { &lTimeService };
packt::EventLoop lEventLoop(pApplication);
dbs::DroidBlaster lDroidBlaster(lContext, pApplication);
lEventLoop.run(lDroidBlaster);
}
10. Are you fed up with adding new .cpp les each me you create a new one? Then
change the Android.mk le to dene a Make macro LS_CPP that lists all .cpp
les in jni directory automacally. This macro is invoked when LOCAL_SRC_FILES
variable is inialized. Please refer to Chapter 9, Porng Exisng Libraries to Android
for more informaon on the Makele language:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LS_CPP=$(subst $(1)/,,$(wildcard $(1)/*.cpp))
LOCAL_MODULE := droidblaster
LOCAL_SRC_FILES := $(call LS_CPP,$(LOCAL_PATH))
LOCAL_LDLIBS := -landroid -llog
LOCAL_STATIC_LIBRARIES := android_native_app_glue
include $(BUILD_SHARED_LIBRARY)
$(call import-module,android/native_app_glue)
11. Compile and run the applicaon.
Wring a Fully-nave Applicaon
[ 180 ]
What just happened?
If you run DroidBlaster, you will discover the following result. The red square crosses
the screen at a constant rhythm. Result should be reproducible among each run:
Graphic feedback is performed through the ANativeWindow_* API which gives nave access
to the display window and allow manipulang its surface like a bitmap. Like with bitmaps,
accessing window surface requires locking and unlocking before and aer processing.
Be safe!
Nave applicaons can crash. They can crash badly and although there are
means to detect where an applicaon crashed (like core dumps in Android
logs, see Chapter 11, Debugging and Troubleshoong), it is always beer
to develop carefully and protect your program code. Here, if the cursor was
drawn outside surface memory buer, a sudden crash would be very likely
to happen.
You can start experimenng more concretely with applicaon events by pressing the power
buon, leaving to the home screen. Several situaons can occur and should be systemacally
tested carefully:
Leaving the applicaon using the Back buon (which destroys the nave thread)
Leaving the applicaon using the Home buon (does not destroy the nave thread
but stops the applicaon and releases the window)
Long press on the power buon to open the Power menu (applicaon loses focus)
Long press on the Home buon to show applicaon switching menu (loses focus)
An unexpected phone call
Leaving the applicaon using the Back buon, reinializes the mark in the middle;
this is because the nave thread gets destructed. This is not the case in other scenarios
(for example, pressing the Home buon).
Chapter 5
[ 181 ]
More on time primitives
Timers are essenal to display animaons and movement at correct speed. They can be
implemented with the POSIX method clock_gettime() which retrieves me with a high
precision, theorecally unl the nanosecond.
Clock has been congured with the opon CLOCK_MONOTONIC. A monotonic mer gives the
elapsed clock me since an arbitrary starng point in the past. It is unaected by potenal
system date change and thus cannot go back in the past compared to other opons. The
downside with CLOCK_MONOTONIC is that it is system specic and it is not guaranteed to be
supported. Hopefully, Android supports it but care should be taken when porng Android
code to other plaorms.
An alternave, less precise but which is aected by changes in the system me, is
gettimeofday(), also provided in time.h. Usage is similar but precision is in microseconds
instead of nanoseconds. Here could be an usage example that could replace the current now()
implementaon in TimeService:
double TimeService::now() {
timeval lTimeVal;
gettimeofday(&lTimeVal, NULL);
return (lTimeVal.tv_sec * 1000.0) + (lTimeVal.tv_usec / 1000.0);
}
Summary
In this chapter, we created our rst fully nave applicaon without a line of Java code
and started to implement the skeleton of an event loop which processes events. More
specically, we have seen how to poll events accordingly and make an applicaon alive.
We have also handled events occurring during acvity lifecycle to acvate and deacvate
acvity as soon as it is idling.
We have locked and unlocked navely the display window to display raw graphics. We can
now draw graphics directly without a temporary back buer. Finally, we have retrieved me
to make the applicaon adapt to device speed, thanks to a monotonic clock.
The basic framework iniated here will form the base of the 2D/3D game that we will
develop throughout this book. However, although nowadays simplicity is fashion, we need
something a bit fancier than just a red square! Follow me into the next chapter and discover
how to render advanced graphics with OpenGL ES for Android.
6
Rendering Graphics with OpenGL ES
Let's face it: one of the main interests of the Android NDK is to write mulmedia
applicaons and games. Indeed, these programs consume lots of resources and
need responsiveness. That is why one of the rst available APIs (and almost
the only one unl recently) in Android NDK is an API for graphics: the Open
Graphics Library for Embedded Systems (abbreviated OpenGL ES).
OpenGL is a standard API created by Silicon Graphics and now managed by the
Khronos Group (see http://www.khronos.org/). OpenGL ES derivave is
available on many plaorms such as iOS or Blackberry OS and is the best hope
for wring portable and ecient graphics code. OpenGL can do both 2D and 3D
graphics with programmable shaders (if hardware supports it). There are two
main releases of OpenGL ES currently supported by Android:
OpenGL ES 1.1: This is the most supported API on Android devices.
It oers an old school graphic API with a xed pipeline (that is, a xed
set of congurable operaons to transform and render geometry).
Although specicaon is not fully implemented, its current
implementaon is perfectly sucient. This is a good choice to
write 2D games or 3D games targeng older devices.
OpenGL ES 2: This is not supported on old phones (like the anc HTC G1)
but more recent ones (at least not so old like the Nexus One… me goes
fast in the mobile world) support it. OpenGL ES 2 replaces the xed
pipeline with a modern programmable pipeline with vertex and pixel
shaders. This is the best choice for advanced 3D games. Note that
OpenGL ES 1.X is frequently emulated by an OpenGL 2 implementaon
behind the scene.
Rendering Graphics with OpenGL ES
[ 184 ]
This chapter teaches how to create 2D graphics. More specically, it shows how to
do the following:
Inialize OpenGL ES and bind it to an Android window
Load a texture from a PNG le
Draw sprites using OpenGL ES 1.1 extensions
Display a le map using vertex and index buers
OpenGL ES and graphics in general is a wide subject. This chapter covers the essenal basics
to get started with OpenGL ES 1.1, largely enough to create the next mind-blowing app!
Initializing OpenGL ES
The rst step to create awesome graphics is to inialize OpenGL ES. Although not terribly
complex, this task is a lile bit involving when binding to an Android window (that is,
aaching a rendering context to a window). These pieces are glued together with the help
of the Embedded-System Graphics Library (or EGL), a companion API of OpenGL ES.
For this rst part, I propose to replace the raw drawing system implemented in a previous
chapter with OpenGL ES. We are going to take care of EGL inializaon and nalizaon and
try to fade screen color from black to white to ensure everything works properly.
Project DroidBlaster_Part5-3 can be used as a starng point for this part. The
resulng project is provided with this book under the name DroidBlaster_Part6-1.
Time for action – initializing OpenGL ES
First, let's encapsulate OpenGL ES inializaon code in a dedicated C++ class:
1. Create header le GraphicsService.hpp in jni folder. It needs to include EGL/
egl.h which denes EGL API to bind OpenGL ES to the Android plaorm. This
header declares among others EGLDisplay, EGLSurface, and EGLContext
types which are handles to system resources.
Our GrapicsService lifecycle is composed of three main steps:
start(): This binds an OpenGL rendering context to the Android
nave window and loads graphic resources (textures and meshes later
in this chapter).
Chapter 6
[ 185 ]
stop(): This unbinds rendering context from Android window and frees
allocated graphic resources.
update(): This performs rendering operaons during each
refresh iteraon.
#define _PACKT_GRAPHICSSERVICE_HPP_
#include "TimeService.hpp"
#include "Types.hpp"
#include <android_native_app_glue.h>
#include <EGL/egl.h>
namespace packt {
class GraphicsService {
public:
GraphicsService(android_app* pApplication,
TimeService* pTimeService);
const char* getPath();
const int32_t& getHeight();
const int32_t& getWidth();
status start();
void stop();
status update();
private:
android_app* mApplication;
TimeService* mTimeService;
int32_t mWidth, mHeight;
EGLDisplay mDisplay;
EGLSurface mSurface;
EGLContext mContext;
};
}
#endif
Rendering Graphics with OpenGL ES
[ 186 ]
2. Create jni/Graphics.Service.cpp. Include GLES/gl.h and GLES/glext.h,
which are the ocial OpenGL include les for Android. Write constructor, destructor,
and geer methods:
#include "GraphicsService.hpp"
#include "Log.hpp"
#include <GLES/gl.h>
#include <GLES/glext.h>
namespace packt
{
GraphicsService::GraphicsService(android_app* pApplication,
TimeService* pTimeService) :
mApplication(pApplication),
mTimeService(pTimeService),
mWidth(0), mHeight(0),
mDisplay(EGL_NO_DISPLAY),
mSurface(EGL_NO_CONTEXT),
mContext(EGL_NO_SURFACE)
{}
int32_t GraphicsService::getPath() {
return mResource.getPath();
}
const int32_t& GraphicsService::getHeight() {
return mHeight;
}
const int32_t& GraphicsService::getWidth() {
return mWidth;
}
...
3. In the same le, carry out the bulk of the work by wring start(). The rst
inializaon steps consist of the following:
Connecng to a display, that is, an Android window, with
eglGetDisplay() and eglInitialize().
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
Chapter 6
[ 187 ]
Finding an appropriate framebuer conguraon with
eglChooseConfig() for the display. Framebuer is an OpenGL term
referring to a rendering surface (including addional elements like a
Z-buer). Conguraons are selected according to requested aributes:
OpenGL ES 1 and a 16 bits surface (5 bits for red, 6 for green, and 5 for
blue). The aribute list is terminated by EGL_NONE sennel. Here, we
choose the default conguraon.
Re-conguring the Android window according to selected conguraon
aributes (retrieved with eglGetConfigAttrib()). This operaon is
Android-specic and is performed with Android ANativeWindow API.
A list of all available framebuer conguraons is also available through
eglGetConfigs() which can then be parsed with eglGetConfigAttrib().
Note how EGL denes its own types and re-declares primive types EGLint
and EGLBoolean to favor plaorm independence:
...
status GraphicsService::start() {
EGLint lFormat, lNumConfigs, lErrorResult;
EGLConfig lConfig;
const EGLint lAttributes[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT,
EGL_BLUE_SIZE, 5, EGL_GREEN_SIZE, 6, EGL_RED_SIZE, 5,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_NONE
};
mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (mDisplay == EGL_NO_DISPLAY) goto ERROR;
if (!eglInitialize(mDisplay, NULL, NULL)) goto ERROR;
if(!eglChooseConfig(mDisplay, lAttributes, &lConfig, 1,
&lNumConfigs) || (lNumConfigs <= 0)) goto ERROR;
if (!eglGetConfigAttrib(mDisplay, lConfig,
EGL_NATIVE_VISUAL_ID, &lFormat)) goto ERROR;
ANativeWindow_setBuffersGeometry(mApplication->window, 0, 0,
lFormat);
...
Rendering Graphics with OpenGL ES
[ 188 ]
4. Connue start() method to create the display surface according to the
conguraon selected previously and context. A context contains all data
related to OpenGL state (enabled and disabled sengs, matrix stack, and so on).
OpenGL ES supports the creaon of mulple contexts for one
display surface. This allows dividing rendering operaons among
threads or rendering to several windows. However, it is not well
supported on Android hardware and should be avoided.
Finally, acvate the created rendering context (eglMakeCurrent()) and
dene the display viewport according to surface aributes (retrieved with
eglQuerySurface()).
...
mSurface = eglCreateWindowSurface(mDisplay, lConfig,
mApplication->window, NULL);
if (mSurface == EGL_NO_SURFACE) goto ERROR;
mContext = eglCreateContext(mDisplay, lConfig,
EGL_NO_CONTEXT, NULL);
if (mContext == EGL_NO_CONTEXT) goto ERROR;
if (!eglMakeCurrent (mDisplay, mSurface, mSurface, mContext)
|| !eglQuerySurface(mDisplay, mSurface, EGL_WIDTH, &mWidth)
|| !eglQuerySurface(mDisplay, mSurface, EGL_HEIGHT, &mHeight)
|| (mWidth <= 0) || (mHeight <= 0)) goto ERROR;
glViewport(0, 0, mWidth, mHeight);
return STATUS_OK;
ERROR:
Log::error("Error while starting GraphicsService");
stop();
return STATUS_KO;
}
...
Chapter 6
[ 189 ]
5. In GraphicsService.cpp, unbind the applicaon from the android window
and release EGL resources when the applicaon stops running:
OpenGL contexts are lost frequently on Android applicaons (when
leaving or going back to the home screen, when a call is received,
when devices go to sleep, and so on). As a lost context becomes
unusable, it is important to release resources as soon as possible.
...
void GraphicsService::stop() {
if (mDisplay != EGL_NO_DISPLAY) {
eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_
SURFACE,
EGL_NO_CONTEXT);
if (mContext != EGL_NO_CONTEXT) {
eglDestroyContext(mDisplay, mContext);
mContext = EGL_NO_CONTEXT;
}
if (mSurface != EGL_NO_SURFACE) {
eglDestroySurface(mDisplay, mSurface);
mSurface = EGL_NO_SURFACE;
}
eglTerminate(mDisplay);
mDisplay = EGL_NO_DISPLAY;
}
}
...
6. Finally, implement the last method update() to refresh the screen during each step
with eglSwapBuffers(). To have a concrete visual feedback, change the display
background color gradually according to the me step with glClearColor() and
erase the framebuer with glClear(). Internally, rendering is performed on a back
buer which is swapped with the front buer shown to the user meanwhile. The
front buer becomes the back buer and vice versa (pointers are switched):
This technique is more commonly referred to as page ipping.
Front and back buers form a swap chain. According to driver
implementaon, they can be extended with a third buer, in which
case we talk about triple buering. Swapping is oen synchronized
with the screen refresh rate to avoid image tearing: this is a VSync.
Rendering Graphics with OpenGL ES
[ 190 ]
...
status GraphicsService::update() {
float lTimeStep = mTimeService->elapsed();
static float lClearColor = 0.0f;
lClearColor += lTimeStep * 0.01f;
glClearColor(lClearColor, lClearColor, lClearColor, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
if (eglSwapBuffers(mDisplay, mSurface) != EGL_TRUE) {
Log::error("Error %d swapping buffers.", eglGetError());
return STATUS_KO;
}
return STATUS_OK;
}
}
We are done with GraphicsService. Let's use it in the nal applicaon.
7. Add GraphicsService to the Context structure in exisng le
jni/Context.hpp:
...
namespace packt
{
class GraphicsService;
class TimeService;
struct Context
{
GraphicsService* mGraphicsService;
TimeService* mTimeService;
};
}
...
8. Now, modify DroidBlaster.hpp to include GraphicsService as a member
variable. You can get rid of previous members mApplication, mPosX, mPosY,
mSize, mSpeed, and methods clear() and drawCursor() created in the
previous chapter:
#ifndef _PACKT_DROIDBLASTER_HPP_
#define _PACKT_DROIDBLASTER_HPP_
#include "ActivityHandler.hpp"
Chapter 6
[ 191 ]
#include "Context.hpp"
#include "GraphicsService.hpp"
#include "TimeService.hpp"
#include "Types.hpp"
namespace dbs {
class DroidBlaster : public packt::ActivityHandler {
public:
DroidBlaster(packt::Context* pContext);
...
private:
packt::GraphicsService* mGraphicsService;
packt::TimeService* mTimeService;
};
}
#endif
9. And obviously, rewrite jni/DroidBlaster.cpp. Method onStep() is completely
rewrien and do not make use of DrawingUtil or ANativeWindow locking and
unlocking features anymore. This is completely replaced by GraphicsService,
which is started when the applicaon is made available. The same goes for
TimeService:
#include "DroidBlaster.hpp"
#include "Log.hpp"
namespace dbs {
DroidBlaster::DroidBlaster(packt::Context* pContext) :
mGraphicsService(pContext->mGraphicsService),
mTimeService(pContext->mTimeService)
{}
packt::status DroidBlaster::onActivate() {
if (mGraphicsService->start() != packt::STATUS_OK) {
return packt::STATUS_KO;
}
mTimeService->reset();
return packt::STATUS_OK;
}
void DroidBlaster::onDeactivate() {
mGraphicsService->stop();
}
Rendering Graphics with OpenGL ES
[ 192 ]
packt::status DroidBlaster::onStep() {
mTimeService->update();
if (mGraphicsService->update() != packt::STATUS_OK) {
return packt::STATUS_KO;
}
return packt::STATUS_OK;
}
...
}
10. Finally, update the main loop in exisng le Main.cpp to instanate
GraphicsService:
#include "Context.hpp"
#include "DroidBlaster.hpp"
#include "EventLoop.hpp"
#include "GraphicsService.hpp"
#include "TimeService.hpp"
void android_main(android_app* pApplication) {
packt::TimeService lTimeService;
packt::GraphicsService lGraphicsService(pApplication,
&lTimeService);
packt::Context lContext = { &lGraphicsService, &lTimeService
};
packt::EventLoop lEventLoop(pApplication);
dbs::DroidBlaster lDroidBlaster(&lContext);
lEventLoop.run(&lDroidBlaster);
}
11. Let's not forget compilaon. OpenGL ES 1.x libraries need to be included: libEGL
for device inializaon and libGLESv1_CM for drawing calls:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LS_CPP=$(subst $(1)/,,$(wildcard $(1)/*.cpp))
LOCAL_MODULE := droidblaster
LOCAL_SRC_FILES := $(call LS_CPP,$(LOCAL_PATH))
LOCAL_LDLIBS := -landroid -llog -lEGL -lGLESv1_CM
Chapter 6
[ 193 ]
LOCAL_STATIC_LIBRARIES := android_native_app_glue
include $(BUILD_SHARED_LIBRARY)
$(call import-module,android/native_app_glue)
What just happened?
Launch the applicaon. If everything works ne, your device screen will progressively fade
from black to white. But instead of clearing display with a raw memset()or seng pixels
one by one like seen in previous chapter, ecient OpenGL ES drawing primives are invoked
instead. Note that the eect appears only the rst me the applicaon is started because
the clear color is stored in a stac variable, which has a dierent lifecycle than local and Java
variables on Android (see Chapter 4, Calling Back Java from Nave Code ). To make it appear
again, kill the applicaon or relaunch it in Debug mode.
We have inialized and connected OpenGL ES and the Android nave window system
together with EGL. Thanks to this API, we have queried a display conguraon that matches
our expectaons and created a framebuer to render our scene on. We have taken care
of releasing resources when the applicaon is deacvated, as OpenGL contexts are lost
frequently on mobile systems. Although EGL is a standard API, specied by the Khronos
group like OpenGL, plaorms oen implement their own variant (haphazardly, EAGL on
iOS). Portability is also limited by the fact that display window inializaon remains the
responsibility of client applicaon.
Reading PNG textures with the asset manager
I guess you need something more consistent than just changing the screen color! But before
showing awesome graphics in our applicaon, we need to load some external resources.
In this second part, we are going to load a texture into OpenGL ES thanks to the Android asset
manager, an API provided since NDK R5. It allows programmers to access any resources stored
in the assets folder of their project folder. Assets stored there are then packaged into the
nal APK archive during applicaon compilaon. Asset resources are considered as raw binary
les that your applicaon needs to interpret and access using their lename relave to the
assets folder (a le assets/mydir/myfile can be accessed with mydir/myfile path).
Files are read-only and likely to be compressed.
If you have already wrien some Java Android applicaon, then you know that Android also
provides resources accessible through compile-me generated IDs inside the res project
folder. This is not directly available on the Android NDK and unless you are ready to use a JNI
bridge, assets are the only way to package resources in your APK.
Rendering Graphics with OpenGL ES
[ 194 ]
In the current part, we are going to load a texture encoded in one of the most popular
picture formats used nowadays: Portable Network Graphics or more commonly known
as PNG. To help us in this task, we are going to integrate libpng NDK to interpret a PNG
le added to our assets. The resulng applicaon will look like the following diagram:
Project DroidBlaster_Part6-1 can be used as a starng point for this
part. The resulng project is provided with this book under the name
DroidBlaster_Part6-2.
Time for action – loading a texture in OpenGL ES
PNG is a complex format to read. So let's embed libpng third-party library:
1. Go to the libpng website at http://www.libpng.org/pub/png/libpng.html
and download the libpng source package (version 1.5.2 in this book).
Original libpng 1.5.2 archive is provided with this book in Chapter6/
Resource folder under the name lpng152.zip. A second archive
lpng152_ndk.zip with the modicaons made in the following steps
is also available.
2. Create a folder libpng inside $ANDROID_NDK/sources/. Move all les from the
libpng package in it.
3. Copy le libpng/scripts/pnglibconf.h.prebuilt into root folder libpng
with other source les. Rename it pnglibconf.h.
Chapter 6
[ 195 ]
4. Write an Android.mk le inside $ANDROID_NDK/sources with the content as
follows. This Makele compiles all C les (macro LS_C called from LOCAL_SRC_
FILES direcve) inside libpng folder, excluding example.c and pngtest.c.
The library is linked with prerequisite library libzip (opon -lz) and packaged as a
stac library. All include les are made available with direcve LOCAL_EXPORT_C_
INCLUDES to clients.
Folder $ANDROID_NDK/sources is a special folder considered
by default as a module folder (which contains reusable libraries. See
Chapter 9, Porng Exisng Libraries to Android for more informaon).
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LS_C=$(subst $(1)/,,$(wildcard $(1)/*.c))
LOCAL_MODULE := png
LOCAL_SRC_FILES := \
$(filter-out example.c pngtest.c,$(call LS_C,$(LOCAL_PATH)))
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
LOCAL_EXPORT_LDLIBS := -lz
include $(BUILD_STATIC_LIBRARY)
5. Now, open jni/Android.mk in DroidBlaster. Link and import libpng thanks
to the LOCAL_STATIC_LIBRARIES and import-module direcves:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LS_CPP=$(subst $(1)/,,$(wildcard $(1)/*.cpp))
LOCAL_MODULE := droidblaster
LOCAL_SRC_FILES := $(call LS_CPP,$(LOCAL_PATH))
LOCAL_LDLIBS := -landroid -llog -lEGL -lGLESv1_CM
LOCAL_STATIC_LIBRARIES := android_native_app_glue png
include $(BUILD_SHARED_LIBRARY)
$(call import-module,android/native_app_glue)
$(call import-module,libpng)
Rendering Graphics with OpenGL ES
[ 196 ]
6. Add libpng folder (${env_var:ANDROID_NDK}/sources/libpng) to your
project paths in the Project | Properes | Path and Symbols | Includes tab.
7. Ensure your module works by compiling DroidBlaster. If everything works ne,
libpng source les should get compiled (note that NDK will not recompile already
compiled sources). Some warnings are likely to appear. You can safely ignore them:
Library libpng is now included in our project. So let's now try to read a PNG
image le.
8. First, create in jni/Resource.hpp a new class Resource to access asset les.
We need three simple operaons: open(), close(), and read().
Resource will encapsulate calls to the nave Android asset management API.
This API is dened in android/asset_manager.hpp which is already included in
header android_native_app_glue.h. Its main entry point is an AAsetMAnager
opaque pointer, from which we can access an asset le represented by an AAsset:
#ifndef _PACKT_RESOURCE_HPP_
#define _PACKT_RESOURCE_HPP_
#include "Types.hpp"
#include <android_native_app_glue.h>
namespace packt {
class Resource {
public:
Resource(android_app* pApplication, const char* pPath);
const char* getPath();
status open();
void close();
status read(void* pBuffer, size_t pCount);
private:
const char* mPath;
Chapter 6
[ 197 ]
AAssetManager* mAssetManager;
AAsset* mAsset;
};
}
#endif
Implement class Resource in jni/Resource.cpp. The asset manager opens
assets with AAssetManager_open(). This is its sole responsibility apart from
lisng folders. Assets are opened in default AASSET_MODE_UNKNOWN mode.
Other possibilies are:
AASSET_MODE_BUFFER: This performs fast small reads
AASSET_MODE_RANDOM: This reads chunks of data forward and backward
AASSET_MODE_STREAMING: This reads data sequenally with occasional
forward seeks
Then, code operates on asset les with AAsset_read() to read data and
AAsset_close() to close the asset:
#include "Resource.hpp"
#include "Log.hpp"
namespace packt {
Resource::Resource(android_app* pApplication, const char*
pPath):
mPath(pPath),
mAssetManager(pApplication->activity->assetManager),
mAsset(NULL)
{}
const char* Resource::getPath() {
return mPath;
}
status Resource::open() {
mAsset = AAssetManager_open(mAssetManager, mPath,
AASSET_MODE_UNKNOWN);
return (mAsset != NULL) ? STATUS_OK : STATUS_KO;
}
void Resource::close() {
if (mAsset != NULL) {
AAsset_close(mAsset);
mAsset = NULL;
}
Rendering Graphics with OpenGL ES
[ 198 ]
}
status Resource::read(void* pBuffer, size_t pCount) {
int32_t lReadCount = AAsset_read(mAsset, pBuffer, pCount);
return (lReadCount == pCount) ? STATUS_OK : STATUS_KO;
}
}
9. Create jni/GraphicsTexture.hpp as follows. Include OpenGL and PNG header
GLES/gl.h and png.h. A texture is loaded from a PNG le with loadImage() and
callback_read(), pushed into OpenGL with load() and released in unload().
A texture is accessible through a simple idener and has a format (RGB, RGBA, and
so on). Texture width and height have to be stored when as image is loaded from le:
#ifndef _PACKT_GRAPHICSTEXTURE_HPP_
#define _PACKT_GRAPHICSTEXTURE_HPP_
#include "Context.hpp"
#include "Resource.hpp"
#include "Types.hpp"
#include <android_native_app_glue.h>
#include <GLES/gl.h>
#include <png.h>
namespace packt {
class GraphicsTexture {
public:
GraphicsTexture(android_app* pApplication, const char*
pPath);
~GraphicsTexture();
int32_t getHeight();
int32_t getWidth();
status load();
void unload();
void apply();
protected:
uint8_t* loadImage();
private:
static void callback_read(png_structp pStruct,
Chapter 6
[ 199 ]
png_bytep pData, png_size_t pSize);
private:
Resource mResource;
GLuint mTextureId;
int32_t mWidth, mHeight;
GLint mFormat;
};
}
#endif
10. Create the C++ source counterpart jni/GraphicsTexture.cpp with a constructor,
a destructor, and geers:
#include "Log.hpp"
#include "GraphicsTexture.hpp"
namespace packt {
GraphicsTexture::GraphicsTexture(android_app* pApplication,
const char* pPath) :
mResource(pApplication, pPath),
mTextureId(0),
mWidth(0), mHeight(0)
{}
int32_t GraphicsTexture::getHeight() {
return mHeight;
}
int32_t GraphicsTexture::getWidth() {
return mWidth;
}
...
11. Then, in the same le, implement loadImage() method to load a PNG le. File
is rst opened through our Resource class and then its signature (the rst 8 bytes)
is checked to ensure le is a PNG (note that it can sll be corrupted):
...
uint8_t* GraphicsTexture::loadImage() {
png_byte lHeader[8];
png_structp lPngPtr = NULL; png_infop lInfoPtr = NULL;
png_byte* lImageBuffer = NULL; png_bytep* lRowPtrs = NULL;
png_int_32 lRowSize; bool lTransparency;
Rendering Graphics with OpenGL ES
[ 200 ]
if (mResource.open() != STATUS_OK) goto ERROR;
if (mResource.read(lHeader, sizeof(lHeader)) != STATUS_OK)
goto ERROR;
if (png_sig_cmp(lHeader, 0, 8) != 0) goto ERROR;
...
12. In the same method, create all structures necessary to read a PNG image.
Aer that, prepare reading operaons by giving our callback_read()
(implemented later in this tutorial) to libpng with our Resource reader.
Set up error management with setjmp(). This mechanism allows jumping like a
goto but through the call stack. If an error occurs, control ow comes back at the
point where setjmp() has been called rst, but enters the if block instead (here
goto ERROR):
...
lPngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL);
if (!lPngPtr) goto ERROR;
lInfoPtr = png_create_info_struct(lPngPtr);
if (!lInfoPtr) goto ERROR;
png_set_read_fn(lPngPtr, &mResource, callback_read);
if (setjmp(png_jmpbuf(lPngPtr))) goto ERROR;
...
13. In loadImage(), start reading PNG le header with png_read_info(), ignoring
the rst 8 bytes read for le signature with png_set_sig_bytes().
PNG les can be encoded in several formats: RGB, RGBA, 256 colors with a palee,
grayscale… R,G, and B color channels can be encoded on up to 16 bits. Hopefully,
libpng provides transformaon funcons to decode unusual formats to more
classical RGB and luminance formats with 8 bits per channel with or without an
alpha channel. Transformaons are validated with png_read_update_info():
...
png_set_sig_bytes(lPngPtr, 8);
png_read_info(lPngPtr, lInfoPtr);
png_int_32 lDepth, lColorType;
png_uint_32 lWidth, lHeight;
png_get_IHDR(lPngPtr, lInfoPtr, &lWidth, &lHeight,
&lDepth, &lColorType, NULL, NULL, NULL);
mWidth = lWidth; mHeight = lHeight;
Chapter 6
[ 201 ]
// Creates a full alpha channel if transparency is encoded as
// an array of palette entries or a single transparent color.
lTransparency = false;
if (png_get_valid(lPngPtr, lInfoPtr, PNG_INFO_tRNS)) {
png_set_tRNS_to_alpha(lPngPtr);
lTransparency = true;
goto ERROR;
}
// Expands PNG with less than 8bits per channel to 8bits.
if (lDepth < 8) {
png_set_packing (lPngPtr);
// Shrinks PNG with 16bits per color channel down to
8bits.
} else if (lDepth == 16) {
png_set_strip_16(lPngPtr);
}
// Indicates that image needs conversion to RGBA if
needed.
switch (lColorType) {
case PNG_COLOR_TYPE_PALETTE:
png_set_palette_to_rgb(lPngPtr);
mFormat = lTransparency ? GL_RGBA : GL_RGB;
break;
case PNG_COLOR_TYPE_RGB:
mFormat = lTransparency ? GL_RGBA : GL_RGB;
break;
case PNG_COLOR_TYPE_RGBA:
mFormat = GL_RGBA;
break;
case PNG_COLOR_TYPE_GRAY:
png_set_expand_gray_1_2_4_to_8(lPngPtr);
mFormat = lTransparency ? GL_LUMINANCE_ALPHA:GL_
LUMINANCE;
break;
case PNG_COLOR_TYPE_GA:
png_set_expand_gray_1_2_4_to_8(lPngPtr);
mFormat = GL_LUMINANCE_ALPHA;
break;
}
png_read_update_info(lPngPtr, lInfoPtr);
...
Rendering Graphics with OpenGL ES
[ 202 ]
14. Allocate the necessary temporary buer to hold image data and a second one with
the address of each output image row for libpng. Note that row order is inverted
because OpenGL uses a dierent coordinate system (rst pixel is at boom-le)
then PNG (rst pixel at top-le). Then start reading eecvely image content with
png_read_image().
...
lRowSize = png_get_rowbytes(lPngPtr, lInfoPtr);
if (lRowSize <= 0) goto ERROR;
lImageBuffer = new png_byte[lRowSize * lHeight];
if (!lImageBuffer) goto ERROR;
lRowPtrs = new png_bytep[lHeight];
if (!lRowPtrs) goto ERROR;
for (int32_t i = 0; i < lHeight; ++i) {
lRowPtrs[lHeight - (i + 1)] = lImageBuffer + i * lRowSize;
}
png_read_image(lPngPtr, lRowPtrs);
...
15. Finally, release resources(whether an error occurs or not) and return loaded data.
...
mResource.close();
png_destroy_read_struct(&lPngPtr, &lInfoPtr, NULL);
delete[] lRowPtrs;
return lImageBuffer;
ERROR:
Log::error("Error while reading PNG file");
mResource.close();
delete[] lRowPtrs; delete[] lImageBuffer;
if (lPngPtr != NULL) {
png_infop* lInfoPtrP = lInfoPtr != NULL ? &lInfoPtr: NULL;
png_destroy_read_struct(&lPngPtr, lInfoPtrP, NULL);
}
return NULL;
}
...
Chapter 6
[ 203 ]
16. We are almost done with loadImage()… almost because libpng sll requires
callback_read() to be implemented. This callback method, passed to libpng
at step 11, is a mechanism designed to integrate custom read operaons… like the
Android asset management API! The asset le is read through Resource instance
transmied as an untyped pointer at step 11:
...
void png_read_callback(png_structp png, png_bytep data,
png_size_t size) {
ResourceReader& lReader =
*((ResourceReader*) png_get_io_ptr(png));
if (lReader.read(data, size) != STATUS_OK) {
lReader.close();
png_error(png, "Error while reading PNG file");
}
}
...
17. We are done with PNG loading! In GraphicsTexture.hpp, get the temporary
image buer loaded in loadImage() back in method load(). Creang a texture
once image data is in memory is easy:
Generate a new texture ID with glGenTextures().
Tell OpenGL we are working on a new texture with glBindTexture().
Congure texture parameters, which need to be set only when texture
is created. GL_LINEAR smooths textures drawn on screen. This is not
essenal for a 2D game which does not scale textures but the smallest
zoom eect will require it. Texture repeon is prevented with GL_CLAMP_
TO_EDGE.
Push image data into current OpenGL texture with glTexImage2D().
And, of course, do not forget to free the temporary image buer!
...
status GraphicsTexture::load() {
uint8_t* lImageBuffer = loadImage();
if (lImageBuffer == NULL) {
return STATUS_KO;
}
// Creates a new OpenGL texture.
GLenum lErrorResult;
glGenTextures(1, &mTextureId);
glBindTexture(GL_TEXTURE_2D, mTextureId);
Rendering Graphics with OpenGL ES
[ 204 ]
// Set-up texture properties.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
GL_CLAMP_TO_EDGE);
// Loads image data into OpenGL.
glTexImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight,
0,
mFormat, GL_UNSIGNED_BYTE, lImageBuffer);
delete[] lImageBuffer;
if (glGetError() != GL_NO_ERROR) {
Log::error("Error loading texture into OpenGL.");
unload();
return STATUS_KO;
}
return STATUS_OK;
}
...
18. The rest of the code is much simpler. Method unload() releases Open GL texture
resources when applicaon exits with glDeleteTextures():
...
void GraphicsTexture::unload() {
if (mTextureId != 0) {
glDeleteTextures(1, &mTextureId);
mTextureId = 0;
}
mWidth = 0; mHeight = 0; mFormat = 0;
}
...
19. Finally, implement method apply() to indicate to OpenGL ES which texture
to draw on screen when refreshing the scene:
...
void GraphicsTexture::apply() {
glActiveTexture( GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, mTextureId);
}
}
Chapter 6
[ 205 ]
Code to properly load textures is ready. Let's manage them in GraphicsService:
20. Open jni/GraphicsService.hpp. Add a destructor and create a method
registerTexture() to allow clients to create new textures by passing an asset
path. Textures are stored in a C++ array. They are loaded when GraphicsService
starts (with loadResources()) and unloaded when it stops (with
unloadResources()):
#ifndef _PACKT_GRAPHICSSERVICE_HPP_
#define _PACKT_GRAPHICSSERVICE_HPP_
#include "GraphicsTexture.hpp"
#include "TimeService.hpp"
#include "Types.hpp"
...
namespace packt {
class GraphicsService
{
public:
GraphicsService(android_app* pApplication,
TimeService* pTimeService);
~GraphicsService();
...
status start();
void stop();
status update();
GraphicsTexture* registerTexture(const char* pPath);
protected:
status loadResources();
status unloadResources();
private:
...
GraphicsTexture* mTextures[32]; int32_t mTextureCount;
};
}
#endif
Rendering Graphics with OpenGL ES
[ 206 ]
21. In jni/GraphicsService.cpp, implementaon of the constructor, destructor,
start() and stop() is rather trivial:
...
namespace packt
{
GraphicsService::GraphicsService(android_app* pApplication,
TimeService* pTimeService) :
...,
mTextures(), mTextureCount(0)
{}
GraphicsService::~GraphicsService() {
for (int32_t i = 0; i < mTextureCount; ++i) {
delete mTextures[i];
mTextures[i] = NULL;
}
mTextureCount = 0;
}
...
status GraphicsService::start() {
...
glViewport(0, 0, mWidth, mHeight);
if (loadResources() != STATUS_OK) goto ERROR;
return STATUS_OK;
ERROR:
Log::error("Error while starting GraphicsService");
stop();
return STATUS_KO;
}
void GraphicsService::stop() {
unloadResources();
if (mDisplay != EGL_NO_DISPLAY) {
...
}
...
Chapter 6
[ 207 ]
22. To nish with jni/GraphicsService.cpp, append new methods for texture
resource management. There is no specic diculty here. A lookup is performed
when registering a texture to prevent duplicaon:
...
status GraphicsService::loadResources() {
for (int32_t i = 0; i < mTextureCount; ++i) {
if (mTextures[i]->load() != STATUS_OK) {
return STATUS_KO;
}
}
return STATUS_OK;
}
status GraphicsService::unloadResources() {
for (int32_t i = 0; i < mTextureCount; ++i) {
mTextures[i]->unload();
}
return STATUS_OK;
}
GraphicsTexture* GraphicsService::registerTexture(
const char* pPath) {
for (int32_t i = 0; i < mTextureCount; ++i) {
if (strcmp(pPath, mTextures[i]->getPath()) == 0) {
return mTextures[i];
}
}
GraphicsTexture* lTexture = new GraphicsTexture(
mApplication, pPath);
mTextures[mTextureCount++] = lTexture;
return lTexture;
}
}
What just happened?
In the previous chapter, we have just embedded exisng module NativeAppGlue to create
a fully nave applicaon. This me, we have created our rst reusable module to integrate
libpng. Combined with the Android asset manager, we are now able to create an OpenGL
texture from a PNG le packaged as an asset. The only drawback is that PNG does not
support 16 bits RGB.
Rendering Graphics with OpenGL ES
[ 208 ]
Do not be greedy with assets
Assets take space, lots of space. Installing large APK size can be problemac,
even when they are deployed on a SD Card (see the installLocation
opon in the Android manifest). Moreover, opening assets of more than 1 MB
or which were compressed was problemac in OS prior to version 2.3. Thus, a
good strategy to deal with tons of megabytes of resources is to keep essenal
assets in your APK and download remaining les to SD Card at runme the rst
me applicaon is launched.
To test if code loads textures without error, you can insert the following lines in
jni/DroidBlaster.cpp. Texture must be located in the assets project folder:
File ship.png loaded is provided with this book in Chapter6/Resource.
...
packt::GraphicsTexture* lShipTex =
mGraphicsService->registerTexture("ship.png");
...
When dealing with textures, an important requirement to remember is that OpenGL
textures must have a power of two dimensions (for example, 128 or 256 pixels). This allows,
for example, the generaon of mipmaps, that is, smaller versions of the same texture, to
increase performance and reduce aliasing arfacts when rendered object distance changes.
Other dimensions will fail on most devices. In addion, textures consume a lot of memory
and bandwidth. So consider using a compressed texture format such as ETC1 which is geng
wider support (but cannot handle alpha channels navely). Have a look at http://blog.
tewdew.com/post/7362195285/the-android-texture-decision for an interesng
arcle about texture compression.
Drawing a sprite
The base of 2D games is sprites, pieces of images composited (or blied) on screen and
which represent an object, character, or anything else animated or not. Sprites can be
displayed with a transparency eect using the Alpha channel of an image. Typically, an
image will contain several frames for a sprite, each frame represenng a dierent
animaon step or dierent objects.
Chapter 6
[ 209 ]
Eding sprite images
If you need a powerful mulplaorm image editor, consider using
GIMP, the GNU Image Manipulaon Program. This program available
on Windows, Linux and Mac OS X is really powerful and open source.
You can download it at http://www.gimp.org/.
To implement sprites, we are going to rely on an OpenGL ES extension generally supported
on Android devices : GL_OES_draw_texture. It allows drawing pictures directly onto the
screen from an texture. This is one of the most ecient technique when creang a 2D game.
Project DroidBlaster_Part6-2 can be used as a starng point for
this part. The resulng project is provided with this book under the
name DroidBlaster_Part6-3.
Time for action – drawing a Ship sprite
Let's write the necessary code to handle a sprite rst:
1. First, we need a class to contain sprites coordinates. Update jni/Types.hpp
to dene a new structure Location:
...
namespace packt {
typedef int32_t status;
const status STATUS_OK = 0;
const status STATUS_KO = -1;
const status STATUS_EXIT = -2;
struct Location {
Location(): mPosX(0), mPosY(0) {};
void setPosition(float pPosX, float pPosY)
{ mPosX = pPosX; mPosY = pPosY; }
void translate(float pAmountX, float pAmountY)
{ mPosX += pAmountX; mPosY += pAmountY; }
float mPosX; float mPosY;
};
}
...
Rendering Graphics with OpenGL ES
[ 210 ]
2. Create GraphicsSprite.hpp in folder jni. A sprite is loaded when
GraphicsService starts with load() and rendered when screen is refreshed
with draw(). It is possible to set an animaon with setAnimation() and play
it innitely or not by drawing sprite frames consecuvely in me.
A sprite requires several properes:
A texture containing the sprite sheet (mTexture).
A locaon to draw on screen (mLocation).
Informaon about sprite frames: mWidth and mHeight, horizontal,
vercal, and total number of frames in mFrameXCount, mFrameYCount,
and mFrameCount.
Animaon informaon: rst and total number of frames of an animaon
in mAnimStartFrame and mAnimFrameCount, animaon speed in
mAnimSpeed, currently shown frame in mAnimFrame, and a looping
indicator in mAnimLoop.
#ifndef _PACKT_GRAPHICSSPRITE_HPP_
#define _PACKT_GRAPHICSSPRITE_HPP_
#include "GraphicsTexture.hpp"
#include "TimeService.hpp"
#include "Types.hpp"
namespace packt {
class GraphicsSprite {
public:
GraphicsSprite(GraphicsTexture* pTexture,
int32_t pHeight, int32_t pWidth, Location*
pLocation);
void load();
void draw(float pTimeStep);
void setAnimation(int32_t pStartFrame, int32_t
pFrameCount,
float pSpeed, bool pLoop);
bool animationEnded();
private:
GraphicsTexture* mTexture;
Location* mLocation;
// Frame.
int32_t mHeight, mWidth;
Chapter 6
[ 211 ]
int32_t mFrameXCount, mFrameYCount, mFrameCount;
// Animation.
int32_t mAnimStartFrame, mAnimFrameCount;
float mAnimSpeed, mAnimFrame;
bool mAnimLoop;
};
}
#endif
3. Write GraphicsSprite.cpp in jni folder. Frame informaon (horizontal,
vercal, and total number of frames) needs to be recomputed in load()
as texture dimensions are known only at load me.
When seng up an animaon with setAnimation(), compute the rst frame index
mAnimStartFrame inside the sprite sheet and the number of images composing the
animaon, mAnimFrameCount. The animaon speed is set through mAnimSpeed and
current animaon frame (updated at each step) is saved in mAnimFrame:
include "GraphicsSprite.hpp"
#include "Log.hpp"
#include <GLES/gl.h>
#include <GLES/glext.h>
namespace packt {
GraphicsSprite::GraphicsSprite(GraphicsTexture* pTexture,
int32_t pHeight, int32_t pWidth, Location* pLocation) :
mTexture(pTexture), mLocation(pLocation),
mHeight(pHeight), mWidth(pWidth),
mFrameCount(0), mFrameXCount(0), mFrameYCount(0),
mAnimStartFrame(0), mAnimFrameCount(0),
mAnimSpeed(0), mAnimFrame(0), mAnimLoop(false)
{}
void GraphicsSprite::load() {
mFrameXCount = mTexture->getWidth() / mWidth;
mFrameYCount = mTexture->getHeight() / mHeight;
mFrameCount = (mTexture->getHeight() / mHeight)
* (mTexture->getWidth() / mWidth);
}
void GraphicsSprite::setAnimation(int32_t pStartFrame,
int32_t pFrameCount, float pSpeed, bool pLoop) {
mAnimStartFrame = pStartFrame;
mAnimFrame = 0.0f, mAnimSpeed = pSpeed, mAnimLoop = pLoop;
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
Rendering Graphics with OpenGL ES
[ 212 ]
int32_t lMaxFrameCount = mFrameCount - pStartFrame;
if ((pFrameCount > -1) && (pFrameCount <= lMaxFrameCount))
{
mAnimFrameCount = pFrameCount;
} else {
mAnimFrameCount = lMaxFrameCount;
}
}
bool GraphicsSprite::animationEnded() {
return mAnimFrame > (mAnimFrameCount - 1);
}
...
4. In GraphicsSprite.cpp, implement last method draw(). First, compute the
current frame to display depending on the animaon state and then draw it with
OpenGL. They are three main steps involved to draw a sprite:
Ensure OpenGL draws the right texture with apply() (that is,
glBindTexture()).
Crop texture to draw only the required sprite frame with
glTexParameteriv() and GL_TEXTURE_CROP_RECT_OES.
Finally send a draw order to OpenGL ES with glDrawTexfOES().
...
void GraphicsSprite::draw(float pTimeStep) {
int32_t lCurrentFrame, lCurrentFrameX,
lCurrentFrameY;
// Updates animation in loop mode.
mAnimFrame += pTimeStep * mAnimSpeed;
if (mAnimLoop) {
lCurrentFrame = (mAnimStartFrame +
int32_t(mAnimFrame) %
mAnimFrameCount);
}
// Updates animation in one-shot mode.
else {
// If animation ended.
if (animationEnded()) {
lCurrentFrame = mAnimStartFrame +
(mAnimFrameCount-1);
} else {
lCurrentFrame = mAnimStartFrame +
int32_t(mAnimFrame);
Chapter 6
[ 213 ]
}
}
// Computes frame X and Y indexes from its id.
lCurrentFrameX = lCurrentFrame % mFrameXCount;
// lCurrentFrameY is converted from OpenGL
coordinates
// to top-left coordinates.
lCurrentFrameY = mFrameYCount - 1
- (lCurrentFrame /
mFrameXCount);
// Draws selected sprite frame.
mTexture->apply();
int32_t lCrop[] = { lCurrentFrameX * mWidth,
lCurrentFrameY * mHeight,
mWidth, mHeight };
glTexParameteriv(GL_TEXTURE_2D,
GL_TEXTURE_CROP_RECT_OES,
lCrop);
glDrawTexfOES(mLocation->mPosX - (mWidth / 2),
mLocation->mPosY - (mHeight / 2),
0.0f, mWidth, mHeight);
}
}
Code to render sprites is ready. Let's make use of it:
5. Modify GraphicsService to manage sprite resources (like textures in the
previous part):
#ifndef _PACKT_GRAPHICSSERVICE_HPP_
#define _PACKT_GRAPHICSSERVICE_HPP_
#include "GraphicsSprite.hpp"
#include "GraphicsTexture.hpp"
...
namespace packt {
class GraphicsService {
public:
...
GraphicsTexture* registerTexture(const char* pPath);
GraphicsSprite* registerSprite(GraphicsTexture* pTexture,
int32_t pHeight, int32_t pWidth, Location* pLocation);
Rendering Graphics with OpenGL ES
[ 214 ]
protected:
status loadResources();
status unloadResources();
void setup();
private:
...
GraphicsTexture* mTextures[32]; int32_t mTextureCount;
GraphicsSprite* mSprites[256]; int32_t mSpriteCount;
};
}
#endif
6. Modify GraphicsService.cpp so that it creates a buer of sprite to draw
while operang. We dene a method registerSprite for this purpose:
...
namespace packt {
GraphicsService::GraphicsService(android_app* pApplication,
TimeService* pTimeService) :
...,
mTextures(), mTextureCount(0),
mSprites(), mSpriteCount(0)
{}
GraphicsService::~GraphicsService() {
for (int32_t i = 0; i < mSpriteCount; ++i) {
delete mSprites[i];
mSprites[i] = NULL;
}
mSpriteCount = 0;
for (int32_t i = 0; i < mTextureCount; ++i) {
delete mTextures[i];
mTextures[i] = NULL;
}
mTextureCount = 0;
}
...
status GraphicsService::start() {
...
Chapter 6
[ 215 ]
if (loadResources() != STATUS_OK) goto ERROR;
setup();
return STATUS_OK;
ERROR:
Log::error("Error while starting GraphicsService");
stop();
return STATUS_KO;
}
...
7. Erase screen with black and draw sprites over using the method update().
Transparency is enabled with glBlendFunc() which blends source texture pixel
with nal framebuer according to the specied formula. Here, source pixel aects
desnaon pixel according to its alpha channel (GL_SRC_ALPHA/GL_ONE_MINUS_
SRC_ALPHA). This is commonly referred to as alpha blending:
...
status GraphicsService::update() {
float lTimeStep = mTimeService->elapsed();
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
for (int32_t i = 0; i < mSpriteCount; ++i) {
mSprites[i]->draw(lTimeStep);
}
glDisable(GL_BLEND);
if (eglSwapBuffers(mDisplay, mSurface) != EGL_TRUE) {
Log::error("Error %d swapping buffers.", eglGetError());
return STATUS_KO;
}
return STATUS_OK;
}
status GraphicsService::loadResources() {
for (int32_t i = 0; i < mTextureCount; ++i) {
if (mTextures[i]->load() != STATUS_OK) {
return STATUS_KO;
}
}
Rendering Graphics with OpenGL ES
[ 216 ]
for (int32_t i = 0; i < mSpriteCount; ++i) {
mSprites[i]->load();
}
return STATUS_OK;
}
...
8. To nish with jni/GraphicsService.cpp, implement setup() to inialize
main OpenGL sengs. Here, enable texturing but disable the Z-buer which is
not needed in a simple 2D game. Ensure sprites are rendered (for the emulator)
with glColor4f():
...
void GraphicsService::setup() {
glEnable(GL_TEXTURE_2D);
glDisable(GL_DEPTH_TEST);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}
...
GraphicsSprite* GraphicsService::registerSprite(
GraphicsTexture* pTexture, int32_t pHeight,
int32_t pWidth, Location* pLocation) {
GraphicsSprite* lSprite = new GraphicsSprite(pTexture,
pHeight, pWidth, pLocation);
mSprites[mSpriteCount++] = lSprite;
return lSprite;
}
}
We are almost done! Let's use our engine drawing capabilies to render a spaceship:
9. Create a Ship game object in jni/Ship.hpp le:
#ifndef _DBS_SHIP_HPP_
#define _DBS_SHIP_HPP_
#include "Context.hpp"
#include "GraphicsService.hpp"
#include "GraphicsSprite.hpp"
#include "Types.hpp"
namespace dbs {
class Ship {
Chapter 6
[ 217 ]
public:
Ship(packt::Context* pContext);
void spawn();
private:
packt::GraphicsService* mGraphicsService;
packt::GraphicsSprite* mSprite;
packt::Location mLocation;
float mAnimSpeed;
};
}
#endif
10. The Ship class registers the resource it needs when it is created, here
ship.png sprite (which must be located in the assets folder) contains
64x64 pixel frames. It is inialized in spawn() in the lower quarter of the
screen and uses an 8-frame animaon:
File ship.png is provided with this book in the Chapter6/
Resource folder.
#include "Ship.hpp"
#include "Log.hpp"
namespace dbs {
Ship::Ship(packt::Context* pContext) :
mGraphicsService(pContext->mGraphicsService),
mLocation(), mAnimSpeed(8.0f) {
mSprite = pContext->mGraphicsService->registerSprite(
mGraphicsService->registerTexture("ship.png"), 64, 64,
&mLocation);
}
void Ship::spawn() {
const int32_t FRAME_1 = 0; const int32_t FRAME_NB = 8;
mSprite->setAnimation(FRAME_1, FRAME_NB, mAnimSpeed, true);
mLocation.setPosition(mGraphicsService->getWidth() * 1 / 2,
Rendering Graphics with OpenGL ES
[ 218 ]
mGraphicsService->getHeight() * 1 / 4);
}
}
11. Include the new Ship class in jni/DroidBlaster.hpp:
#ifndef _PACKT_DROIDBLASTER_HPP_
#define _PACKT_DROIDBLASTER_HPP_
#include "ActivityHandler.hpp"
#include "Context.hpp"
#include "GraphicsService.hpp"
#include "Ship.hpp"
#include "TimeService.hpp"
#include "Types.hpp"
namespace dbs {
class DroidBlaster : public packt::ActivityHandler {
...
private:
packt::GraphicsService* mGraphicsService;
packt::TimeService* mTimeService;
Ship mShip;
};
}
#endif
12. Modify jni/DroidBlaster.cpp accordingly. Implementaon is trivial:
#include "DroidBlaster.hpp"
#include "Log.hpp"
namespace dbs {
DroidBlaster::DroidBlaster(packt::Context* pContext) :
mGraphicsService(pContext->mGraphicsService),
mTimeService(pContext->mTimeService),
mShip(pContext)
{}
...
packt::status DroidBlaster::onActivate() {
Chapter 6
[ 219 ]
if (mGraphicsService->start() != packt::STATUS_OK) {
return packt::STATUS_KO;
}
mShip.spawn();
mTimeService->reset();
return packt::STATUS_OK;
}
...
}
What just happened?
Launch DroidBlaster now to see the following screen with the ship animated at a rate
of 8 FPS:
In this part, we have seen how to draw a sprite eciently with a common OpenGL ES
extension GL_OES_draw_texture. This technique is simple to use and is generally the
way to go to render sprites. However, it suers from a few caveats that can be solved only
by going back to polygons:
glDrawTexOES() is available on OpenGL ES 1.1! OpenGL ES 2.0 and some old
devices do not support it.
Sprite cannot be rotated.
This technique may cause lots of state changes when drawing many dierent sprites
(like a background) which could impact performance.
Rendering Graphics with OpenGL ES
[ 220 ]
A common cause of bad performance in OpenGL programs relies in state changes. Changing
OpenGL device state (for example, binding a new buer or texture, changing an opon with
glEnable(), and so on) is a costly operaon and should be avoided as much as possible,
for example, by sorng draw calls and changing only the needed states. For example,
we could improve our Texture::apply() method by checking the texture currently
set before binding it.
One of the best OpenGL ES documentaon is available, well…
from the Apple developer site: http://developer.apple.
com/library/IOS/#documentation/3DDrawing/
Conceptual/OpenGLES_ProgrammingGuide/.
Rendering a tile map with vertex buffer objects
What would a 2D game be without a map; more precisely a le map. A le map is a full-size
map composed of small quad polygons or les mapped with a piece of image. These les
are made so that they can be pasted beside, repeatedly. We are now going to implement a
le map to draw a background. The rendering technique is inspired from the Android game
Replica Island (see http://replicaisland.net). It is based on vertex and index buer
to batch le rendering in a few OpenGL calls (thus minimizing state changes).
Tiled map editor
Tiled is an open source program available on Windows, Linux, and
Mac OS X to create your own custom le maps with a friendly editor.
Tiled exports XML-based les with the TMX extension. Download it
from http://www.mapeditor.org/.
Let's now implement our own le map. The nal applicaon should look like the following:
Chapter 6
[ 221 ]
Project DroidBlaster_Part6-3 can be used as a starng point for
this part. The resulng project is provided with this book under
the name DroidBlaster_Part6-4.
Time for action – drawing a tile-based background
First, let's embed RapidXml library to read XML les:
1. Download RapidXml (version 1.1.13 in this book) at http://rapidxml.
sourceforge.net/.
RapidXml archive is provided with this book in the Chapter6/
Resource folder.
2. Find rapidxml.hpp in the downloaded archive and copy it into your jni folder.
3. RapidXml works with excepons by default. As we will study excepon handling
later in this book, deacvate them in jni/Android.mk with a predened macro:
...
LS_CPP=$(subst $(1)/,,$(wildcard $(1)/*.cpp))
LOCAL_CFLAGS := -DRAPIDXML_NO_EXCEPTIONS
LOCAL_MODULE := droidblaster
LOCAL_SRC_FILES := $(call LS_CPP,$(LOCAL_PATH))
LOCAL_LDLIBS := -landroid -llog -lEGL -lGLESv1_CM
...
4. For eciency reasons, RapidXml read XML les directly from a memory buer
containing the whole le. So open Resource.hpp and add a new method to get
a full buer from an asset (bufferize()) and retrieve its length (getLength()):
...
namespace packt {
class Resource {
public:
...
off_t getLength();
const void* bufferize();
private:
...
Rendering Graphics with OpenGL ES
[ 222 ]
};
}
...
5. The asset management API oers all the required stu to implement
these methods:
...
namespace packt {
...
off_t Resource::getLength() {
return AAsset_getLength(mAsset);
}
const void* Resource::bufferize() {
return AAsset_getBuffer(mAsset);
}
}
Now, let's write the code necessary to handle a simple TMX le map:
6. Create a new header le jni/GraphicsTileMap.hpp as follows.
A GraphicsTileMap is rst loaded, then drawn when the screen
refreshes and nally unloaded. Loading itself occurs in three steps:
loadFile(): This loads a Tiled TMX le with RapidXml
loadVertices(): This sets up an OpenGL Vertex Buer Object
and generate verces from le data
loadIndexes(): This generates an index buer with indexes delimitang
two triangle polygons for each le
A le map requires the following:
A texture containing the sprite sheet.
Two resource handles (mVertexBuffer, mIndexBuffer) poinng
to OpenGL vertex and index buers, the number of elements they
contain (mVertexCount, mIndexCount) and the number of coordinate
components (X/Y/Z and U/V coordinates in mVertexComponent).
Informaon about the number of les in the nal map (mWidth and
mHeight).
A descripon of le width and height in pixels (mTileHeight and
mTileHeight) and count (mTileCount and mTileXCount).
Chapter 6
[ 223 ]
#ifndef _PACKT_GRAPHICSTILEMAP_HPP_
#define _PACKT_GRAPHICSTILEMAP_HPP_
#include "GraphicsTexture.hpp"
#include "Types.hpp"
#include <android_native_app_glue.h>
namespace packt {
class GraphicsTileMap {
public:
GraphicsTileMap(android_app* pApplication, const
char* pPath,
GraphicsTexture* pTexture, Location*
pLocation);
status load();
void unload();
void draw();
private:
int32_t* loadFile();
void loadVertices(int32_t* pTiles, uint8_t**
pVertexBuffer,
uint32_t* pVertexBufferSize);
void loadIndexes(uint8_t** pIndexBuffer,
uint32_t* pIndexBufferSize);
private:
Resource mResource;
Location* mLocation;
// OpenGL resources.
GraphicsTexture* mTexture;
GLuint mVertexBuffer, mIndexBuffer;
int32_t mVertexCount, mIndexCount;
const int32_t mVertexComponents;
// Tile map description.
int32_t mHeight, mWidth;
int32_t mTileHeight, mTileWidth;
int32_t mTileCount, mTileXCount;
};
}
#endif
Rendering Graphics with OpenGL ES
[ 224 ]
7. Start implemenng GraphicsTileMap in jni/GraphicsTileMap.cpp. Because
excepons are not supported in the current project, dene parse_error_
handler() method to handle parsing problems. By design, result of this handler is
undened (that is, a crash). So implement a non-local jump instead, similar to what
we have done for libpng:
#include "GraphicsTileMap.hpp"
#include "Log.hpp"
#include <GLES/gl.h>
#include <GLES/glext.h>
#include "rapidxml.hpp"
namespace rapidxml {
static jmp_buf sJmpBuffer;
void parse_error_handler(const char* pWhat, void* pWhere) {
packt::Log::error("Error while parsing TMX file.");
packt::Log::error(pWhat);
longjmp(sJmpBuffer, 0);
}
}
namespace packt {
GraphicsTileMap::GraphicsTileMap(android_app* pApplication,
const char* pPath, GraphicsTexture* pTexture,
Location* pLocation) :
mResource(pApplication, pPath), mLocation(pLocation),
mTexture(pTexture), mVertexBuffer(0), mIndexBuffer(0),
mVertexCount(0), mIndexCount(0), mVertexComponents(5),
mHeight(0), mWidth(0),
mTileHeight(0), mTileWidth(0), mTileCount(0),
mTileXCount(0)
{}
...
8. Let's write the code necessary to read a TMX le exported by Tiled. Asset le is
read with resource and copied into a temporary buer which is not modiable
(the opposite of the buer returned by bufferize(), which is agged
with const).
RapidXml parses XML les through an xml_document instance. It works directly
on the provided buer, which it may modify to normalize space, translate character
enes, or terminate strings with zero. A non-destrucve mode without these
features is also available. XML nodes and aributes can then be retrieved easily:
Chapter 6
[ 225 ]
...
int32_t* GraphicsTileMap::loadFile() {
using namespace rapidxml;
xml_document<> lXmlDocument;
xml_node<>* lXmlMap, *lXmlTileset, *lXmlLayer;
xml_node<>* lXmlTile, *lXmlData;
xml_attribute<>* lXmlTileWidth, *lXmlTileHeight;
xml_attribute<>* lXmlWidth, *lXmlHeight, *lXmlGID;
char* lFileBuffer = NULL; int32_t* lTiles = NULL;
if (mResource.open() != STATUS_OK) goto ERROR;
{
int32_t lLength = mResource.getLength();
if (lLength <= 0) goto ERROR;
const void* lFileBufferTmp = mResource.bufferize();
if (lFileBufferTmp == NULL) goto ERROR;
lFileBuffer = new char[mResource.getLength() + 1];
memcpy(lFileBuffer, lFileBufferTmp,mResource.getLength());
lFileBuffer[mResource.getLength()] = '\0';
mResource.close();
}
// Parses the document. Jumps back here if an error occurs
if (setjmp(sJmpBuffer)) goto ERROR;
lXmlDocument.parse<parse_default>(lFileBuffer);
// Reads XML tags.
lXmlMap = lXmlDocument.first_node("map");
if (lXmlMap == NULL) goto ERROR;
lXmlTileset = lXmlMap->first_node("tileset");
if (lXmlTileset == NULL) goto ERROR;
lXmlTileWidth = lXmlTileset->first_attribute("tilewidth");
if (lXmlTileWidth == NULL) goto ERROR;
lXmlTileHeight = lXmlTileset->first_attribute("tileheight");
if (lXmlTileHeight == NULL) goto ERROR;
lXmlLayer = lXmlMap->first_node("layer");
if (lXmlLayer == NULL) goto ERROR;
lXmlWidth = lXmlLayer->first_attribute("width");
if (lXmlWidth == NULL) goto ERROR;
lXmlHeight = lXmlLayer->first_attribute("height");
if (lXmlHeight == NULL) goto ERROR;
Rendering Graphics with OpenGL ES
[ 226 ]
lXmlData = lXmlLayer->first_node("data");
if (lXmlData == NULL) goto ERROR;
...
9. Connue implemenng loadFile() by inializing member data. Aer that, load
each le index into a new memory buer that we will use later to create a vertex
buer. Note that vercal coordinates are reversed between TMX and OpenGL
coordinates and that TMX les rst le index is 1 instead of 0 (hence -1 when
seng lTiles[] value):
...
mWidth = atoi(lXmlWidth->value());
mHeight = atoi(lXmlHeight->value());
mTileWidth = atoi(lXmlTileWidth->value());
mTileHeight = atoi(lXmlTileHeight->value());
if ((mWidth <= 0) || (mHeight <= 0)
|| (mTileWidth <= 0) || (mTileHeight <= 0)) goto ERROR;
mTileXCount = mTexture->getWidth()/mTileWidth;
mTileCount = mTexture->getHeight()/mTileHeight * mTileXCount;
lTiles = new int32_t[mWidth * mHeight];
lXmlTile = lXmlData->first_node("tile");
for (int32_t lY = mHeight - 1; lY >= 0; --lY) {
for (int32_t lX = 0; lX < mWidth; ++lX) {
if (lXmlTile == NULL) goto ERROR;
lXmlGID = lXmlTile->first_attribute("gid");
lTiles[lX + (lY * mWidth)] = atoi(lXmlGID->value())-1;
if (lTiles[lX + (lY * mWidth)] < 0) goto ERROR;
lXmlTile = lXmlTile->next_sibling("tile");
}
}
delete[] lFileBuffer;
return lTiles;
ERROR:
mResource.close();
delete[] lFileBuffer; delete[] lTiles;
mHeight = 0; mWidth = 0;
mTileHeight = 0; mTileWidth = 0;
return NULL;
}
...
Chapter 6
[ 227 ]
10. Now the big piece: loadVertices(), populang a temporary memory buer with
verces. First we need to compute some informaon such as the total number of
verces and allocate the buer accordingly, knowing that it contains four verces
composed of ve oat components (X/Y/Z and U/V) per le. We also need to know
the size of a texel, that is, the size of one pixel in UV coordinates. UV coordinates are
bound to [0,1] where 0 means texture le or boom and 1 texture right or boom.
Then, we basically loop over each le and compute vertex coordinates (X/Y
posion and UV coordinates) at the right oset (that is, locaon) in the buer. UV
coordinates are slightly shied to avoid seams at le edges especially when using
bilinear ltering which can cause adjacent le textures to be blended:
...
void GraphicsTileMap::loadVertices(int32_t* pTiles,
uint8_t** pVertexBuffer, uint32_t*
pVertexBufferSize) {
mVertexCount = mHeight * mWidth * 4;
*pVertexBufferSize = mVertexCount * mVertexComponents;
GLfloat* lVBuffer = new GLfloat[*pVertexBufferSize];
*pVertexBuffer = reinterpret_cast<uint8_t*>(lVBuffer);
int32_t lRowStride = mWidth * 2;
GLfloat lTexelWidth = 1.0f / mTexture->getWidth();
GLfloat lTexelHeight = 1.0f / mTexture->getHeight();
int32_t i;
for (int32_t tileY = 0; tileY < mHeight; ++tileY) {
for (int32_t tileX = 0; tileX < mWidth; ++tileX) {
// Finds current tile index (0 for 1st tile, 1...).
int32_t lTileSprite = pTiles[tileY * mWidth + tileX]
% mTileCount;
int32_t lTileSpriteX = (lTileSprite % mTileXCount)
* mTileWidth;
int32_t lTileSpriteY = (lTileSprite / mTileXCount)
* mTileHeight;
// Values to compute vertex offsets in the buffer.
int32_t lOffsetX1 = tileX * 2;
int32_t lOffsetX2 = tileX * 2 + 1;
int32_t lOffsetY1 = (tileY * 2) * (mWidth * 2);
int32_t lOffsetY2 = (tileY * 2 + 1) * (mWidth * 2);
// Vertex positions in the scene.
GLfloat lPosX1 = tileX * mTileWidth;
Rendering Graphics with OpenGL ES
[ 228 ]
GLfloat lPosX2 = (tileX + 1) * mTileWidth;
GLfloat lPosY1 = tileY * mTileHeight;
GLfloat lPosY2 = (tileY + 1) * mTileHeight;
// Tile UV coordinates (coordinates origin needs to be
// translated from top-left to bottom-left origin).
GLfloat lU1 = (lTileSpriteX) * lTexelWidth;
GLfloat lU2 = lU1 + (mTileWidth * lTexelWidth);
GLfloat lV2 = 1.0f - (lTileSpriteY) * lTexelHeight;
GLfloat lV1 = lV2 - (mTileHeight * lTexelHeight);
// Small shift to limit edge artifacts (1/4 of texel).
lU1 += lTexelWidth/4.0f; lU2 -= lTexelWidth/4.0f;
lV1 += lTexelHeight/4.0f; lV2 -=
lTexelHeight/4.0f;
// 4 vertices per tile in the vertex buffer.
i = mVertexComponents * (lOffsetY1 + lOffsetX1);
lVBuffer[i++] = lPosX1; lVBuffer[i++] = lPosY1;
lVBuffer[i++] = 0.0f;
lVBuffer[i++] = lU1; lVBuffer[i++] = lV1;
i = mVertexComponents * (lOffsetY1 + lOffsetX2);
lVBuffer[i++] = lPosX2; lVBuffer[i++] = lPosY1;
lVBuffer[i++] = 0.0f;
lVBuffer[i++] = lU2; lVBuffer[i++] = lV1;
i = mVertexComponents * (lOffsetY2 + lOffsetX1);
lVBuffer[i++] = lPosX1; lVBuffer[i++] = lPosY2;
lVBuffer[i++] = 0.0f;
lVBuffer[i++] = lU1; lVBuffer[i++] = lV2;
i = mVertexComponents * (lOffsetY2 + lOffsetX2);
lVBuffer[i++] = lPosX2; lVBuffer[i++] = lPosY2;
lVBuffer[i++] = 0.0f;
lVBuffer[i++] = lU2; lVBuffer[i++] = lV2;
}
}
}
...
11. Our vertex buer is prey useless without its index buer companion. Populate it
with two triangle polygons per le (that is, 6 indexes) to form quad:
...
void GraphicsTileMap::loadIndexes(uint8_t** pIndexBuffer,
uint32_t* pIndexBufferSize)
{
mIndexCount = mHeight * mWidth * 6;
*pIndexBufferSize = mIndexCount;
GLushort* lIBuffer = new GLushort[*pIndexBufferSize];
Chapter 6
[ 229 ]
*pIndexBuffer = reinterpret_cast<uint8_t*>(lIBuffer);
int32_t lRowStride = mWidth * 2;
int32_t i = 0;
for (int32_t tileY = 0; tileY < mHeight; tileY++) {
int32_t lIndexY = tileY * 2;
for (int32_t tileX = 0; tileX < mWidth; tileX++) {
int32_t lIndexX = tileX * 2;
// Values to compute vertex offsets in the buffer.
GLshort lVertIndexY1 = lIndexY * lRowStride;
GLshort lVertIndexY2 = (lIndexY + 1) * lRowStride;
GLshort lVertIndexX1 = lIndexX;
GLshort lVertIndexX2 = lIndexX + 1;
// 2 triangles per tile in the index buffer.
lIBuffer[i++] = lVertIndexY1 + lVertIndexX1;
lIBuffer[i++] = lVertIndexY1 + lVertIndexX2;
lIBuffer[i++] = lVertIndexY2 + lVertIndexX1;
lIBuffer[i++] = lVertIndexY2 + lVertIndexX1;
lIBuffer[i++] = lVertIndexY1 + lVertIndexX2;
lIBuffer[i++] = lVertIndexY2 + lVertIndexX2;
}
}
}
...
12. In GraphicsTileMap.cpp, terminate loading code by generang nal buers with
glGenBuffers() and binding them (to indicate we are working on them) with
glBindBuffer(). Then, push vertex and index buer data into graphics memory
through glBufferData(). Our temporary buers can then be discarded:
status GraphicsTileMap::load() {
GLenum lErrorResult;
uint8_t* lVertexBuffer = NULL, *lIndexBuffer = NULL;
uint32_t lVertexBufferSize, lIndexBufferSize;
// Loads tiles and creates temporary vertex/index buffers.
int32_t* lTiles = loadFile();
Rendering Graphics with OpenGL ES
[ 230 ]
if (lTiles == NULL) goto ERROR;
loadVertices(lTiles, &lVertexBuffer, &lVertexBufferSize);
if (lVertexBuffer == NULL) goto ERROR;
loadIndexes(&lIndexBuffer, &lIndexBufferSize);
if (lIndexBuffer == NULL) goto ERROR;
// Generates new buffer names.
glGenBuffers(1, &mVertexBuffer);
glGenBuffers(1, &mIndexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer);
// Loads buffers into OpenGL.
glBufferData(GL_ARRAY_BUFFER, lVertexBufferSize *
sizeof(GLfloat), lVertexBuffer, GL_STATIC_DRAW);
lErrorResult = glGetError();
if (lErrorResult != GL_NO_ERROR) goto ERROR;
glBufferData(GL_ELEMENT_ARRAY_BUFFER, lIndexBufferSize *
sizeof(GLushort), lIndexBuffer, GL_STATIC_DRAW);
lErrorResult = glGetError();
if (lErrorResult != GL_NO_ERROR) goto ERROR;
// Unbinds buffers.
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
delete[] lTiles; delete[] lVertexBuffer;
delete[] lIndexBuffer;
return STATUS_OK;
ERROR:
Log::error("Error loading tilemap");
unload();
delete[] lTiles; delete[] lVertexBuffer;
delete[] lIndexBuffer;
return STATUS_KO;
}
...
Chapter 6
[ 231 ]
13. We are done with resource loading. Take care of unloading them in unload():
...
void GraphicsTileMap::unload() {
mHeight = 0, mWidth = 0;
mTileHeight = 0, mTileWidth = 0;
mTileCount = 0, mTileXCount = 0;
if (mVertexBuffer != 0) {
glDeleteBuffers(1, &mVertexBuffer);
mVertexBuffer = 0; mVertexCount = 0;
}
if (mIndexBuffer != 0) {
glDeleteBuffers(1, &mIndexBuffer);
mIndexBuffer = 0; mIndexCount = 0;
}
}
...
14. To nish with GraphicsTileMap.cpp, write draw() method to render the
le map:
Bind the le sheet texture for rendering.
Set up geometry transformaons with glTranslatef() to posion
the map to its nal coordinates in the scene. Note that matrices are
hierarchical, hence the preliminary call to glPushMatrix() to stack
le map matrix on top of the projecon and world matrices. Posion
coordinates are rounded to prevent seams from appearing between les
because of rendering interpolaon.
Enable, bind, and describe vertex and index buer contents
with glEnableClientState(), glVertexPointer(), and
glTexCoordPointer().
Issue a rendering call to draw the whole map mesh with
glDrawElements().
Reset OpenGL machine state when done.
...
void GraphicsTileMap::draw() {
int32_t lVertexSize = mVertexComponents *
sizeof(GLfloat);
GLvoid* lVertexOffset = (GLvoid*) 0;
GLvoid* lTexCoordOffset = (GLvoid*)
(sizeof(GLfloat) * 3);
mTexture->apply();
Rendering Graphics with OpenGL ES
[ 232 ]
glPushMatrix();
glTranslatef(int32_t(mLocation->mPosX + 0.5f),
int32_t(mLocation->mPosY + 0.5f),
0.0f);
// Draws using hardware buffers
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
mIndexBuffer);
glVertexPointer(3, GL_FLOAT, lVertexSize,
lVertexOffset);
glTexCoordPointer(2, GL_FLOAT, lVertexSize,
lTexCoordOffset);
glDrawElements(GL_TRIANGLES, mIndexCount,
GL_UNSIGNED_SHORT, 0 * sizeof(GLushort));
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glPopMatrix();
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
}
Let's append our new le map module to the applicaon:
15. Like for textures and sprites, let GraphicsService manage le maps:
#ifndef _PACKT_GRAPHICSSERVICE_HPP_
#define _PACKT_GRAPHICSSERVICE_HPP_
#include "GraphicsSprite.hpp"
#include "GraphicsTexture.hpp"
#include "GraphicsTileMap.hpp"
#include "TimeService.hpp"
#include "Types.hpp"
#include <android_native_app_glue.h>
#include <EGL/egl.h>
namespace packt {
class GraphicsService {
Chapter 6
[ 233 ]
public:
...
GraphicsTexture* registerTexture(const char* pPath);
GraphicsSprite* registerSprite(GraphicsTexture* pTexture,
int32_t pHeight, int32_t pWidth, Location* pLocation);
GraphicsTileMap* registerTileMap(const char* pPath,
GraphicsTexture* pTexture, Location* pLocation);
...
private:
...
GraphicsTexture* mTextures[32]; int32_t mTextureCount;
GraphicsSprite* mSprites[256]; int32_t mSpriteCount;
GraphicsTileMap* mTileMaps[8]; int32_t mTileMapCount;
};
}
#endif
16. In jni/GraphicsService.cpp, implement registerTileMap() and update
load(), unload(), and class destructor like for sprites previous tutorial.
Change setup() to push a projecon and ModelView matrix in the matrix stack:
Projecon is orthographic since 2D games do not need a perspecve eect.
ModelView matrix describes basically the posion and orientaon of the
camera. Here, camera (that is, the whole scene) does not move; only the
background le map moves to simulate a scrolling eect. Thus, a simple
identy matrix is sucient.
Then, modify update() to eecvely draw le maps:
...
namespace packt {
...
void GraphicsService::setup() {
glEnable(GL_TEXTURE_2D);
glDisable(GL_DEPTH_TEST);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrthof(0.0f, mWidth, 0.0f, mHeight, 0.0f, 1.0f);
glMatrixMode( GL_MODELVIEW);
glLoadIdentity();
}
Rendering Graphics with OpenGL ES
[ 234 ]
status GraphicsService::update() {
float lTimeStep = mTimeService->elapsed();
for (int32_t i = 0; i < mTileMapCount; ++i) {
mTileMaps[i]->draw();
}
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
for (int32_t i = 0; i < mSpriteCount; ++i) {
mSprites[i]->draw(lTimeStep);
}
glDisable(GL_BLEND);
if (eglSwapBuffers(mDisplay, mSurface) != EGL_TRUE) {
Log::error("Error %d swapping buffers.", eglGetError());
return STATUS_KO;
}
return STATUS_OK;
}
}
17. Write jni/Background.hpp to declare a game object drawing a background
le map:
#ifndef _DBS_BACKGROUND_HPP_
#define _DBS_BACKGROUND_HPP_
#include "Context.hpp"
#include "GraphicsService.hpp"
#include "GraphicsTileMap.hpp"
#include "Types.hpp"
namespace dbs {
class Background {
public:
Background(packt::Context* pContext);
void spawn();
void update();
private:
Chapter 6
[ 235 ]
packt::TimeService* mTimeService;
packt::GraphicsService* mGraphicsService;
packt::GraphicsTileMap* mTileMap;
packt::Location mLocation; float mAnimSpeed;
};
}
#endif
18. Then implement this class in jni/Background.cpp. Register a le map tilemap.
tmx which must be copied in asset project folder:
File tilemap.tmx is provided with this book
in the Chapter6/Resource folder.
#include "Background.hpp"
#include "Log.hpp"
namespace dbs {
Background::Background(packt::Context* pContext) :
mTimeService(pContext->mTimeService),
mGraphicsService(pContext->mGraphicsService),
mLocation(), mAnimSpeed(8.0f) {
mTileMap = mGraphicsService->registerTileMap("tilemap.
tmx",
mGraphicsService->registerTexture("tilemap.png"),
&mLocation);
}
void Background::update() {
const float SCROLL_PER_SEC = -64.0f;
float lScrolling = mTimeService->elapsed() * SCROLL_PER_
SEC;
mLocation.translate(0.0f, lScrolling);
}
}
19. We are close to the end. Add a Background object in jni/DroidBlaster.hpp:
#ifndef _PACKT_DROIDBLASTER_HPP_
#define _PACKT_DROIDBLASTER_HPP_
#include "ActivityHandler.hpp"
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
Rendering Graphics with OpenGL ES
[ 236 ]
#include "Background.hpp"
#include "Context.hpp"
#include "GraphicsService.hpp"
#include "Ship.hpp"
#include "TimeService.hpp"
#include "Types.hpp"
namespace dbs {
class DroidBlaster : public packt::ActivityHandler {
...
Background mBackground;
Ship mShip;
};
}
#endif
20. Finally, inialize, and update this Background object in jni/DroidBlaster.cpp:
#include "DroidBlaster.hpp"
#include "Log.hpp"
namespace dbs {
DroidBlaster::DroidBlaster(packt::Context* pContext) :
mGraphicsService(pContext->mGraphicsService),
mTimeService(pContext->mTimeService),
mBackground(pContext), mShip(pContext)
{}
packt::status DroidBlaster::onActivate() {
if (mGraphicsService->start() != packt::STATUS_OK) {
return packt::STATUS_KO;
}
mBackground.spawn();
mShip.spawn();
mTimeService->reset();
return packt::STATUS_OK;
}
status DroidBlaster::onStep() {
mTimeService->update();
mBackground.update();
Chapter 6
[ 237 ]
if (mGraphicsService->update() != packt::STATUS_OK) {
return packt::STATUS_KO;
}
return packt::STATUS_OK;
}
}
What just happened?
The nal result should look like the following. The terrain is scrolling below the ship:
Vertex Buer Objects, coupled with index buers, are a really ecient way to render lots
of polygons in a single call, by pre-compung verces and textures coordinates in advance.
They largely minimize the number of necessary state changes. Buer objects are also
denitely the way to go for 3D rendering. Note however that if this technique is ecient
when many les are rendered, it will be much less interesng if your background is only
composed of only a few les, in which case sprites may be more appropriate.
However, the work done in this part can sll be vastly improved. The le map rendering
method here is inecient: it draws the whole vertex buer systemacally. Hopefully, today's
graphic drivers are opmized to clip invisible verces, which sll gives us good performance.
But an algorithm could, for example, issue draw calls only for the visible porons of the
vertex buer.
This le map technique also allows mulple extensions. For example, several le maps
scrolled at dierent speeds can be superposed to create a parallax eect. Of course, one
would need to enable alpha blending (at step 16 in GraphicsService::update()) to
properly blend layers. Let your imaginaon do the rest!
Rendering Graphics with OpenGL ES
[ 238 ]
Summary
OpenGL and graphics, in general, is a really vast domain. One book is not enough to cover it
enrely. But drawing 2D graphics with textures and buer objects opens the door to much
more advanced stu! In more detail, we have learned how to inialize and bind OpenGL ES
to the Android windows with EGL. We have also loaded a PNG texture packaged as assets
with an external library. Then, we have drawn sprites eciently with OpenGL ES extensions.
This technique should not be overused as it can impact performance when many sprites are
blied. Finally, we have rendered a le map eciently by pre-compung rendered les in
vertex and index buers.
With the knowledge acquired here, the road to OpenGL ES 2 is at a perfectly walkable
distance! But if you cannot wait to see 3D graphics, Chapter 9, Porng Exisng Libraries to
Android and Chapter 10, Towards Professional Gaming, are your next desnaon to discover
how to embed a 3D engine. But if you are a bit more paent, let's discover how to reach the
fourth dimension, the musical one, with OpenSL ES.
7
Playing Sound with OpenSL ES
Mulmedia is not only about graphics; it is also about sound and music.
Applicaons in this domain are among the most popular in the Android market.
Indeed, music has always been a strong engine for mobile devices sales
and music lovers are a target of choice. This is why an OS like Android could
probably not go far without some musical talent!
When talking about sound on Android, we should disnguish Java from the
nave world. Indeed, both sides feature completely dierent APIs: MediaPlayer,
SoundPool, AudioTrack, and JetPlayer on one hand, Open SL for Embedded
Systems (also abbreviated OpenSL ES) on the other hand:
MediaPlayer is more high-level and easy to use. It handles not only
music but also video. It is the way to go when simple le playback
is sucient.
SoundPool and AudioTrack are more low-level and closer to low
latency when playing sound. AudioTrack is the most exible but
also complex to use and allows sound buer modicaons on the
y (by hand!).
JetPlayer is more dedicated to the playback of MIDI les. This API
can be interesng for dynamic musing synthesis in a mulmedia
applicaon or game (the see JetBoy example provided with Android SDK).
OpenSL ES which aims at oering a cross-plaorm API to manage audio
on embedded systems. In other words, the OpenGL ES for audio. Like
GLES, its specicaon is led by the Khronos Group. On Android,
OpenSL ES is in fact implemented on top of AudioTrack API.
OpenSL ES was rst released on Android 2.3 Gingerbread and is not available
on previous releases (Android 2.2 and lower). While there is a profusion of APIs
in Java, OpenSL ES is the only one provided on the nave side and is exclusively
available on it.
Playing Sound with OpenSL ES
[ 240 ]
However, OpenSL ES is sll immature. The OpenSL specicaon is sll
incompletely supported and several limitaons shall be expected. In addion,
OpenSL specicaon is implemented in its version 1.0.1 on Android although
version 1.1 is already out. Thus, OpenSL ES implementaon is not frozen yet
and should connue evolving. Some subsequent change may have to be
expected in the future.
For this reason, 3D Audio features are available starng from Android 2.3
through OpenSL ES, but only for devices whose system is compiled with the
appropriate prole. Indeed, current OpenSL ES specicaon provides three
dierent proles, Game, Music, and Phone for dierent types of devices.
At the me this book is wrien, none of these proles are supported.
Another important point to consider is that Android is currently not suited for
low latency! OpenSL ES API does not improve this situaon. This issue is not only
related to the system itself but also to the hardware. And if latency is becoming
a concern for the Android development team and manufacturers, months will be
needed to see decent progress. Anyway, expect OpenSL ES and low-level Java APIs
SoundPool and AudioTrack to support low latency sooner or later.
But OpenSL ES has qualies. First, it may be easier to integrate in the
architecture of a nave applicaon, since it is itself wrien in C/C++. It does not
have to carry a garbage collector on its back. Nave code is not interpreted and
can be opmized in-depth through assembly code (and the NEON instrucon
set). These are some of the many reasons to consider it.
The OpenMAX AL low-level mulmedia API is also available since NDK R7
(although not fully supported). This API is, however, more related to video/
sound playback and is less powerful than Open SL ES for sound and music.
It is somewhat similar to the android.media.MediaPlayer on the
Java side. Have a look at http://www.khronos.org/openmax/ for
more informaon.
Chapter 7
[ 241 ]
This chapter is an introducon to the musical capabilies of OpenSL ES on the Android NDK.
We are about to discover how to do the following:
Inialize OpenSL ES on Android
Play background music
Play sounds with a sound buer queue
Record sounds and play them
Initializing OpenSL ES
Lets start this chapter smoothly by inializing OpenSL ES inside a new service, which we
are going to call SoundService (the term service is just a design choice and should not
be confused with Android Java services).
Project DroidBlaster_Part6-4 can be used as a starng point for
this part. The resulng project is provided with this book under the
name DroidBlaster_Part7-1.
Time for action – creating OpenSL ES engine and output
First, lets create this new class to manage sounds:
1. Open project DroidBlaster and create a new le jni/SoundService.hpp.
First, include OpenSL ES headers: the standard header OpenSLES.h, OpenSLES_
Android.h, and OpenSLES_AndroidConfiguration.h. The two laer dene
objects and methods , and are specically created for Android. Then create
SoundService class to do the following:
Inialize OpenSL ES with the method start()
Stop the sound and release OpenSL ES with the method stop()
There are two main kinds of pseudo-object structures (that is, containing funcon
pointers applied on the structure itself like a C++ object with this) in OpenSL ES:
Objects: These are represented by a SLObjectItf, which provides a few
common methods to get allocated resources and get object interfaces. This
could be roughly compared to an Object in Java.
Interfaces: These give access to object features. There can be several
interfaces for an object. Depending on the host device, some interfaces
may or may not be available. These are very roughly comparable to
interfaces in Java.
Playing Sound with OpenSL ES
[ 242 ]
In SoundService, declare two SLObjectItf instances, one for the
OpenSL ES engine and other for the speakers. Engines are available through
an SLEngineItf interface:
#ifndef _PACKT_SOUNDSERVICE_HPP_
#define _PACKT_SOUNDSERVICE_HPP_
#include “Types.hpp”
#include <android_native_app_glue.h>
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <SLES/OpenSLES_AndroidConfiguration.h>
namespace packt {
class SoundService {
public:
SoundService(android_app* pApplication);
status start();
void stop();
private:
android_app* mApplication;
SLObjectItf mEngineObj; SLEngineItf mEngine;
SLObjectItf mOutputMixObj;
};
}
#endif
2. Implement SoundService in jni/SoundService.cpp. Write method start():
Inialize OpenSL ES engine object (that is, the basic type SLObjectItf)
with method slCreateEngine(). When we create an OpenSL ES object,
the specic interfaces we are going to use have to be indicated. Here, we
request (as compulsory) the SL_IID_ENGINE interface to create other
OpenSL ES objects, the engine being the central object of the OpenSL ES API.
Android OpenSL ES implementaon is not really strict. Forgeng to declare
some required interfaces does not mean you will not be allowed to access
them later.
Chapter 7
[ 243 ]
Then, invoke Realize() on the engine object. Any OpenSL ES object needs
to be realized to allocate required internal resources before use.
Finally, retrieve SLEngineItf-specic interface.
The engine interface gives us the possibility to instanate an audio output
mix with the method CreateOutputMix(). The audio output mix dened
here delivers sound to default speakers. It is rather autonomous (played
sound is sent automacally to the speaker), so there is no need to request
any specic interface here.
#include “SoundService.hpp”
#include “Log.hpp”
namespace packt {
SoundService::SoundService(android_app* pApplication):
mApplication(pApplication),
mEngineObj(NULL), mEngine(NULL),
mOutputMixObj(NULL)
{}
status SoundService::start() {
Log::info(“Starting SoundService.”);
SLresult lRes;
const SLuint32 lEngineMixIIDCount = 1;
const SLInterfaceID lEngineMixIIDs[]={SL_IID_ENGINE};
const SLboolean lEngineMixReqs[]={SL_BOOLEAN_TRUE};
const SLuint32 lOutputMixIIDCount=0;
const SLInterfaceID lOutputMixIIDs[]={};
const SLboolean lOutputMixReqs[]={};
lRes = slCreateEngine(&mEngineObj, 0, NULL,
lEngineMixIIDCount, lEngineMixIIDs, lEngineMixReqs);
if (lRes != SL_RESULT_SUCCESS) goto ERROR;
lRes=(*mEngineObj)->Realize(mEngineObj,SL_BOOLEAN_FALSE);
if (lRes != SL_RESULT_SUCCESS) goto ERROR;
lRes=(*mEngineObj)->GetInterface(mEngineObj,
SL_IID_ENGINE, &mEngine);
if (lRes != SL_RESULT_SUCCESS) goto ERROR;
lRes=(*mEngine)->CreateOutputMix(mEngine,
&mOutputMixObj,lOutputMixIIDCount,lOutputMixIIDs,
lOutputMixReqs);
Playing Sound with OpenSL ES
[ 244 ]
lRes=(*mOutputMixObj)->Realize(mOutputMixObj,
SL_BOOLEAN_FALSE);
return STATUS_OK;
ERROR:
Packt::Log::error(“Error while starting SoundService.”);
stop();
return STATUS_KO;
}
...
3. Write the stop() method to destroy what has been created in start():
...
void SoundService::stop() {
if (mOutputMixObj != NULL) {
(*mOutputMixObj)->Destroy(mOutputMixObj);
mOutputMixObj = NULL;
}
if (mEngineObj != NULL) {
(*mEngineObj)->Destroy(mEngineObj);
mEngineObj = NULL; mEngine = NULL;
}
}
}
Now, we can embed our new service:
4. Open exisng le jni/Context.hpp and dene a new entry for
SoundService:
#ifndef _PACKT_CONTEXT_HPP_
#define _PACKT_CONTEXT_HPP_
#include “Types.hpp”
namespace packt {
class GraphicsService;
class SoundService;
class TimeService;
struct Context {
GraphicsService* mGraphicsService;
Chapter 7
[ 245 ]
SoundService* mSoundService;
TimeService* mTimeService;
};
}
#endif
5. Then, append SoundService inside jni/DroidBlaster.hpp:
#ifndef _PACKT_DROIDBLASTER_HPP_
#define _PACKT_DROIDBLASTER_HPP_
#include “ActivityHandler.hpp”
#include “Background.hpp”
#include “Context.hpp”
#include “GraphicsService.hpp”
#include “Ship.hpp”
#include “SoundService.hpp”
#include “TimeService.hpp”
#include “Types.hpp”
namespace dbs {
class DroidBlaster : public packt::ActivityHandler {
...
private:
packt::GraphicsService* mGraphicsService;
packt::SoundService* mSoundService;
packt::TimeService* mTimeService;
Background mBackground;
Ship mShip;
};
}
#endif
6. Create, start, and stop the sound service in jni/DroidBlaster.cpp source le.
Code implementaon should be trivial:
#include “DroidBlaster.hpp”
#include “Log.hpp”
namespace dbs {
DroidBlaster::DroidBlaster(packt::Context* pContext) :
mGraphicsService(pContext->mGraphicsService),
mSoundService(pContext->mSoundService),
Playing Sound with OpenSL ES
[ 246 ]
mTimeService(pContext->mTimeService),
mBackground(pContext), mShip(pContext)
{}
packt::status DroidBlaster::onActivate() {
if (mGraphicsService->start() != packt::STATUS_OK) {
return packt::STATUS_KO;
}
if (mSoundService->start() != packt::STATUS_OK) {
return packt::STATUS_KO;
}
mBackground.spawn();
mShip.spawn();
mTimeService->reset();
return packt::STATUS_OK;
}
void DroidBlaster::onDeactivate() {
mGraphicsService->stop();
mSoundService->stop();
}
...
}
7. Finally, instanate the sound service in jni/Main.cpp:
#include “Context.hpp”
#include “DroidBlaster.hpp”
#include “EventLoop.hpp”
#include “GraphicsService.hpp”
#include “SoundService.hpp”
#include “TimeService.hpp”
void android_main(android_app* pApplication) {
packt::TimeService lTimeService;
packt::GraphicsService lGraphicsService(pApplication,
&lTimeService);
packt::SoundService lSoundService(pApplication);
packt::Context lContext = { &lGraphicsService, &lSoundService,
Chapter 7
[ 247 ]
&lTimeService };
packt::EventLoop lEventLoop(pApplication);
dbs::DroidBlaster lDroidBlaster(&lContext);
lEventLoop.run(&lDroidBlaster);
}
Link to libOpenSLES.so in the jni/Android.mk file:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LS_CPP=$(subst $(1)/,,$(wildcard $(1)/*.cpp))
LOCAL_CFLAGS := -DRAPIDXML_NO_EXCEPTIONS
LOCAL_MODULE := droidblaster
LOCAL_SRC_FILES := $(call LS_CPP,$(LOCAL_PATH))
LOCAL_LDLIBS := -landroid -llog -lEGL -lGLESv1_CM -lOpenSLES
LOCAL_STATIC_LIBRARIES := android_native_app_glue png
include $(BUILD_SHARED_LIBRARY)
$(call import-module,android/native_app_glue)
$(call import-module,libpng)
What just happened?
Run the applicaon and check that no error is logged. We have inialized OpenSL ES library
which gives us access to ecient sound handling primives directly from nave code. The
current code does not perform anything apart from inializaon. No sound comes out from
the speakers yet.
The entry point to OpenSL ES here is the SLEngineItf, which is mainly an OpenSL ES object
factory. It can create a channel to an output device (a speaker or anything else) as well as
sound players or recorders (and even more!), as we will see later in this chapter.
The SLOutputMixItf is the object represenng the audio output. Generally, this will be
the device speaker or headset. Although OpenSL ES specicaon allows enumerang
available output (and also input) devices, NDK implementaon is not mature enough to
obtain or select proper one (SLAudioIODeviceCapabilitiesItf, the ocial interface
to obtain such an informaon). So when dealing with output and input device selecon
(only input device for recorders needs to be specied currently), prefer scking to default
values: SL_DEFAULTDEVICEID_AUDIOINPUT and SL_DEFAULTDEVICEID_AUDIOOUTPUT
dened in OpenSLES.h.
Playing Sound with OpenSL ES
[ 248 ]
Current Android NDK implementaon allows only one engine per applicaon (this should not
be an issue) and at most 32 created objects. Beware however that creaon of any object can
fail as this is dependent on available system resources.
More on OpenSL ES philosophy
OpenSL ES is dierent from its graphics compatriot GLES, partly because it does not have a
long history to carry. It is constructed on an (more or less...) object-oriented principle based
on Objects and Interfaces. The following denions come from the ocial specicaon:
An object is an abstracon of a set of resources, assigned for a well-dened set
of tasks, and the state of these resources. An object has a type determined on its
creaon. The object type determines the set of tasks that an object can perform.
This can be considered similar to a class in C++.
An interface is an abstracon of a set of related features that a certain object
provides. An interface includes a set of methods, which are funcons of the
interface. An interface also has a type which determines the exact set of methods
of the interface. We can dene the interface itself as a combinaon of its type and
the object to which it is related.
An interface ID idenes an interface type. This idener is used within the source
code to refer to the interface type.
An OpenSL ES object is set up in few steps as follows:
1. Instanang it through a build method (belonging usually to the engine).
2. Realizing it to allocate necessary resources.
3. Retrieving object interfaces. A basic object only has a very limited set of operaons
(Realize(), Resume(), Destroy(), and so on). Interfaces give access to real
object features and describes what operaons can be performed on an object,
for example, a Play interface to play or pause a sound.
Any interfaces can be requested but only the one supported by the object is going to be
successfully retrieved. You cannot retrieve the record interface for an audio player because
it returns (somemes it is annoying!) SL_RESULT_FEATURE_UNSUPPORTED (error code 12).
In technical terms, an OpenSL ES interface is a structure containing funcon pointers
(inialized by OpenSL ES implementaon) with a self parameter to simulate C++ objects
and this , for example:
struct SLObjectItf_ {
SLresult (*Realize) (SLObjectItf self, SLboolean async);
SLresult (*Resume) ( SLObjectItf self, SLboolean async);
...
}
Chapter 7
[ 249 ]
Here, Realize(), Resume(), and so on are object methods that can be applied on an
SLObjectItf object. The approach is idencal for interfaces.
For more detailed informaon on what OpenSL ES can provide, refer to the specicaon
on Khronos web site: http://www.khronos.org/opensles as well as the OpenSL ES
documentaon in Android NDK docs directory. Android implementaon does not fully
respect the specicaon, at least for now. So do not be disappointed when discovering
that only a limited subset of the specicaon (especially sample codes) works on Android.
Playing music les
OpenSL ES is inialized, but the only thing coming out of speakers yet is silence! So what
about nding a nice piece of music (somemes abbreviated BGM) and playing it navely
with Android NDK? OpenSL ES provides the necessary stu to read music les such as MP3s.
Project DroidBlaster_Part7-1 can be used as a starng point
for this part. The resulng project is provided with this book
under the name DroidBlaster_Part7-2.
Time for action – playing background music
Lets improve the code wrien in the previous part to read and play an MP3 le:
1. MP3 les are opened by OpenSL ES using a POSIX le descriptor, poinng to the
le. Improve jni/ResourceManager.cpp created in the previous chapters by
injecng a new structure ResourceDescriptor and appending a new method
descript():
#ifndef _PACKT_RESOURCE_HPP_
#define _PACKT_RESOURCE_HPP_
#include “Types.hpp”
#include <android_native_app_glue.h>
namespace packt {
struct ResourceDescriptor {
int32_t mDescriptor;
off_t mStart;
off_t mLength;
};
Playing Sound with OpenSL ES
[ 250 ]
class Resource {
public:
...
off_t getLength();
const void* bufferize();
ResourceDescriptor descript();
private:
...
};
}
#endif
2. Implementaon in ResourceManager.cpp, of course, makes use of the asset
manager API to open the descriptor and ll a ResourceDescriptor structure:
...
namespace packt {
...
ResourceDescriptor Resource::descript() {
ResourceDescriptor lDescriptor = { -1, 0, 0 };
AAsset* lAsset = AAssetManager_open(mAssetManager, mPath,
AASSET_MODE_UNKNOWN);
if (lAsset != NULL) {
lDescriptor.mDescriptor = AAsset_openFileDescriptor(
lAsset, &lDescriptor.mStart, &lDescriptor.
mLength);
AAsset_close(lAsset);
}
return lDescriptor;
}
}
3. Go back to jni/SoundService.hpp and dene two methods, playBGM()
and stopBGM(), to play a background music.
Also declare an OpenSL ES object for the music player along with the
following interfaces:
SLPlayItf: This plays and stops music les
SLSeekItf: This controls posion and looping
Chapter 7
[ 251 ]
...
namespace packt
{
class SoundService {
public:
...
status playBGM(const char* pPath);
void stopBGM();
...
private:
...
SLObjectItf mBGMPlayerObj; SLPlayItf mBGMPlayer;
SLSeekItf mBGMPlayerSeek;
};
}
#endif
4. Start implemenng jni/SoundService.cpp. Include Resource.hpp to get
access to asset le descriptors. Inialize new members in constructor and update
stop() to stop the background music automacally (or some users are not going
to be happy!):
#include “SoundService.hpp”
#include “Resource.hpp”
#include “Log.hpp”
namespace packt {
SoundService::SoundService(android_app* pApplication) :
mApplication(pApplication),
mEngineObj(NULL), mEngine(NULL),
mOutputMixObj(NULL),
mBGMPlayerObj(NULL), mBGMPlayer(NULL), mBGMPlayerSeek(NULL)
{}
...
void SoundService::stop() {
stopBGM();
Playing Sound with OpenSL ES
[ 252 ]
if (mOutputMixObj != NULL) {
(*mOutputMixObj)->Destroy(mOutputMixObj);
mOutputMixObj = NULL;
}
if (mEngineObj != NULL) {
(*mEngineObj)->Destroy(mEngineObj);
mEngineObj = NULL; mEngine = NULL;
}
}
...
5. Enrich SoundService.cpp with playback features by implemenng playBGM().
First we need to describe our audio setup through two main structures:
SLDataSource and SLDataSink. The rst describes the audio input channel
and the second, the audio output channel.
Here, we congure the data source as a MIME source so that le type gets detected
automacally from le descriptor. File descriptor is, of course, opened with a call to
ResourceManager::descript().
Data sink (that is, desnaon channel) is congured with the OutputMix object
created in the rst part of this chapter while inializing OpenSL ES engine
(and which refers to default audio output, that is, speakers or headset):
...
status SoundService::playBGM(const char* pPath) {
SLresult lRes;
Resource lResource(mApplication, pPath);
ResourceDescriptor lDescriptor = lResource.descript();
if (lDescriptor.mDescriptor < 0) {
Log::info(“Could not open BGM file”);
return STATUS_KO;
}
SLDataLocator_AndroidFD lDataLocatorIn;
lDataLocatorIn.locatorType = SL_DATALOCATOR_ANDROIDFD;
lDataLocatorIn.fd = lDescriptor.mDescriptor;
lDataLocatorIn.offset = lDescriptor.mStart;
lDataLocatorIn.length = lDescriptor.mLength;
SLDataFormat_MIME lDataFormat;
lDataFormat.formatType = SL_DATAFORMAT_MIME;
Chapter 7
[ 253 ]
lDataFormat.mimeType = NULL;
lDataFormat.containerType = SL_CONTAINERTYPE_UNSPECIFIED;
SLDataSource lDataSource;
lDataSource.pLocator = &lDataLocatorIn;
lDataSource.pFormat = &lDataFormat;
SLDataLocator_OutputMix lDataLocatorOut;
lDataLocatorOut.locatorType = SL_DATALOCATOR_OUTPUTMIX;
lDataLocatorOut.outputMix = mOutputMixObj;
SLDataSink lDataSink;
lDataSink.pLocator = &lDataLocatorOut;
lDataSink.pFormat = NULL;
...
6. Then create the OpenSL ES audio player. As always with OpenSL ES objects,
instanate it through the engine rst and then realize it. Two interfaces
SL_IID_PLAY and SL_IID_SEEK are imperavely required:
...
const SLuint32 lBGMPlayerIIDCount = 2;
const SLInterfaceID lBGMPlayerIIDs[] =
{ SL_IID_PLAY, SL_IID_SEEK };
const SLboolean lBGMPlayerReqs[] =
{ SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
lRes = (*mEngine)->CreateAudioPlayer(mEngine,
&mBGMPlayerObj, &lDataSource, &lDataSink,
lBGMPlayerIIDCount, lBGMPlayerIIDs, lBGMPlayerReqs);
if (lRes != SL_RESULT_SUCCESS) goto ERROR;
lRes = (*mBGMPlayerObj)->Realize(mBGMPlayerObj,
SL_BOOLEAN_FALSE);
if (lRes != SL_RESULT_SUCCESS) goto ERROR;
lRes = (*mBGMPlayerObj)->GetInterface(mBGMPlayerObj,
SL_IID_PLAY, &mBGMPlayer);
if (lRes != SL_RESULT_SUCCESS) goto ERROR;
lRes = (*mBGMPlayerObj)->GetInterface(mBGMPlayerObj,
SL_IID_SEEK, &mBGMPlayerSeek);
if (lRes != SL_RESULT_SUCCESS) goto ERROR;
...
Playing Sound with OpenSL ES
[ 254 ]
7. Finally, using the play and seek interfaces, switch the playback in loop mode
(that is, music keeps playing) from the track beginning (that is, 0 ms) unl its
end (SL_TIME_UNKNOWN) and then start playing (SetPlayState() with
SL_PLAYSTATE_PLAYING).
...
lRes = (*mBGMPlayerSeek)->SetLoop(mBGMPlayerSeek,
SL_BOOLEAN_TRUE, 0, SL_TIME_UNKNOWN);
if (lRes != SL_RESULT_SUCCESS) goto ERROR;
lRes = (*mBGMPlayer)->SetPlayState(mBGMPlayer,
SL_PLAYSTATE_PLAYING);
if (lRes != SL_RESULT_SUCCESS) goto ERROR;
return STATUS_OK;
ERROR:
return STATUS_KO;
}
...
8. The last method stopBGM() is shorter. It stops and then destroys the player:
...
void SoundService::stopBGM() {
if (mBGMPlayer != NULL) {
SLuint32 lBGMPlayerState;
(*mBGMPlayerObj)->GetState(mBGMPlayerObj,
&lBGMPlayerState);
if (lBGMPlayerState == SL_OBJECT_STATE_REALIZED) {
(*mBGMPlayer)->SetPlayState(mBGMPlayer,
SL_PLAYSTATE_PAUSED);
(*mBGMPlayerObj)->Destroy(mBGMPlayerObj);
mBGMPlayerObj = NULL;
mBGMPlayer = NULL;
mBGMPlayerSeek = NULL;
}
}
}
}
Chapter 7
[ 255 ]
9. Copy an MP3 le into the assets directory and name it bgm.mp3.
File bgm.mp3 is provided with this book in Chapter7/Resource.
10. Finally, in jni/DroidBlaster.cpp, start music playback right aer
SoundService is started:
#include “DroidBlaster.hpp”
#include “Log.hpp”
namespace dbs {
...
packt::status DroidBlaster::onActivate() {
packt::Log::info(“Activating DroidBlaster”);
if (mGraphicsService->start() != packt::STATUS_OK) {
return packt::STATUS_KO;
}
if (mSoundService->start() != packt::STATUS_OK) {
return packt::STATUS_KO;
}
mSoundService->playBGM
mBackground.spawn();
mShip.spawn();
mTimeService->reset();
return packt::STATUS_OK;
}
void DroidBlaster::onDeactivate() {
mGraphicsService->stop();
mSoundService->stop();
}
...
}
Playing Sound with OpenSL ES
[ 256 ]
What just happened?
We have discovered how to play a music clip from an MP3 le. Playback loops unl the
game is terminated. When using a MIME data source, the le type is auto-detected. Several
formats are currently supported format in Gingerbread including Wave PCM, Wave alaw,
Wave ulaw, MP3, Ogg Vorbis and so on. MIDI playback is currently not supported.
You may be surprised to see that, in the example, startBGM() and stopBGM() recreates
and destroys the audio player, respecvely. The reason is that there is currently no way to
change a MIME data source without completely recreang the OpenSL ES AudioPlayer
object. So although this technique is ne to play a long clip, it is not adapted for playing
short sound dynamically.
The way the sample code is presented here is typical of how OpenSL ES works. The OpenSL
ES engine object, that kind of object factory, creates an AudioPlayer object which cannot
do much in that state. First, it needs to be realized to allocate necessary resources. But that
is not enough. It needs to retrieve the right interfaces, like the SL_IID_PLAY interface to
change audio player state to playing/stopped. Then OpenSL API can be eecvely used.
That is quite some work, taking into account result vericaon (as any call is suscepble to
fail), which kind of cluers the code. Geng inside this API can take a lile bit more me
than usual, but once understood, these concepts become rather easy to deal with.
Playing sounds
The technique presented to play BGM from a MIME source is very praccal but sadly, not
exible enough. Recreang an AudioPlayer object is not necessary and accessing asset
les each me is not good in term of eciency.
So when it comes to playing sounds quickly in response to an event and generang them
dynamically, we need to use a sound buer queue. Each sound is preloaded or even
generated into a memory buer, and placed into a queue when playback is requested. No
need to access a le at runme!
A sound buer, in current OpenSL ES Android implementaon, can contain PCM data. PCM,
which stands for Pulse Code Modulaon, is a data format dedicated to the representaon of
digital sounds. It is the format used in CD and in some Wave les. A PCM can be Mono (same
sound on all speakers) or Stereo (dierent sound for le and right speakers if available).
PCM is not compressed and is not ecient in terms of storage (just compare a musical CD
with a data CD full of MP3). But this format is lossless and oers the best quality. Quality
depends on the sampling rate: analog sounds are represented digitally as a series of measure
(that is, sample) of the sound signal.
Chapter 7
[ 257 ]
A sound sample at 44100 Hz (that is 44100 measures per second) has a beer quality
but also takes more place than a sound sampled at 16000 Hz. Also, each measure can
be represented with a more or less ne degree of precision (the encoding). On current
Android implementaon:
A sound can use 8000 Hz, 11025 Hz, 12000 Hz, 16000 Hz, 22050 Hz, 24000 Hz,
32000 Hz, 44100 Hz, or 48000 Hz sampling,
A sample can be encoded on 8-bit unsigned or 16-bit signed (ner precision) in
lile-endian or big-endian.
In the following step-by-step tutorial, we are going to use a raw PCM le encoded over
16-bit in lile-endian.
Project DroidBlaster_Part7-2 can be used as a starng point for
this part. The resulng project is provided with this book under the
name DroidBlaster_Part7-3.
Time for action – creating and playing a sound buffer queue
First, lets create a new object to hold sound buers:
1. In jni/Sound.hpp, create a new class Sound to manage a sound buer. It features
a method load() to load a PCM le and unload() to release it:
#ifndef _PACKT_SOUND_HPP_
#define _PACKT_SOUND_HPP_
class SoundService;
#include “Context.hpp”
#include “Resource.hpp”
#include “Types.hpp”
namespace packt {
class Sound {
public:
Sound(android_app* pApplication, const char* pPath);
const char* getPath();
status load();
status unload();
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
Playing Sound with OpenSL ES
[ 258 ]
private:
friend class SoundService;
private:
Resource mResource;
uint8_t* mBuffer; off_t mLength;
};
}
#endif
2. Sound loading implementaon is quite simple: it creates a buer with the same
size as the PCM le and loads all le content in it:
#include “Sound.hpp”
#include “Log.hpp”
#include <png.h>
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <SLES/OpenSLES_AndroidConfiguration.h>
namespace packt {
Sound::Sound(android_app* pApplication, const char* pPath) :
mResource(pApplication, pPath),
mBuffer(NULL), mLength(0)
{}
const char* Sound::getPath() {
return mResource.getPath();
}
status Sound::load() {
status lRes;
if (mResource.open() != STATUS_OK) {
return STATUS_KO;
}
mLength = mResource.getLength();
mBuffer = new uint8_t[mLength];
lRes = mResource.read(mBuffer, mLength);
mResource.close();
if (lRes != STATUS_OK) {
Log::error(“Error while reading PCM sound.”);
Chapter 7
[ 259 ]
return STATUS_KO;
} else {
return STATUS_OK;
}
}
status Sound::unload() {
delete[] mBuffer;
mBuffer = NULL; mLength = 0;
return STATUS_OK;
}
}
We can manage sound buers in the dedicated sound service.
3. Open SoudService.hpp and create a few new methods:
registerSound() to load and manage a new sound buer
playSound() to send a sound buer to the sound play queue
startSoundPlayer() to inialize the sound queue when
SoundService starts
A sound queue can be manipulated through SLPlayItf and SLBufferQueueItf
interfaces. Sound buers are stored in xed-size C++ array:
#ifndef _PACKT_SOUNDSERVICE_HPP_
#define _PACKT_SOUNDSERVICE_HPP_
#include “Sound.hpp”
#include “Types.hpp”
...
namespace packt {
class SoundService {
public:
...
Sound* registerSound(const char* pPath);
void playSound(Sound* pSound);
private:
status startSoundPlayer();
private:
Playing Sound with OpenSL ES
[ 260 ]
...
SLObjectItf mPlayerObj; SLPlayItf mPlayer;
SLBufferQueueItf mPlayerQueue;
Sound* mSounds[32]; int32_t mSoundCount;
};
}
#endif
4. Now, open jni/SoundService.cpp implementaon le. Update start()
to call startSoundPlayer() and load sound resources registered with
registerSound(). Also create a destructor to release these resources
when applicaon exits:
...
namespace packt {
SoundService::SoundService(android_app* pApplication) :
...,
mPlayerObj(NULL), mPlayer(NULL), mPlayerQueue(NULL),
mSounds(), mSoundCount(0)
{}
SoundService::~SoundService() {
for (int32_t i = 0; i < mSoundCount; ++i) {
delete mSounds[i];
mSoundCount = 0;
}
}
status SoundService::start() {
...
if (startSoundPlayer() != STATUS_OK) goto ERROR;
for (int32_t i = 0; i < mSoundCount; ++i) {
if (mSounds[i]->load() != STATUS_OK) goto ERROR;
}
return STATUS_OK;
ERROR:
packt::Log::error(“Error while starting SoundService”);
stop();
return STATUS_KO;
}
...
Chapter 7
[ 261 ]
Sound* SoundService::registerSound(const char* pPath) {
for (int32_t i = 0; i < mSoundCount; ++i) {
if (strcmp(pPath, mSounds[i]->getPath()) == 0) {
return mSounds[i];
}
}
Sound* lSound = new Sound(mApplication, pPath);
mSounds[mSoundCount++] = lSound;
return lSound;
}
...
5. Write startSoundPlayer(), beginning with the SLDataSource and
SLDataSink to describe the input and output channel. On the opposite to the BGM
player, the data format structure is not SLDataFormat_MIME (to open an MP3 le)
but a SLDataFormat_PCM with sampling, encoding, and endianness informaon.
Sounds need to be Mono (that is, only one sound channel for both le and right
speakers when available). The queue is created with the Android-specic extension
SLDataLocator_AndroidSimpleBufferQueue():
...
status SoundService::startSoundPlayer() {
SLresult lRes;
// Set-up sound audio source.
SLDataLocator_AndroidSimpleBufferQueue lDataLocatorIn;
lDataLocatorIn.locatorType =
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
// At most one buffer in the queue.
lDataLocatorIn.numBuffers = 1;
SLDataFormat_PCM lDataFormat;
lDataFormat.formatType = SL_DATAFORMAT_PCM;
lDataFormat.numChannels = 1; // Mono sound.
lDataFormat.samplesPerSec = SL_SAMPLINGRATE_44_1;
lDataFormat.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
lDataFormat.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
lDataFormat.channelMask = SL_SPEAKER_FRONT_CENTER;
lDataFormat.endianness = SL_BYTEORDER_LITTLEENDIAN;
SLDataSource lDataSource;
lDataSource.pLocator = &lDataLocatorIn;
lDataSource.pFormat = &lDataFormat;
Playing Sound with OpenSL ES
[ 262 ]
SLDataLocator_OutputMix lDataLocatorOut;
lDataLocatorOut.locatorType = SL_DATALOCATOR_OUTPUTMIX;
lDataLocatorOut.outputMix = mOutputMixObj;
SLDataSink lDataSink;
lDataSink.pLocator = &lDataLocatorOut;
lDataSink.pFormat = NULL;
...
6. Then, in startSoundPlayer(), create and realize the sound player. We are going
to need its SL_IID_PLAY and also SL_IID_BUFFERQUEUE interface now available
thanks to the data locator congured in previous step:
...
const SLuint32 lSoundPlayerIIDCount = 2;
const SLInterfaceID lSoundPlayerIIDs[] =
{ SL_IID_PLAY, SL_IID_BUFFERQUEUE };
const SLboolean lSoundPlayerReqs[] =
{ SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
lRes = (*mEngine)->CreateAudioPlayer(mEngine, &mPlayerObj,
&lDataSource, &lDataSink, lSoundPlayerIIDCount,
lSoundPlayerIIDs, lSoundPlayerReqs);
if (lRes != SL_RESULT_SUCCESS) goto ERROR;
lRes = (*mPlayerObj)->Realize(mPlayerObj, SL_BOOLEAN_FALSE);
if (lRes != SL_RESULT_SUCCESS) goto ERROR;
lRes = (*mPlayerObj)->GetInterface(mPlayerObj, SL_IID_PLAY,
&mPlayer);
if (lRes != SL_RESULT_SUCCESS) goto ERROR;
lRes = (*mPlayerObj)->GetInterface(mPlayerObj,
SL_IID_BUFFERQUEUE, &mPlayerQueue);
if (lRes != SL_RESULT_SUCCESS) goto ERROR;
...
7. To nish with startSoundPlayer(), start the queue by seng it in the playing
state. This does not actually mean that a sound is played. The queue is empty so that
would not be possible. But if a sound gets enqueued, then it is automacally played:
...
lRes = (*mPlayer)->SetPlayState(mPlayer,
SL_PLAYSTATE_PLAYING);
if (lRes != SL_RESULT_SUCCESS) goto ERROR;
Chapter 7
[ 263 ]
return STATUS_OK;
ERROR:
packt::Log::error(“Error while starting SoundPlayer”);
return STATUS_KO;
}
...
8. Update method stop() to destroy the sound player and free sound buers:
...
void SoundService::stop() {
stopBGM();
if (mOutputMixObj != NULL) {
(*mOutputMixObj)->Destroy(mOutputMixObj);
mOutputMixObj = NULL;
}
if (mEngineObj != NULL) {
(*mEngineObj)->Destroy(mEngineObj);
mEngineObj = NULL; mEngine = NULL;
}
if (mPlayerObj != NULL) {
(*mPlayerObj)->Destroy(mPlayerObj);
mPlayerObj = NULL; mPlayer = NULL; mPlayerQueue = NULL;
}
for (int32_t i = 0; i < mSoundCount; ++i) {
mSounds[i]->unload();
}
}
...
9. Terminate SoundService by wring playSound(), which rst stops any sound
being played and then enqueue the new sound buer to play:
...
void SoundService::playSound(Sound* pSound) {
SLresult lRes;
SLuint32 lPlayerState;
(*mPlayerObj)->GetState(mPlayerObj, &lPlayerState);
if (lPlayerState == SL_OBJECT_STATE_REALIZED) {
int16_t* lBuffer = (int16_t*) pSound->mBuffer;
off_t lLength = pSound->mLength;
Playing Sound with OpenSL ES
[ 264 ]
// Removes any sound from the queue.
lRes = (*mPlayerQueue)->Clear(mPlayerQueue);
if (lRes != SL_RESULT_SUCCESS) goto ERROR;
// Plays the new sound.
lRes = (*mPlayerQueue)->Enqueue(mPlayerQueue, lBuffer,
lLength);
if (lRes != SL_RESULT_SUCCESS) goto ERROR;
}
return;
ERROR:
packt::Log::error(“Error trying to play sound”);
}
}
Lets play a sound le when the game starts:
10. Store a reference to sound buer in le jni/DroidBlaster.hpp:
#ifndef _PACKT_DROIDBLASTER_HPP_
#define _PACKT_DROIDBLASTER_HPP_
#include “ActivityHandler.hpp”
#include “Background.hpp”
#include “Context.hpp”
#include “GraphicsService.hpp”
#include “Ship.hpp”
#include “Sound.hpp”
#include “SoundService.hpp”
#include “TimeService.hpp”
#include “Types.hpp”
namespace dbs {
class DroidBlaster : public packt::ActivityHandler {
...
private:
...
Background mBackground;
Ship mShip;
packt::Sound* mStartSound;
};
}
#endif
Chapter 7
[ 265 ]
11. Finally, play the sound in jni/DroidBlaster.cpp when the applicaon
is acvated:
#include “DroidBlaster.hpp”
#include “Log.hpp”
namespace dbs {
DroidBlaster::DroidBlaster(packt::Context* pContext) :
mGraphicsService(pContext->mGraphicsService),
mSoundService(pContext->mSoundService),
mTimeService(pContext->mTimeService),
mBackground(pContext), mShip(pContext),
mStartSound(mSoundService->registerSound(“start.pcm”))
{}
packt::status DroidBlaster::onActivate() {
...
mSoundService->playBGM(“bgm.mp3”);
mSoundService->playSound(mStartSound);
mBackground.spawn();
mShip.spawn();
...
}
}
What just happened?
We have discovered how to preload sounds in a buer and play them as needed. What
dierenates the sound playing technique from the BGM one showed earlier is the use of
a buer queue. A buer queue is exactly what its name reveals: a FIFO (First In, First Out)
collecon of sound buers played one aer the other. Buers are enqueued for playback
when all previous buers are played.
Buers can be recycled. This technique is essenal in combinaon with streaming les: two
or more buers are lled and sent to the queue. When rst buer has nished playing, the
second one starts while the rst buer is lled with new data. As soon as possible, the rst
buer is enqueued before the queue gets empty. This process repeats forever unl playback
is over. In addion, buers are raw data and can thus be processed or ltered on the y.
Playing Sound with OpenSL ES
[ 266 ]
In the present tutorial, because DroidBlaster does not need to play more than one sound
at once and no form of streaming is necessary, the buer queue size is simply set to one
buer (step 5, lDataLocatorIn.numBuffers = 1;). In addion, we want new sounds
to pre-empt older ones, which explains why queue is systemacally cleared. Your OpenSL
ES architecture should be of course adapted to your needs. If it becomes necessary to play
several sounds simultaneously, then several audio players (and therefore buer queues)
should be created.
Sound buers are stored in the PCM format, which does not self-describe its internal format.
Sampling, encoding, and other format informaon needs to be selected in the applicaon
code. Although this is ne for most of them, a soluon, if that is not exible enough, can be
to load a Wave le which contains all the necessary header informaon.
If you have read carefully the second part of this chapter about playing BGM, you will
remember that we have used a MIME data source to load dierent kind of sound les,
Waves included. So why not use a MIME source instead of a PCM source? Well, this is
because a buer queue works only with PCM data. Although improvements can be expected
in the future, audio le decoding sll need to be performed by hand. Trying to connect a
MIME source to a buer queue (like we are going to do with the recorder) will cause an
SL_RESULT_FEATURE_UNSUPPORTED error.
OpenSL ES has been updated in NDK R7 and now allows decoding
compressed les such as MP3 les to PCM buers.
Exporng PCM sounds with Audacity
A great open source tool to lter and sequence sounds is Audacity. It
allows altering sampling rate and modifying channels (Mono/Stereo).
Audacity is able to export as well as import sound as raw PCM data.
Event callback
It is possible to detect when a sound has nished playing using callbacks. A callback can be
set up by calling the RegisterCallback() method on a queue (but other type of objects
can also register callbacks) like in the following example:
...
namespace packt {
class SoundService {
...
Chapter 7
[ 267 ]
private:
static void callback_sound(SLBufferQueueItf pObject,
void* pContext);
...
};
}
#endif
For example, the callback can receive this, that is, a SoundService self reference, to allow
processing with any contextual informaon, if needed. Although this is facultave, an event
mask is set up to ensure callback is called only when event SL_PLAYEVENT_HEADATEND
(player has nished playing the buer) is triggered. A few others play events are available
in OpenSLES.h:
...
namespace packt {
...
status SoundService::startSoundPlayer() {
...
// Registers a callback called when sound is finished.
lResult = (*mPlayerQueue)->RegisterCallback(mPlayerQueue,
callback_sound, this);
slCheckErrorWithStatus(lResult, “Problem registering player
callback (Error %d).”, lResult);
lResult = (*mPlayer)->SetCallbackEventsMask(mPlayer, SL_
PLAYEVENT_HEADATEND);
slCheckErrorWithStatus(lResult, “Problem registering player
callback mask (Error %d).”, lResult);
// Starts the sound player
...
}
void callback_sound(SLBufferQueueItf pBufferQueue, void *context)
{
// Context can be casted back to the original type.
SoundService& lService = *(SoundService*) context;
...
Log::info(“Ended playing sound.”);
}
...
}
Playing Sound with OpenSL ES
[ 268 ]
Now, when a buer ends playing, a message is logged. Operaons like, for example,
enqueuing a new buer (to handle streaming for example) can be performed.
Callback and threading
Callbacks are like system interrupons or applicaon events:
their processing must be short and fast. If advanced processing
is necessary, it should not be performed inside the callback but
on another thread, nave threads being perfect candidates.
Indeed, callbacks are emied on a system thread, dierent than the one requesng OpenSL
ES services (that is, the nave thread in our case). Of course, with threads rise the problem
of thread-safety when accessing your own variables from the callback. Although protecng
code with mutexes is tempng, they are not always compable with real-me audio as their
eect on scheduling (inversion of priority issues) can disturb playback. Prefer using thread
safe technique like a lock-free queue to communicate with callbacks.
Recording sounds
Android devices are all about interacons. And interacons can come not only from touches
and sensors, but also from audio input. Most Android devices provide a micro to record
sound and allow an applicaon such as the Android desktop search to oer vocal features
to record queries.
If sound input is available, OpenSL ES gives access to the sound recorder navely. It
collaborates with a buer queue to take data from the input device and ll an output sound
buer from it. Setup is prey similar to what has been done with the AudioPlayer except
that data source and data sink are permuted.
To discover how this works, next the challenge consists in recording a sound when an
applicaon starts and playing it when it has nished recording.
Project DroidBlaster_Part7-3 can be used as a starng point for
this part. The resulng project is provided with this book under the
name DroidBlaster_Part7-Recorder.
Chapter 7
[ 269 ]
Have a go hero – recording and playing a sound
Turning SoundService into a recorder can be done in four steps:
1. Using status startSoundRecorder() inialize the sound recorder. Invoke it
right aer startSoundPlayer().
2. With void recordSound() start recording a sound buer with device micro.
Invoke this method at instances such as when the applicaon is acvated in
onActivate() aer background music playback starts.
3. A new callback static void callback_recorder(SLAndroidSimpleBuffe
rQueueItf, void*) to be noed of record queue events. You have to register
this callback so that it is triggered when a recorder event happens. Here, we are
interested in buer full events, that is, when the sound recording is nished.
4. void playRecordedSound() to play a sound once recorded. Play it at instances
such as when sound has nished being recorded in callback_recorder().
This is not technically correct because of potenal race condions but is ne for
an illustraon.
Before going any further, recording requires a specic authorizaon and, of course,
an appropriate Android device (you would not like an applicaon to record your
secret conversaons behind your back!). This authorizaon has to be requested in
Android manifest:
<?xml version=”1.0” encoding=”utf-8”?>
<manifest xmlns:android=”http://schemas.android.com/apk/res/android”
package=”com.packtpub.droidblaster” android:versionCode=”1”
android:versionName=”1.0”>
...
<uses-permission android:name=”android.permission.RECORD_AUDIO”/>
</manifest>
Sounds are recorded with a recorder object created from the OpenSL ES engine, as usual.
The recorder oers two interesng interfaces:
SLRecordItf: This interface is to start and stop recording. The idener is
SL_IID_RECORD.
SLAndroidSImpleBufferQueueItf: This manages a sound queue for the
recorder. This is an Android extension provided by NDK because current OpenSL
ES 1.0.1 specicaon does not support recording to a queue. The idener is
SL_IID_ANDROIDSIMPLEBUFFERQUEUE.
const SLuint32 lSoundRecorderIIDCount = 2;
const SLInterfaceID lSoundRecorderIIDs[] =
{ SL_IID_RECORD, SL_IID_ANDROIDSIMPLEBUFFERQUEUE };
Playing Sound with OpenSL ES
[ 270 ]
const SLboolean lSoundRecorderReqs[] =
{ SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
SLObjectItf mRecorderObj;
(*mEngine)->CreateAudioRecorder(mEngine, &mRecorderObj,
&lDataSource, &lDataSink,
lSoundRecorderIIDCount, lSoundRecorderIIDs,
lSoundRecorderReqs);
To create the recorder, you will need to declare your audio source and sink similar to the
following one. The data source is not a sound but a default recorder device (like a microphone).
On the other hand, the data sink (that is, the output channel) is not a speaker but a sound
buer in PCM format (with the requested sampling, encoding, and endianness). The Android
extension SLDataLocator_AndroidSimpleBufferQueue must be used to work with
a recorder since standard OpenSL buer queues cannot be used as with a recorder:
SLDataLocator_AndroidSimpleBufferQueue lDataLocatorOut;
lDataLocatorOut.locatorType =
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
lDataLocatorOut.numBuffers = 1;
SLDataFormat_PCM lDataFormat;
lDataFormat.formatType = SL_DATAFORMAT_PCM;
lDataFormat.numChannels = 1;
lDataFormat.samplesPerSec = SL_SAMPLINGRATE_44_1;
lDataFormat.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
lDataFormat.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
lDataFormat.channelMask = SL_SPEAKER_FRONT_CENTER;
lDataFormat.endianness = SL_BYTEORDER_LITTLEENDIAN;
SLDataSink lDataSink;
lDataSink.pLocator = &lDataLocatorOut;
lDataSink.pFormat = &lDataFormat;
SLDataLocator_IODevice lDataLocatorIn;
lDataLocatorIn.locatorType = SL_DATALOCATOR_IODEVICE;
lDataLocatorIn.deviceType = SL_IODEVICE_AUDIOINPUT;
lDataLocatorIn.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
lDataLocatorIn.device = NULL;
SLDataSource lDataSource;
lDataSource.pLocator = &lDataLocatorIn;
lDataSource.pFormat = NULL;
Chapter 7
[ 271 ]
To record a sound, you also need to create a sound buer with an appropriate size according
to the duraon of your recording. Size depends on the sampling rate. For example, for a
record of 2 s with a sampling rate of 44100 Hz and 16-bit quality, sound buer size would
look like the following:
mRecordSize = 44100 * 2
mRecordBuffer = new int16_t[mRecordSize];
In recordSound(), you can stop the recorder thanks to SLRecordItf to ensure it is not
already recording and clear the queue. The same process applies to destroy the recorder
when applicaon exits.
(*mRecorder)->SetRecordState(mRecorder, SL_RECORDSTATE_STOPPED);
(*mRecorderQueue)->Clear(mRecorderQueue);
Then you can enqueue a new buer and start recording:
(*mRecorderQueue)->Enqueue(mRecorderQueue, mRecordBuffer,
mRecordSize * sizeof(int16_t));
(*mRecorder)->SetRecordState(mRecorder,SL_RECORDSTATE_RECORDING);
Of course, it would be perfectly possible to just enqueue a new sound so that any current
recording is processed to its end (for example, to create a connuous chain of recording).
The sound being enqueued would be processed potenally later in that case. All depends
on your needs.
You eventually need to know when your sound buer has nished recording. To do so,
register a callback triggered when a recorder event happens (for example, a buer has been
lled). An event mask should be set to ensure callback is called only when a buer has been
lled (SL_RECORDEVENT_BUFFER_FULL). A few others are available in OpenSLES.h but
not all are supported (SL_RECORDEVENT_HEADATLIMIT, and so on):
(*mRecorderQueue)->RegisterCallback(mRecorderQueue,
callback_recorder, this);
(*mRecorder)->SetCallbackEventMask(mRecorder,
SL_RECORDEVENT_BUFFER_FULL);
Finally, when callback_recorder() is triggered, just stop recording and play the recorded
buer with playRecordedSound(). The recorded buer needs to be enqueued in the
audio players queue for playback:
(*mPlayerQueue)->Enqueue(mPlayerQueue, mRecordBuffer,
mRecordSize * sizeof(int16_t));
Playing Sound with OpenSL ES
[ 272 ]
Playing recorded sound directly from a callback is nice to perform quick
and simple tests. But to implement such a mechanism properly, more
advanced thread-safe technique (preferably lock-free) is required.
Indeed, in this example, there is a risk of race condion with SoundService destructor
(which destroys the queue used in the callback).
Summary
In this chapter, we saw how to create and realize an OpenSL ES engine connected to
an output channel. We played music from an encoded le and saw that an encoded le
cannot be loaded in a buer.
We also played sound buers in a sound queue. Buers can either be appended to a queue,
in which case they are played with delay, or inserted in replacement of previous sounds,
in which case they are played immediately. Finally, we have recorded sound in buers and
played them back.
Should you prefer OpenSL ES over Java APIs? There is no denite answer. Devices evolve
at much quieter pace than Android itself. So if your applicaon aims at a large compability,
that is, Android 2.2 or less, Java APIs are the only soluon. On the other hand, if it is planned
for later releases, then OpenSL ES is an opon to consider, praying that most devices will
be migrated to Gingerbread! But you have to be ready to support the cost of possible
future evoluons.
If all you need is a nice high-level API, then Java APIs may suit your requirements beer. If
you need ner playback or recording control, then there is no signicant dierence between
low-level Java APIs and OpenSL ES. In that case, choice should be architectural: if your code
is mainly Java, then you should probably go with Java and reciprocally. If you need to reuse
an exisng sound-related library, opmize performance or perform intense computaons,
like sound ltering on the y, then OpenSL ES is probably the right choice. There is no
garbage collector overhead and aggressive opmizaon is favored in the nave code.
Whatever choice you make, know that Android NDK has a lot more to oer. Aer rendering
graphics with Open GL ES and playing sound with OpenSL ES, the next chapter will take care
of handling input navely: keyboard, touches, and sensors.
8
Handling Input Devices and Sensors
Android is all about interacon. Admiedly, that means feedback, through
graphics, audio, vibraons, and so on. But there is no interacon without input!
The success of today's smart-phones takes its root in their mulple and modern
input possibilies: touch screens, keyboard, mouse, GPS, accelerometer, light
detector, sound recorder, and so on. Handling and combining them properly is a
key to enrich your applicaon and and to make it successful.
Although Android handles many input peripherals, the Android NDK has long been very
limited in their support, not to say good for nothing, unl the release of R5! We can now
access them directly through a nave API. Examples of available devices are:
Keyboard, either physical (with a slide-out keyboard) or virtual (which appears
on screen)
Direconal pad (up, down, le, right, and acon buons), oen
abbreviated D-Pad
Trackball (opcal ones included)
Touch screen, which has made the success of modern smart-phones
Mouse or Track Pad (since NDK R5, but available on Honeycomb devices only)
We can also access hardware sensors, for example:
Accelerometer, which measures linear acceleraon applied to a device.
Gyroscope, which measures angular velocity. It is oen combined with the
magnetometer to compute orientaon accurately and quickly. Gyroscope has
been introduced recently and is not available on most devices yet.
Handling Input Devices and Sensors
[ 274 ]
Magnetometer, which gives the ambient magnec eld and thus (if not perturbed)
cardinal direcon.
Light sensor, for example, to automacally adapt screen luminosity.
Proximity sensor, for example, to detect ear distance during a call.
In addion to hardware sensors, "soware sensors" have been introduced with Gingerbread.
These sensors are derived from hardware sensor's data:
Gravity sensor, to measure the gravity direcon and magnitude
Linear acceleraon sensor, which measures device "movement" excluding gravity
Rotaon vector, which indicates device orientaon in space
Gravity sensor and linear acceleraon sensor are derived from the accelerometer. On the
other hand, rotaon vector is derived from the magnetometer and the accelerometer.
Because these sensors are generally computed over me, they usually incur a slight delay
to get up-to-date values.
To familiarize more deeply with input devices and sensors, this chapter teaches how to:
Handle screen touches
Detect keyboard, D-Pad, and trackball events
Turn the accelerometer sensor into a joypad
Interacting with Android
The most emblemac innovaon of today's smart phones is the touch screen, which has
replaced the now anque mice. A touch screen detects, as its name suggests, touches
made with ngers or styluses. Depending on the quality of the screen, several touches
(also referred as cursors in Android) can be handled, de-mulplying interacon possibilies.
So let's start this chapter by handling touch events in DroidBlaster. To keep the example
simple, we will only handle one touch. The goal is to move the ship in the direcon of a
touch. The farther the touch is, the faster goes the ship. Beyond a pre-dened range, ship
speed reaches a top limit.
Chapter 8
[ 275 ]
The nal project structure will look as shown in the following diagram:
DroidBlaster
Ship
LogContext
TimeService
GraphicsService
EventLoop
GraphicsTexture Resource
packt
ActivityHandler
*
Background
GraphicsTileMap
GraphicsSprite Location
*
*
dbsdbs
RapidXml
SoundService
InputService
*Sound
InputHandler
Project DroidBlaster_Part7-3 can be used as a starng
point for this part. The resulng project is provided with this
book under the name DroidBlaster_Part8-1.
Handling Input Devices and Sensors
[ 276 ]
Time for action – handling touch events
Let's begin with the plumber to connect Android input event queue to our applicaon.
1. In the same way we created an ActivityHandler to process applicaon events
in Chapter 5, Wring a Nave Applicaon, create a class InputHandler, in a new
le jni/InputHandler.hpp to process the input events. Input API is declared in
android/input.h.
2. Create a onTouchEvent() to handle touch events. These events are packaged in
an AInputEvent structure dened in Android include les. Other input peripherals
will be added later in this chapter:
#ifndef _PACKT_INPUTHANDLER_HPP_
#define _PACKT_INPUTHANDLER_HPP_
#include <android/input.h>
namespace packt {
class InputHandler {
public:
virtual ~InputHandler() {};
virtual bool onTouchEvent(AInputEvent* pEvent) = 0;
};
}
#endif
3. Modify jni/EventLoop.hpp header le to include and handle an
InputHandler instance. Like with acvity event, dene an internal method
processInputEvent() triggering a stac callback callback_input():
#ifndef _PACKT_EVENTLOOP_HPP_
#define _PACKT_EVENTLOOP_HPP_
#include "ActivityHandler.hpp"
#include "InputHandler.hpp"
#include "Types.hpp"
#include <android_native_app_glue.h>
namespace packt {
class EventLoop {
public:
EventLoop(android_app* pApplication);
Chapter 8
[ 277 ]
void run(ActivityHandler* pActivityHandler,
InputHandler* pInputHandler);
protected:
...
void processAppEvent(int32_t pCommand);
int32_t processInputEvent(AInputEvent* pEvent);
void processSensorEvent();
private:
...
static void callback_event(android_app* pApplication,
int32_t pCommand);
static int32_t callback_input(android_app* pApplication,
AInputEvent* pEvent);
private:
...
android_app* mApplication;
ActivityHandler* mActivityHandler;
InputHandler* mInputHandler;
};
}
#endif
4. We need to process input events in jni/EventLoop.cpp source le and nofy
the associated InputHandler.
First, connect the Android input queue to our callback_input(). The
EventLoop itself (that is, this) is passed anonymously through the userData
member of the android_app structure. That way, callback is able to delegate
input processing back to our own object, that is, to processInputEvent().
Touch screen events are of the type MotionEvent (as opposed to key events). They
can be discriminated according to their source (AINPUT_SOURCE_TOUCHSCREEN)
thanks to Android nave input API (here, AInputEvent_getSource()):
Note how callback_input() and by extension processInputEvent()
return an integer value (which is in fact a Boolean). This value indicates that
an input event (for example, a pressed buon) has been processed by the
applicaon and does not need to be processed further by the system. For
example, return 1 when the back buon is pressed to stop event processing and
prevent acvity from geng terminated.
Handling Input Devices and Sensors
[ 278 ]
#include "EventLoop.hpp"
#include "Log.hpp"
namespace packt {
EventLoop::EventLoop(android_app* pApplication) :
mEnabled(false), mQuit(false),
mApplication(pApplication),
mActivityHandler(NULL), mInputHandler(NULL) {
mApplication->userData = this;
mApplication->onAppCmd = callback_event;
mApplication->onInputEvent = callback_input;
}
void EventLoop::run(ActivityHandler* pActivityHandler,
InputHandler* pInputHandler) {
int32_t lResult;
int32_t lEvents;
android_poll_source* lSource;
// Makes sure native glue is not stripped by the linker.
app_dummy();
mActivityHandler = pActivityHandler;
mInputHandler = pInputHandler;
packt::Log::info("Starting event loop");
while (true) {
// Event processing loop.
...
}
...
int32_t EventLoop::processInputEvent(AInputEvent* pEvent) {
int32_t lEventType = AInputEvent_getType(pEvent);
switch (lEventType) {
case AINPUT_EVENT_TYPE_MOTION:
switch (AInputEvent_getSource(pEvent)) {
case AINPUT_SOURCE_TOUCHSCREEN:
return mInputHandler->onTouchEvent(pEvent);
break;
}
break;
}
Chapter 8
[ 279 ]
return 0;
}
int32_t EventLoop::callback_input(android_app* pApplication,
AInputEvent* pEvent) {
EventLoop& lEventLoop = *(EventLoop*) pApplication->userData;
return lEventLoop.processInputEvent(pEvent);
}
}
Plumber is ready. Let's handle these events concretely.
5. To analyze touch events, create a InputService class in jni/InputService.
hpp implemenng our InputHandler. It contains a start() method to realize
necessary inializaons and implements onTouchEvent().
More interesngly, InputService provides getHorizontal() and
getVertical() methods, which indicate the virtual joypad direcon. Direcon
is dened between the touch point and a reference point (which will be the ship).
We also need to know window height and width (reference values, which come
from GraphicsService) to handle coordinate conversions:
#ifndef _PACKT_INPUTSERVICE_HPP_
#define _PACKT_INPUTSERVICE_HPP_
#include "Context.hpp"
#include "InputHandler.hpp"
#include "Types.hpp"
#include <android_native_app_glue.h>
namespace packt {
class InputService : public InputHandler {
public:
InputService(android_app* pApplication,
const int32_t& pWidth, const int32_t& pHeight);
float getHorizontal();
float getVertical();
void setRefPoint(Location* pTouchReference);
status start();
public:
Handling Input Devices and Sensors
[ 280 ]
bool onTouchEvent(AInputEvent* pEvent);
private:
android_app* mApplication;
float mHorizontal, mVertical;
Location* mRefPoint;
const int32_t& mWidth, &mHeight;
};
}
#endif
6. Now, the interesng part: jni/InputService.cpp. First, dene the constructor,
destructor, geers, and seers.
Input service needs a start() method to clear state members:
#include "InputService.hpp"
#include "Log.hpp"
#include <android_native_app_glue.h>
#include <cmath>
namespace packt {
InputService::InputService(android_app* pApplication,
const int32_t& pWidth, const int32_t& pHeight) :
mApplication(pApplication),
mHorizontal(0.0f), mVertical(0.0f),
mRefPoint(NULL), mWidth(pWidth), mHeight(pHeight)
{}
float InputService::getHorizontal() {
return mHorizontal;
}
float InputService::getVertical() {
return mVertical;
}
void InputService::setRefPoint(Location* pTouchReference) {
mRefPoint = pTouchReference;
}
status InputService::start() {
Chapter 8
[ 281 ]
mHorizontal = 0.0f, mVertical = 0.0f;
if ((mWidth == 0) || (mHeight == 0)) {
return STATUS_KO;
}
return STATUS_OK;
}
The eecve event processing comes in onTouchEvent(). Horizontal and vercal
direcons are computed according to the distance between the reference point and
the touch point. This distance is restricted by TOUCH_MAX_RANGE to an arbitrary
range of 65 pixels. Thus, ship max speed is reached when reference-to-touch point
distance is beyond TOUCH_MAX_RANGE pixels. Touch coordinates are retrieved
thanks to AMotionEvent_getX() and AMotionEvent_getY() when nger
moves. Direcon vector is reset to 0 when no more touch is detected:
Beware that the way touch events are red is not homogeneous among
devices. For example, some devices emit events connuously while
nger is down whereas others only emit them when nger moves. In
our case, we could re-compute movement each frame instead of when
an event is triggered to get a more predictable behavior.
...
bool InputService::onTouchEvent(AInputEvent* pEvent) {
const float TOUCH_MAX_RANGE = 65.0f; // In pixels.
if (mRefPoint != NULL) {
if (AMotionEvent_getAction(pEvent)
== AMOTION_EVENT_ACTION_MOVE) {
// Needs a conversion to proper coordinates
// (origin at bottom/left). Only lMoveY needs it.
float lMoveX = AMotionEvent_getX(pEvent, 0)
- mRefPoint->mPosX;
float lMoveY = mHeight - AMotionEvent_getY(pEvent, 0)
- mRefPoint->mPosY;
float lMoveRange = sqrt((lMoveX * lMoveX)
+ (lMoveY * lMoveY));
if (lMoveRange > TOUCH_MAX_RANGE) {
float lCropFactor = TOUCH_MAX_RANGE / lMoveRange;
lMoveX *= lCropFactor; lMoveY *= lCropFactor;
}
mHorizontal = lMoveX / TOUCH_MAX_RANGE;
Handling Input Devices and Sensors
[ 282 ]
mVertical = lMoveY / TOUCH_MAX_RANGE;
} else {
mHorizontal = 0.0f; mVertical = 0.0f;
}
}
return true;
}
}
7. Insert InputService into the Context structure in jni/Context.hpp.
#ifndef _PACKT_CONTEXT_HPP_
#define _PACKT_CONTEXT_HPP_
#include "Types.hpp"
namespace packt {
class GraphicsService;
class InputService;
class SoundService;
class TimeService;
struct Context {
GraphicsService* mGraphicsService;
InputService* mInputService;
SoundService* mSoundService;
TimeService* mTimeService;
};
}
#endif
Finally, let's react to touch events in the game itself.
8. Get the InputService back in jni/DroidBlaster.hpp:
#ifndef _PACKT_DROIDBLASTER_HPP_
#define _PACKT_DROIDBLASTER_HPP_
#include "ActivityHandler.hpp"
#include "Background.hpp"
#include "Context.hpp"
#include "InputService.hpp"
#include "GraphicsService.hpp"
#include "Ship.hpp"
...
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
Chapter 8
[ 283 ]
namespace dbs {
class DroidBlaster : public packt::ActivityHandler {
public:
...
private:
packt::InputService* mInputService;
packt::GraphicsService* mGraphicsService;
packt::SoundService* mSoundService;
packt::TimeService* mTimeService;
...
};
}
#endif
9. InputService is started in jni/DroidBlaster.cpp when the acvity is
acvated. Because it calls ANativeWindow_lock() to retrieve window height
and width, InputService needs to be started before GraphicsService to
avoid a deadlock:
#include "DroidBlaster.hpp"
#include "Log.hpp"
namespace dbs {
DroidBlaster::DroidBlaster(packt::Context* pContext) :
mInputService(pContext->mInputService),
mGraphicsService(pContext->mGraphicsService),
mSoundService(pContext->mSoundService),
...
{}
packt::status DroidBlaster::onActivate() {
if (mGraphicsService->start() != packt::STATUS_OK) {
return packt::STATUS_KO;
}
if (mInputService->start() != packt::STATUS_OK) {
return packt::STATUS_KO;
}
...
}
...
packt::status DroidBlaster::onStep() {
Handling Input Devices and Sensors
[ 284 ]
mTimeService->update();
mBackground.update();
mShip.update();
// Updates services.
if (mGraphicsService->update() != packt::STATUS_OK) {
...
}
}
10. The InputService is used by the Ship class to reposion. Open jni/Ship.hpp
and associate it with the InputService and TimeService. The ship posion is
moved according to user input and me step in a new method update():
#ifndef _DBS_SHIP_HPP_
#define _DBS_SHIP_HPP_
#include "Context.hpp"
#include "InputService.hpp"
#include "GraphicsService.hpp"
#include "GraphicsSprite.hpp"
#include "Types.hpp"
namespace dbs {
class Ship {
public:
Ship(packt::Context* pContext);
void spawn();
void update();
private:
packt::InputService* mInputService;
packt::GraphicsService* mGraphicsService;
packt::TimeService* mTimeService;
packt::GraphicsSprite* mSprite;
packt::Location mLocation;
float mAnimSpeed;
};
}
#endif
Chapter 8
[ 285 ]
11. The reference point from which distance to the touch is computed is inialized with
the ship posion. During update, ship is moved toward the touch point according to
the me step and the direcon calculated in InputService class:
#include "Ship.hpp"
#include "Log.hpp"
namespace dbs {
Ship::Ship(packt::Context* pContext) :
mInputService(pContext->mInputService),
mGraphicsService(pContext->mGraphicsService),
mTimeService(pContext->mTimeService),
mLocation(), mAnimSpeed(8.0f) {
mSprite = pContext->mGraphicsService->registerSprite(
mGraphicsService->registerTexture("ship.png"), 64, 64,
&mLocation);
mInputService->setRefPoint(&mLocation);
}
...
void Ship::update() {
const float SPEED_PERSEC = 400.0f;
float lSpeed = SPEED_PERSEC * mTimeService->elapsed();
mLocation.translate(mInputService->getHorizontal() * lSpeed,
mInputService->getVertical() * lSpeed);
}
}
12. Finally, update the android_main() method in jni/Main.cpp to build
an instance of InputService and pass it to the event processing loop:
#include "Context.hpp"
#include "DroidBlaster.hpp"
#include "EventLoop.hpp"
#include "InputService.hpp"
#include "GraphicsService.hpp"
#include "SoundService.hpp"
#include "TimeService.hpp"
void android_main(android_app* pApplication) {
packt::TimeService lTimeService;
packt::GraphicsService lGraphicsService(pApplication,
&lTimeService);
Handling Input Devices and Sensors
[ 286 ]
packt::InputService lInputService(pApplication,
lGraphicsService.getWidth(),lGraphicsService.getHeight());
packt::SoundService lSoundService(pApplication);
packt::Context lContext = { &lInputService, &lGraphicsService,
&lSoundService, &lTimeService };
packt::EventLoop lEventLoop(pApplication);
dbs::DroidBlaster lDroidBlaster(&lContext);
lEventLoop.run(&lDroidBlaster, &lInputService);
}
What just happened?
We have created a simple example of an input system based on touch events. The ship
ies toward the touch point at a speed dependent on the touch distance. Yet, many
improvements are possible such as taking into account screen density and size, following
one specic pointer…
Touch screen event coordinates are absolute. Their origin is in the upper-le corner of the
screen (on the opposite of OpenGL which is on the lower-le corner). If screen rotaon is
authorized by an applicaon, the origin will stay on the upper, le whether the screen is in
portrait or landscape mode.
To implement it, we have connected our event loop to the input event queue provided
by the native_app_glue module. This queue is internally represented as an Unix pipe,
like the acvity event queue. Touch screen events are embedded in an AInputEvent
structure, which stores also other kind of input events. Input events can be handled with
the funcons declared in android/input.h. Input event types can be discriminated
using AInputEvent_getType() and AInputEvent_getSource() methods (note the
AInputEvent_ prex). Methods related to touch events are prexed by AMotionEvent_.
Chapter 8
[ 287 ]
The touch API is rather rich. Many details can be requested such as (non-exhausvely):
AMotionEvent_getAction() To detect whether a nger is entering in contact with
the screen, leaving it, or moving over the surface.
The result is an integer value composed of the event
type (on byte 1, for example, AMOTION_EVENT_
ACTION_DOWN) and a pointer index (on byte 2, to
know which nger the event refers to).
AMotionEvent_getX()
AMotionEvent_getY()
To retrieve touch coordinates on screen, expressed in
pixels as a oat (sub-pixel values are possible).
AMotionEvent_getDownTime()
AMotionEvent_getEventTime()
To retrieve how much me nger has been sliding
over the screen and when the event has been
generated in nanoseconds.
AMotionEvent_getPressure()
AMotionEvent_getSize()
To detect how careful users are with their device.
Values usually range between 0.0 and 1.0 (but may
exceed it). Size and pressure are generally closely
related. Behavior can vary greatly and be noisy
depending on hardware.
AMotionEvent_
getHistorySize()
AMotionEvent_
getHistoricalX()
AMotionEvent_
getHistoricalY()
...
Touch events of type AMOTION_EVENT_ACTION_
MOVE can be grouped together for eciency purpose.
These methods give access to these "historical points"
that occurred between previous and current events.
Have a look at android/input.h for an exhausve list of methods.
If you look more deeply at AMotionEvent API, you will noce that some events have a
second parameter pointer_index, which ranges between 0 and the number of acve
pointers. Indeed, most touch screens today are mul-touch! Two or more ngers on a screen
(if hardware supports it) are translated in Android by two or more pointers. To manipulate
them, look at:
AMotionEvent_
getPointerCount()
To know how many ngers touch the screen.
AMotionEvent_getPointerId() To get a pointer unique idener from a pointer
index. This is the only way to track a parcular
pointer (that is, nger) over me, as its index may
change when ngers touch or leave the screen.
Handling Input Devices and Sensors
[ 288 ]
Do not rely on hardware
If you followed the story of the (now prehistoric!) Nexus One, then you know
that it came out with an hardware defect. Pointers were oen geng mixed
up, two of them exchanging one of their coordinates. So be always prepared
to handle hardware specicies or hardware that behaves incorrectly!
Detecting keyboard, D-Pad, and Trackball events
The most common input device among all is the keyboard. This is true for Android too. An
Android keyboard can be physical: in the device front face (like tradional blackberries) or
on a slide-out screen. But a keyboard can also be virtual, that is, emulated on the screen at
the cost of a large poron of space taken. In addion to the keyboard itself, every Android
device should include a few physical buons (somemes emulated on screen) such as Menu,
Home, Search, and so on.
A much less common type of input device is the Direconal-Pad. A D-Pad is a set of physical
buons to move up, down, le, or right and a specic acon/conrmaon buon. Although
they oen disappear from recent phones and tablets, D-Pads remain one of the most
convenient ways to move across text or UI widgets. D-Pads are oen replaced by trackballs.
Trackballs behave similarly to a mouse (the one with a ball inside) that would be upside-down.
Some trackballs are analogical, but others (for example, opcal ones) behave as a D-Pad
(that is, all or nothing).
To see how they work, let's use these peripherals to move our space ship in DroidBlaster.
The Android NDK now allows handling all these input peripherals on the nave side. So let's
try them!
Chapter 8
[ 289 ]
Project DroidBlaster_Part8-1 can be used as a starng
point for this part. The resulng project is provided with this
book under the name DroidBlaster_Part8-2.
Time for action – handling keyboard, D-Pad, and
trackball, natively
First, let's handle keyboard and trackball events.
1. Open jni/InputHandler.hpp and add keyboard and trackball event handlers:
#ifndef _PACKT_INPUTHANDLER_HPP_
#define _PACKT_INPUTHANDLER_HPP_
#include <android/input.h>
namespace packt {
class InputHandler {
public:
virtual ~InputHandler() {};
virtual bool onTouchEvent(AInputEvent* pEvent) = 0;
virtual bool onKeyboardEvent(AInputEvent* pEvent) = 0;
virtual bool onTrackballEvent(AInputEvent* pEvent) = 0;
};
}
#endif
2. Update method processInputEvent() inside the exisng le jni/EventLoop.
cpp to redirect keyboard and trackball events to InputHandler.
Trackballs and touch events are assimilated to moon events and can be
discriminated according to their source. On the opposite side, key events are
discriminated according to their type. Indeed, there exist two dedicated APIs for
MotionEvents (the same for trackballs and touch events) and for KeyEvents
(idencal for keyboard, D-Pad, and so on):
#include "EventLoop.hpp"
#include "Log.hpp"
namespace packt {
...
int32_t EventLoop::processInputEvent(AInputEvent* pEvent) {
Handling Input Devices and Sensors
[ 290 ]
int32_t lEventType = AInputEvent_getType(pEvent);
switch (lEventType) {
case AINPUT_EVENT_TYPE_MOTION:
switch (AInputEvent_getSource(pEvent)) {
case AINPUT_SOURCE_TOUCHSCREEN:
return mInputHandler->onTouchEvent(pEvent);
break;
case AINPUT_SOURCE_TRACKBALL:
return mInputHandler->onTrackballEvent(pEvent);
break;
}
break;
case AINPUT_EVENT_TYPE_KEY:
return mInputHandler->onKeyboardEvent(pEvent);
break;
}
return 0;
}
...
}
3. Now, modify the jni/InputService.hpp le to override these new methods…
also dene an update() method to react to pressed keys. We are interested in
the menu buon that is going to cause the applicaon to exit:
#ifndef _PACKT_INPUTSERVICE_HPP_
#define _PACKT_INPUTSERVICE_HPP_
...
namespace packt {
class InputService : public InputHandler {
public:
...
status start();
status update();
public:
bool onTouchEvent(AInputEvent* pEvent);
bool onKeyboardEvent(AInputEvent* pEvent);
bool onTrackballEvent(AInputEvent* pEvent);
Chapter 8
[ 291 ]
private:
...
Location* mRefPoint;
int32_t mWidth, mHeight;
bool mMenuKey;
};
}
#endif
4. Now, update the class constructor jni/InputService.cpp and implement
method update() to exit when the menu buon is pressed:
#include "InputService.hpp"
#include "Log.hpp"
#include <android_native_app_glue.h>
#include <cmath>
namespace packt {
InputService::InputService(android_app* pApplication,
const int32_t& pWidth, const int32_t& pHeight) :
mApplication(pApplication),
mHorizontal(0.0f), mVertical(0.0f),
mRefPoint(NULL), mWidth(pWidth), mHeight(pHeight),
mMenuKey(false)
{}
...
status InputService::update() {
if (mMenuKey) {
return STATUS_EXIT;
}
return STATUS_OK;
}
...
5. Sll in InputService.cpp, process keyboard events in onKeyboardEvent(). Use:
AKeyEvent_getAction() to get event type (that is, pressed or not).
AKeyEvent_getKeyCode() to get the buon identy.
Handling Input Devices and Sensors
[ 292 ]
In the following code, when le, right, up, or down buons are pressed,
InputService compute corresponding direcon into elds mHorizontal
and mVertical dened in previous part. Movement starts when buon is
down and stops when it is up.
We also process the Menu buon here, when it gets unpressed:
This code works only on devices with a D-Pad, which is the
case of the emulator. Note however that due to Android
fragmentaon, reacon may dier according to hardware.
...
bool InputService::onKeyboardEvent(AInputEvent* pEvent) {
const float ORTHOGONAL_MOVE = 1.0f;
if(AKeyEvent_getAction(pEvent)== AKEY_EVENT_ACTION_DOWN) {
switch (AKeyEvent_getKeyCode(pEvent)) {
case AKEYCODE_DPAD_LEFT:
mHorizontal = -ORTHOGONAL_MOVE;
break;
case AKEYCODE_DPAD_RIGHT:
mHorizontal = ORTHOGONAL_MOVE;
break;
case AKEYCODE_DPAD_DOWN:
mVertical = -ORTHOGONAL_MOVE;
break;
case AKEYCODE_DPAD_UP:
mVertical = ORTHOGONAL_MOVE;
break;
case AKEYCODE_BACK:
return false;
}
} else {
switch (AKeyEvent_getKeyCode(pEvent)) {
case AKEYCODE_DPAD_LEFT:
case AKEYCODE_DPAD_RIGHT:
mHorizontal = 0.0f;
break;
case AKEYCODE_DPAD_DOWN:
case AKEYCODE_DPAD_UP:
mVertical = 0.0f;
break;
case AKEYCODE_MENU:
Chapter 8
[ 293 ]
mMenuKey = true;
break;
case AKEYCODE_BACK:
return false;
}
}
return true;
}
...
6. Similarly, process trackball events in a new method onTrackballEvent(). Retrieve
trackball magnitude with AMotionEvent_getX() and AMotionEvent_getY().
Because some trackballs do not oer a gradated magnitude, the movement is
quaned with plain constants. Possible noise is ignored with an arbitrary
trigger threshold:
When using trackball that way, the ship moves unl a "counter-movement"
(for example, requesng to go to the right when going le) or acon buon
is pressed (last else secon):
For a wide audience applicaon, code should be adapted
to handle hardware capabilies and specicies such as
gradated values of analogical trackballs.
...
bool InputService::onTrackballEvent(AInputEvent* pEvent) {
const float ORTHOGONAL_MOVE = 1.0f;
const float DIAGONAL_MOVE = 0.707f;
const float THRESHOLD = (1/100.0f);
if (AMotionEvent_getAction(pEvent)
== AMOTION_EVENT_ACTION_MOVE) {
float lDirectionX = AMotionEvent_getX(pEvent, 0);
float lDirectionY = AMotionEvent_getY(pEvent, 0);
float lHorizontal, lVertical;
if (lDirectionX < -THRESHOLD) {
if (lDirectionY < -THRESHOLD) {
lHorizontal = -DIAGONAL_MOVE;
lVertical = DIAGONAL_MOVE;
} else if (lDirectionY > THRESHOLD) {
lHorizontal = -DIAGONAL_MOVE;
lVertical = -DIAGONAL_MOVE;
Handling Input Devices and Sensors
[ 294 ]
} else {
lHorizontal = -ORTHOGONAL_MOVE;
lVertical = 0.0f;
}
} else if (lDirectionX > THRESHOLD) {
if (lDirectionY < -THRESHOLD) {
lHorizontal = DIAGONAL_MOVE;
lVertical = DIAGONAL_MOVE;
} else if (lDirectionY > THRESHOLD) {
lHorizontal = DIAGONAL_MOVE;
lVertical = -DIAGONAL_MOVE;
} else {
lHorizontal = ORTHOGONAL_MOVE;
lVertical = 0.0f;
}
} else if (lDirectionY < -THRESHOLD) {
lHorizontal = 0.0f;
lVertical = ORTHOGONAL_MOVE;
} else if (lDirectionY > THRESHOLD) {
lHorizontal = 0.0f;
lVertical = -ORTHOGONAL_MOVE;
}
// Ends movement if there is a counter movement.
if ((lHorizontal < 0.0f) && (mHorizontal > 0.0f)) {
mHorizontal = 0.0f;
} else if((lHorizontal > 0.0f)&&(mHorizontal < 0.0f)){
mHorizontal = 0.0f;
} else {
mHorizontal = lHorizontal;
}
if ((lVertical < 0.0f) && (mVertical > 0.0f)) {
mVertical = 0.0f;
} else if ((lVertical > 0.0f) && (mVertical < 0.0f)) {
mVertical = 0.0f;
} else {
mVertical = lVertical;
}
} else {
mHorizontal = 0.0f; mVertical = 0.0f;
}
return true;
}
}
Let's nish by making a slight modicaon to the game itself.
Chapter 8
[ 295 ]
7. Finally, edit DroidBlaster.cpp and update InputService at each iteraon:
#include "DroidBlaster.hpp"
#include "Log.hpp"
namespace dbs {
...
packt::status DroidBlaster::onStep() {
mTimeService->update();
mBackground.update();
mShip.update();
if (mInputService->update() != packt::STATUS_OK) {
return packt::STATUS_KO;
}
if (mGraphicsService->update() != packt::STATUS_OK) {
return packt::STATUS_KO;
}
return packt::STATUS_OK;
}
...
}
What just happened?
We have extended our input system to handle the keyboard, D-Pad, and trackball events.
D-Pad can be considered as a keyboard extension and is processed the same way. Indeed,
D-Pad and keyboard events are transported in the same structure (AInputEvent) and
handled by the same API (prexed with AKeyEvent). The following table lists the main key
event methods:
Handling Input Devices and Sensors
[ 296 ]
AKeyEvent_getAction() Indicate if buon is down (AKEY_EVENT_ACTION_
DOWN) or released (AKEY_EVENT_ACTION_UP). Note
that mulple key acons can be emied in batch (AKEY_
EVENT_ACTION_MULTIPLE).
AKeyEvent_getKeyCode() To retrieve the actual buon being pressed (dened in
android/keycodes.h), for example, AKEYCODE_
DPAD_LEFT for the le buon.
AKeyEvent_getFlags() Key events can be associated with one or more ags
that give various informaon on the event like AKEY_
EVENT_LONG_PRESS, AKEY_EVENT_FLAG_SOFT_
KEYBOARD for event originated from an emulated
keyboard.
AKeyEvent_getScanCode() Is similar to a key code except that this is the raw key ID,
dependent and dierent from device to device.
AKeyEvent_getMetaState() Meta states are ags that indicate if some modier keys
like Alt or Shi are pressed simultaneously (for example,
AMETA_SHIFT_ON, AMETA_NONE, and so on).
AKeyEvent_
getRepeatCount()
Indicates how many mes the buon event occurred,
usually when you leave buon down.
AKeyEvent_getDownTime() To know when a buon was pressed.
Although some of them (especially opcal ones) behave like a D-Pad, trackballs are not using
the same API. Actually, trackballs are handled through the AMotionEvent API (like touch
events). Of course, some informaon provided for touch events is not always available on
trackballs. The most important funcons to look at are:
AMotionEvent_getAction() To know if an event represents a move acon (as opposed
to a press acon).
AMotionEvent_getX()
AMotionEvent_getY()
To get trackball movement.
AKeyEvent_getDownTime() To know if trackball is pressed (like D-Pad acon buon).
Currently, most trackballs use an all-or-nothing pressure
to indicate the press event.
Something tricky with trackballs, that may not be obvious at rst, is that no event up is
generated to indicate that trackball has nished moving. Moreover, trackball events are
generated as a series (as a burst) which makes it harder to detect when movement is
nished. There is no easy way to handle this except using a manual mer and checking
regularly that no event has happened for a sucient amount of me.
Chapter 8
[ 297 ]
Again, do not rely on an expected behaviour
Never expect peripherals to behave exactly the same on all phones.
Trackballs are a very good example: they can either indicate a direcon like
an analogical pad or a straight direcon like a D-Pad (for example, opcal
trackballs). There is currently no way to dierenate device characteriscs
from the available APIs. The only soluons are to either calibrate device
and congure it at runme or save a kind of device database.
Have a go hero – displaying software keyboard
An annoying problem with the Android NDK and NativeActivity is that there is no easy
way to display a virtual keyboard. And of course, without a virtual keyboard, nothing can
be keyed in. This is where the JNI skills you have gained by reading Chapter 3 and Chapter 4
come to the rescue.
The piece of Java code to show or hide the keyboard is rather concise:
InputMethodManager mgr = (InputMethodManager)
myActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
mgr.showSoftInput(pActivity.getWindow().getDecorView(), 0);
...
mgr.hideSoftInput(pActivity.getWindow().getDecorView(), 0);
Write the equivalent JNI code in four steps:
1. First, create a JNI helper class which:
Takes an android_app instance and aaches the JavaVM during
construcon. The JavaVM is provided in member activity->vm
of android_app.
Detaches the JavaVM when class gets destroyed.
Oers helper methods to create and delete global references like
implemented in Chapter 4, Calling Back Java from Nave Code
(makeGlobalRef() and deleteGlobalRef()).
Provides geers to a JNIEnv cached on VM aachment and the
NativeActivity instance provided in member activity->clazz
of android_app.
Handling Input Devices and Sensors
[ 298 ]
2. Then, write a Keyboard class which receives a JNI instance in parameter and cache
all the necessary jclass, jmethodID, and jfieldID to execute the piece of Java
code presented above. This is similar to the StoreWatcher in Chapter 4, Calling
Back Java from Nave Code, but in C++ this me.
Dene methods to:
Cache JNI elements. Call it when InputService is inialized to handle
error cases properly and report a status.
Release global references when applicaon is deacvated.
Show and hide the keyboard by execung the JNI methods cached earlier.
3. Instanate both the JNI and the Keyboard classes in your android_main()
method and pass the laer to your InputService.
4. Open the virtual keyboard when the menu key is pressed instead of leaving the
game. Finally, detect keys that are pressed on the virtual keyboard. For example,
try to detect the key AKEYCODE_E to exit the game.
The nal project is provided with this book in
DroidBlaster_Part8-2-Keyboard.
Probing device sensors
Handling input devices is essenal to any applicaon, but probing sensors is important
for the smartest one! The most spread sensor among Android game applicaons is
the accelerometer.
An accelerometer, as its name suggests, measures the linear acceleraon applied to a
device. When moving a device up, down, le, or right, the accelerometer gets excited and
indicates an acceleraon vector in 3D space. Vector is expressed relave to screen default
orientaon. Coordinates system is relave to device natural orientaon:
X axis points le
Y points up
Z points from back to front
Axes become inverted if device is rotated (for example, Y points le if the device is rotated
90 degrees clockwise).
Chapter 8
[ 299 ]
A very interesng feature of accelerometers is that they undergo a constant acceleraon:
gravity, around 9.8m/s2 on earth. For example, when lying at on a table, acceleraon vector
indicates -9.8 on the Z-axis. When straight, it indicates the same value on Y axis. So assuming
device posion is xed, device orientaon on two axes in space can be deduced from the
gravity acceleraon vector. Magnetometer is sll required to get full device orientaon in
3D space.
Remember that accelerometers work with linear acceleraon.
They allow detecng translaon when device is not rotang and
paral orientaon when device is xed. But both movements
cannot be combined without a magnetometer and/or gyroscope.
The nal project structure will look as shown in the following diagram:
DroidBlaster
Ship
LogContext
TimeService
GraphicsService
EventLoop
GraphicsTexture Resource
packt
ActivityHandler
*
Background
GraphicsTileMap
GraphicsSprite Location
*
*
dbsdbs
RapidXml
SoundService
InputService
*Sound
InputHandler
Sensor
*
*
Handling Input Devices and Sensors
[ 300 ]
Project DroidBlaster_Part8-2 can be used as a
starng point for this part. The resulng project is provided
with this book under the name DroidBlaster_Part8-3.
Time for action – turning your device into a joypad
First, we need to handle sensor events in the event loop.
1. Open InputHandler.hpp and add a new method onAccelerometerEvent().
Include android/sensor.h ocial header for sensors.
#ifndef _PACKT_INPUTHANDLER_HPP_
#define _PACKT_INPUTHANDLER_HPP_
#include <android/input.h>
#include <android/sensor.h>
namespace packt {
class InputHandler {
public:
virtual ~InputHandler() {};
virtual bool onTouchEvent(AInputEvent* pEvent) = 0;
virtual bool onKeyboardEvent(AInputEvent* pEvent) = 0;
virtual bool onTrackballEvent(AInputEvent* pEvent) = 0;
virtual bool onAccelerometerEvent(ASensorEvent* pEvent) = 0;
};
}
#endif
2. Update jni/EventLoop.hpp class by adding a stac callback dedicated to
sensors named callback_sensor(). This method delegates processing to
member method processSensorEvent(), which redistributes events to
InputHandler instance.
A sensor event queue is represented by an ASensorManager opaque structure.
On the opposite of the acvity and input event queues, the sensor queue is not
managed by the native_app_glue module (as seen in Chapter 5, Wring a Fully
Nave Applicaon). We need to set it up ourselves with an ASensorEventQueue
and an android_poll_source:
#ifndef _PACKT_EVENTLOOP_HPP_
#define _PACKT_EVENTLOOP_HPP_
Chapter 8
[ 301 ]
...
namespace packt {
class EventLoop {
...
protected:
...
void processAppEvent(int32_t pCommand);
int32_t processInputEvent(AInputEvent* pEvent);
void processSensorEvent();
private:
friend class Sensor;
static void callback_event(android_app* pApplication,
int32_t pCommand);
static int32_t callback_input(android_app* pApplication,
AInputEvent* pEvent);
static void callback_sensor(android_app* pApplication,
android_poll_source* pSource);
private:
...
ActivityHandler* mActivityHandler;
InputHandler* mInputHandler;
ASensorManager* mSensorManager;
ASensorEventQueue* mSensorEventQueue;
android_poll_source mSensorPollSource;
};
}
#endif
3. Modify le jni/EventLoop.cpp, starng with its constructor:
#include "EventLoop.hpp"
#include "Log.hpp"
namespace packt {
EventLoop::EventLoop(android_app* pApplication) :
mEnabled(false), mQuit(false),
mApplication(pApplication),
mActivityHandler(NULL), mInputHandler(NULL),
Handling Input Devices and Sensors
[ 302 ]
mSensorPollSource(), mSensorManager(NULL),
mSensorEventQueue(NULL) {
mApplication->userData = this;
mApplication->onAppCmd = callback_event;
mApplication->onInputEvent = callback_input;
}
...
4. When starng an EventLoop in activate(), create a new sensor queue and
aach it with ASensorManager_createEventQueue() so that it gets polled
with the acvity and input event queues. LOOPER_ID_USER is a slot dened
inside native_app_glue to aach a custom queue to the internal glue Looper
(see Chapter 5, Wring a Fully Nave Applicaon. The glue Looper already has
two internal slots (LOOPER_ID_MAIN and LOOPER_ID_INPUT handled
transparently). Sensors are managed through a central manager ASensorManager
which can be retrieved using ASensorManager_getInstance().
In the deactivate() method, destroy the sensor event queue without mercy
with method ASensorManager_destroyEventQueue():
...
void EventLoop::activate() {
if ((!mEnabled) && (mApplication->window != NULL)) {
mSensorPollSource.id = LOOPER_ID_USER;
mSensorPollSource.app = mApplication;
mSensorPollSource.process = callback_sensor;
mSensorManager = ASensorManager_getInstance();
if (mSensorManager != NULL) {
mSensorEventQueue = ASensorManager_createEventQueue(
mSensorManager, mApplication->looper,
LOOPER_ID_USER, NULL, &mSensorPollSource);
if (mSensorEventQueue == NULL) goto ERROR;
}
mQuit = false; mEnabled = true;
if (mActivityHandler->onActivate() != STATUS_OK) {
goto ERROR;
}
}
return;
ERROR:
mQuit = true;
Chapter 8
[ 303 ]
deactivate();
ANativeActivity_finish(mApplication->activity);
}
void EventLoop::deactivate() {
if (mEnabled) {
mActivityHandler->onDeactivate();
mEnabled = false;
if (mSensorEventQueue != NULL) {
ASensorManager_destroyEventQueue(mSensorManager,
mSensorEventQueue);
mSensorEventQueue = NULL;
}
mSensorManager = NULL;
}
}
...
5. Finally, redirect sensor events to the handler in processSensorEvent().
Sensor events are wrapped in an ASensorEvent structure. This structure
contains a type eld to idenfy the sensor the event originates from
(here, to keep accelerometer events):
...
void EventLoop::processSensorEvent() {
ASensorEvent lEvent;
while (ASensorEventQueue_getEvents(mSensorEventQueue,
&lEvent, 1) > 0) {
switch (lEvent.type) {
case ASENSOR_TYPE_ACCELEROMETER:
mInputHandler->onAccelerometerEvent(&lEvent);
break;
}
}
}
void EventLoop::callback_sensor(android_app* pApplication,
android_poll_source* pSource) {
EventLoop& lEventLoop = *(EventLoop*) pApplication->userData;
lEventLoop.processSensorEvent();
}
}
Handling Input Devices and Sensors
[ 304 ]
6. Create a new le jni/Sensor.hpp as follows. The Sensor class is responsible for
the acvaon (with enable()) and deacvaon (with disable()) of the sensor.
Method toggle() is a wrapper to switch the sensor state.
This class works closely with EventLoop to process sensor messages (actually,
this code could have been integrated in EventLoop itself). Sensors themselves
are wrapped in an ASensor opaque structure and have a type (a constant dened
in android/sensor.h idencal to the ones in android.hardware.Sensor):
#ifndef _PACKT_SENSOR_HPP_
#define _PACKT_SENSOR_HPP_
#include "Types.hpp"
#include <android/sensor.h>
namespace packt {
class EventLoop;
class Sensor {
public:
Sensor(EventLoop& pEventLoop, int32_t pSensorType);
status toggle();
status enable();
status disable();
private:
EventLoop& mEventLoop;
const ASensor* mSensor;
int32_t mSensorType;
};
}
#endif
7. Implement Sensor in jni/Sensor.cpp le and write enable() in three steps:
Get a sensor of a specic type with ASensorManager_
getDefaultSensor().
Then, enable it with ASensorEventQueue_enableSensor() so that the
event queue receives related events.
Chapter 8
[ 305 ]
Set the desired event rate with ASensorEventQueue_setEventRate().
For a game, we typically want measures close to real me. The minimum
delay is queried with ASensor_getMinDelay() and seng it to a lower
value results in failure.
Obviously, we should perform this setup only when the sensor event queue
is ready. Sensor is deacvated in disable() with ASensorEventQueue_
disableSensor() thanks to the sensor instance retrieved previously.
#include "Sensor.hpp"
#include "EventLoop.hpp"
#include "Log.hpp"
namespace packt {
Sensor::Sensor(EventLoop& pEventLoop, int32_t pSensorType):
mEventLoop(pEventLoop),
mSensor(NULL),
mSensorType(pSensorType)
{}
status Sensor::toggle() {
return (mSensor != NULL) ? disable() : enable();
}
status Sensor::enable() {
if (mEventLoop.mEnabled) {
mSensor = ASensorManager_getDefaultSensor(
mEventLoop.mSensorManager, mSensorType);
if (mSensor != NULL) {
if (ASensorEventQueue_enableSensor(
mEventLoop.mSensorEventQueue, mSensor) < 0) {
goto ERROR;
}
int32_t lMinDelay = ASensor_getMinDelay(mSensor);
if (ASensorEventQueue_setEventRate(mEventLoop
.mSensorEventQueue, mSensor, lMinDelay) < 0) {
goto ERROR;
}
} else {
packt::Log::error("No sensor type %d", mSensorType);
}
}
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
Handling Input Devices and Sensors
[ 306 ]
return STATUS_OK;
ERROR:
Log::error("Error while activating sensor.");
disable();
return STATUS_KO;
}
status Sensor::disable() {
if ((mEventLoop.mEnabled) && (mSensor != NULL)) {
if (ASensorEventQueue_disableSensor(
mEventLoop.mSensorEventQueue, mSensor) < 0) {
goto ERROR;
}
mSensor = NULL;
}
return STATUS_OK;
ERROR:
Log::error("Error while deactivating sensor.");
return STATUS_KO;
}
}
Sensors are connected to our event loop. Let's handle sensor events in our
input service.
8. Manage the accelerometer sensor in jni/InputService.hpp. Add a method
stop() to disable sensors when service stops:
#ifndef _PACKT_INPUTSERVICE_HPP_
#define _PACKT_INPUTSERVICE_HPP_
#include "Context.hpp"
#include "InputHandler.hpp"
#include "Sensor.hpp"
#include "Types.hpp"
#include <android_native_app_glue.h>
namespace packt {
class InputService : public InputHandler {
public:
InputService(android_app* pApplication,
Chapter 8
[ 307 ]
Sensor* pAccelerometer,
const int32_t& pWidth, const int32_t& pHeight);
status start();
status update();
void stop();
...
public:
bool onTouchEvent(AInputEvent* pEvent);
bool onKeyboardEvent(AInputEvent* pEvent);
bool onTrackballEvent(AInputEvent* pEvent);
bool onAccelerometerEvent(ASensorEvent* pEvent);
private:
...
bool mMenuKey;
Sensor* mAccelerometer;
};
}
#endif
9. Rewrite update() to toggle the accelerometer when the menu buon is pressed
(instead of leaving the applicaon). Implement stop() to disable sensors when
applicaon is stopped (and save baery):
...
namespace packt {
InputService::InputService(android_app* pApplication,
Sensor* pAccelerometer,
const int32_t& pWidth, const int32_t& pHeight) :
mApplication(pApplication),
mHorizontal(0.0f), mVertical(0.0f),
mRefPoint(NULL), mWidth(pWidth), mHeight(pHeight),
mMenuKey(false),
mAccelerometer(pAccelerometer)
{}
...
status InputService::update() {
if (mMenuKey) {
if (mAccelerometer->toggle() != STATUS_OK) {
Handling Input Devices and Sensors
[ 308 ]
return STATUS_KO;
}
}
mMenuKey = false;
return STATUS_OK;
}
void InputService::stop() {
mAccelerometer->disable();
}
...
10. Here is the core code which computes direcon from the accelerometer captured
values. In the following code, X and Z axis express the roll and the pitch respecvely.
We check for both, the roll and the pitch, whether the device is in a neutral
orientaon (that is, CENTER_*) or sloping to the extreme (MIN_* and (MAX_*). Z
values need to be inverted:
Android devices can be naturally portrait-oriented (most smart-phones
if not all) or landscape-oriented (mostly tablets). This has an impact on
applicaons which require portrait or landscape mode: axes are not
aligned the same way. Use y-axis (that is, vector.y) instead of x axis in
the following piece for landscape oriented devices.
...
bool InputService::onAccelerometerEvent(ASensorEvent* pEvent) {
const float GRAVITY = ASENSOR_STANDARD_GRAVITY / 2.0f;
const float MIN_X = -1.0f; const float MAX_X = 1.0f;
const float MIN_Y = 0.0f; const float MAX_Y = 2.0f;
const float CENTER_X = (MAX_X + MIN_X) / 2.0f;
const float CENTER_Y = (MAX_Y + MIN_Y) / 2.0f;
float lRawHorizontal = pEvent->vector.x / GRAVITY;
if (lRawHorizontal > MAX_X) {
lRawHorizontal = MAX_X;
} else if (lRawHorizontal < MIN_X) {
lRawHorizontal = MIN_X;
}
mHorizontal = CENTER_X - lRawHorizontal;
float lRawVertical = pEvent->vector.z / GRAVITY;
Chapter 8
[ 309 ]
if (lRawVertical > MAX_Y) {
lRawVertical = MAX_Y;
} else if (lRawVertical < MIN_Y) {
lRawVertical = MIN_Y;
}
mVertical = lRawVertical - CENTER_Y;
return true;
}
}
11. In jni/DroidBlaster.cpp, call stop() to ensure sensors get disabled:
#include "DroidBlaster.hpp"
#include "Log.hpp"
namespace dbs {
...
void DroidBlaster::onDeactivate() {
packt::Log::info("Deactivating DroidBlaster");
mGraphicsService->stop();
mInputService->stop();
mSoundService->stop();
}
...
}
Let's terminate by inializing the input service properly aer all the
modicaons are done.
12. Finally, inialize the accelerometer in jni/Main.hpp. Because they are closely
related, move the EventLoop inializaon line on top:
...
#include "GraphicsService.hpp"
#include "InputService.hpp"
#include "Sensor.hpp"
#include "SoundService.hpp"
#include "TimeService.hpp"
#include "Log.hpp"
void android_main(android_app* pApplication) {
packt::EventLoop lEventLoop(pApplication);
packt::Sensor lAccelerometer(lEventLoop,
ASENSOR_TYPE_ACCELEROMETER);
Handling Input Devices and Sensors
[ 310 ]
packt::TimeService lTimeService;
packt::GraphicsService lGraphicsService(pApplication,
&lTimeService);
packt::InputService lInputService(pApplication,
&lAccelerometer,
lGraphicsService.getWidth(),lGraphicsService.getHeight());
packt::SoundService lSoundService(pApplication);
packt::Context lContext = { &lGraphicsService, &lInputService,
&lSoundService, &lTimeService };
dbs::DroidBlaster lDroidBlaster(&lContext);
lEventLoop.run(&lDroidBlaster, &lInputService);
}
What just happened?
We have created an event queue to listen to sensor events. Events are wrapped in an
ASensorEvent structure, dened in android/sensor.h. This structure provides the:
Sensor event origin, that is, which sensor produced this event.
Sensor event occurrence me.
Sensor output value. This value is stored in a union structure, that is, you
can use either one of the inside structures (here, we are interested in the
acceleration vector).
The same ASensorEvent structure is used for any Android sensor:
typedef struct ASensorEvent {
int32_t version;
int32_t sensor;
int32_t type;
int32_t reserved0;
int64_t timestamp;
union {
float data[16];
ASensorVector vector;
ASensorVector acceleration;
ASensorVector magnetic;
float temperature;
float distance;
float light;
float pressure;
};
Chapter 8
[ 311 ]
int32_t reserved1[4];
} ASensorEvent;
typedef struct ASensorVector {
union {
float v[3];
struct {
float x;
float y;
float z;
};
struct {
float azimuth;
float pitch;
float roll;
};
};
int8_t status;
uint8_t reserved[3];
} ASensorVector;
In our example, the accelerometer is set up with the lowest event rate possible, which may
vary between devices. It is important to note that sensor event rate has a direct impact on
baery saving! So use a rate that is sucient for your applicaon. ASensor_ API oers
some method to query available sensors and their capabilies: ASensor_getName(),
ASensor_getVendor(), ASensor_getMinDelay(), and so on.
Sensors have a unique idener, dened in android/sensor.h, which is the same on all
Android devices: ASENSOR_TYPE_ACCELEROMETER, ASENSOR_TYPE_MAGNETIC_FIELD,
ASENSOR_TYPE_GYRISCOPE ASENSOR_TYPE_LIGHT, ASENSOR_TYPE_PROXIMITY.
Addional sensors may exist and be available even if they are not named in android/
sensor.h header. On Gingerbread, this is the case of the gravity sensor (idener 9),
the linear acceleraon sensor (idener 10) and the rotaon vector (idener 11).
The sense of orientaon
The rotaon vector sensor, successor of the now deprecated orientaon
vector, is essenal in Augmented Reality applicaon. It gives you device
orientaon in 3D space. Combined with the GPS, it allows locang any object
through the eye of your device. The rotaon sensor provides a data vector,
which can be translated to an OpenGL view matrix thanks to the android.
hardware.SensorManager class (see its source code). An example is
provided with this book in DroidBlaster_Part8-3-Orientation.
Handling Input Devices and Sensors
[ 312 ]
Have a go hero – Handling screen rotation
There is sadly no way to get device rotaon relave to screen natural orientaon with nave
APIs. Thus, we need to rely on JNI to get current rotaon properly. The piece of Java code to
detect screen rotaon is the following:
WindowManager mgr = (InputMethodManager)
myActivity.getSystemService(Context.WINDOW_SERVICE);
int rotation = mgr.getDefaultDisplay().getRotation();
Rotaon values are can be ROTATION_0, ROTATION_90, ROTATION_180, or ROTATION_270
(provided in the Java class Surface). Write the equivalent JNI code in four steps:
1. Create a Configuration class which takes an android_app as constructor
parameter and whose only purpose is to provide the rotaon value.
2. In Configuration constructor, aach the JavaVM, retrieve the rotaon,
and nally detach the VM.
3. Instanate both the Configuration class in your android_main() method
and pass it to your InputService to get rotaon value.
4. Write a ulity method toScreenCoord() to convert canonical sensor coordinates
(that is, in the natural orientaon referenal) to screen coordinates:
void InputService::toScreenCoord(screen_rot pRotation,
ASensorVector* pCanonical, ASensorVector* pScreen) {
struct AxisSwap {
int8_t mNegX; int8_t mNegY;
int8_t mXSrc; int8_t mYSrc;
};
static const AxisSwap lAxisSwaps[] = {
{ 1, -1, 0, 1}, // ROTATION_0
{ -1, -1, 1, 0}, // ROTATION_90
{ -1, 1, 0, 1}, // ROTATION_180
{ 1, 1, 1, 0}}; // ROTATION_270
const AxisSwap& lSwap = lAxisSwaps[pRotation];
pScreen->v[0] = lSwap.mNegX * pCanonical->v[lSwap.mXSrc];
pScreen->v[1] = lSwap.mNegY * pCanonical->v[lSwap.mYSrc];
pScreen->v[2] = pCanonical->v[2];
}
This piece of code comes from an interesng document about sensors on the NVidia
developer site at http://developer.download.nvidia.com/tegra/docs/
tegra_android_accelerometer_v5f.pdf.
Chapter 8
[ 313 ]
5. Finally, x onAccelerometerEvent() to reverse accelerometer axis according
to the current screen rotaon. Just call the ulity method and use resulng X
and Z axes.
The nal project is provided with this book
in DroidBlaster_Part8-3-Keyboard.
Summary
In this chapter, we learnt dierent ways to interact with Android navely using input
and sensors. We discovered how to handle touch events. We also read key events from
keyboard and D-Pad and processed trackballs moon events. Finally, we have turned the
accelerometer into a Joypad. Because of Android fragmentaon, expect specicies in
input device's behavior and be prepared to adapt your code.
We have already been far in the capabilies of Android NDK in terms of applicaon structure,
graphics, sound, input, and sensors. But reinvenng the wheel is not a soluon! In the next
chapter, we are going to unleash the real power of Android by porng exisng libraries.
9
Porting Existing Libraries to Android
There are two main reasons why one would be interested in the Android NDK:
rst, for performance, and second, for portability. In the previous chapters,
we have seen how to access main nave Android APIs from nave code for
eciency purposes. In this chapter, we are going to bring the whole C/C++
ecosystem to Android. Well, at least discovering the path, as decades of C/
C++ development would be dicult to t the limited memory of mobile devices
anyway! Indeed, C and C++ are sll some of the most widely used programming
languages nowadays.
In previous NDK releases, portability was limited due to the paral support of
C++, especially Excepons and Run-Time Type informaon (or RTTI, a basic C++
reecon mechanism to get data types at runme such as instanceof in Java).
Any library requiring them could not be ported without modifying their code or
installing a custom NDK (the Crystax NDK, rebuilt by the community from ocial
sources and available at http://www.crystax.net/). Hopefully, many of
these restricons have been lied in NDK R5 (except wide character support).
In this chapter, in order to port exisng code to Android, we are going to learn how to:
Acvate the Standard Template Library and Boost framework
Enable excepons and Run Time Type Informaon (or RTTI)
Compile two open source libraries: Box2D and Irrlicht
Write Makeles to compile modules
By the end of this chapter, you should understand the nave building process and know
how to use Makeles appropriately.
Porng exisng libraries to Android
[ 316 ]
Developing with the Standard Template Library
The Standard Template Library (or STL) is a normalized library of containers, iterators,
algorithms, and helper classes, to ease most common programming operaons: dynamic
arrays, associave arrays, strings, sorng, and so on. This library gained reconnaissance
among developers over years and is widely spread. Developing in C++ without the STL
is like coding with one hand behind to the back!
Unl NDK R5, no STL was included. The whole C++ ecosystem was only one step ahead,
but not yet reachable. With some eorts, compiling an STL implementaon (for example,
STLport), for which excepons and RTTI were oponal, was possible, but only if the code
built upon did not require these features (unless building with the Crystax NDK). Anyway,
this nightmare is over, as STL and excepons are now ocially included. Two
implementaons can be chosen:
STLport, a mulplaorm STL, which is probably one of the most portable
implementaons, well accepted among open source projects
GNU STL (more commonly libstdc++), the ocial GCC STL
The STLport version included in the NDK R5 does not support excepons (RTTI being
supported from NDK R7) but can be used either as a shared or a stac library. On the
other hand, GNU STL supports excepons but is currently available as a stac library only.
In this rst part, let's embed STLport in DroidBlaster to ease collecon management.
Project DroidBlaster_Part8-3 can be used as a starng point
for this part. The resulng project is provided with this book under the
name DroidBlaster_Part9-1.
Time for action – embedding GNU STL in DroidBlaster
1. Create a jni/Application.mk le beside jni/Android.mk and write the
following content. That's it! Your applicaon is now STL-enabled, thanks to this
single line:
APP_STL = stlport_static
Of course, enabling the STL is useless, if we do not acvely use it in our code.
Let's take advantage of this opportunity to switch from asset les to external
les (on a sdcard or internal memory).
Chapter 9
[ 317 ]
2. Open the exisng le, jni/Resource.hpp, and:
Include the fstream stl header to read les.
Replace the Asset management members with an ifstream object
(that is, an input le stream). We are also going to need a buer for the
bufferize() method.
Remove the descript() method and the ResourceDescriptor class.
Descriptors work with the Asset API only.
#ifndef _PACKT_RESOURCE_HPP_
#define _PACKT_RESOURCE_HPP_
#include "Types.hpp"
#include <fstream>
namespace packt {
...
class Resource {
...
private:
const char* mPath;
std::ifstream mInputStream;
char* mBuffer;
};
}
#endif
3. Open the corresponding implementaon le jni/Resource.cpp. Replace the
previous implementaon, based on the asset management API with STL streams.
Files will be opened in binary mode (even the le map XML le that is going to be
directly buered in memory). To read the le length, we can use the stat() POSIX
primive. Method bufferize() is emulated with a temporary buer:
#include "Resource.hpp"
#include "Log.hpp"
#include <sys/stat.h>
namespace packt {
Resource::Resource(android_app* pApplication, const char*
pPath):
Porng exisng libraries to Android
[ 318 ]
mPath(pPath), mInputStream(), mBuffer(NULL)
{}
status Resource::open() {
mInputStream.open(mPath, std::ios::in | std::ios::binary);
return mInputStream ? STATUS_OK : STATUS_KO;
}
void Resource::close() {
mInputStream.close();
delete[] mBuffer; mBuffer = NULL;
}
status Resource::read(void* pBuffer, size_t pCount) {
mInputStream.read((char*)pBuffer, pCount);
return (!mInputStream.fail()) ? STATUS_OK : STATUS_KO;
}
const char* Resource::getPath() {
return mPath;
}
off_t Resource::getLength() {
struct stat filestatus;
if (stat(mPath, &filestatus) >= 0) {
return filestatus.st_size;
} else {
return -1;
}
}
const void* Resource::bufferize() {
off_t lSize = getLength();
if (lSize <= 0) return NULL;
mBuffer = new char[lSize];
mInputStream.read(mBuffer, lSize);
if (!mInputStream.fail()) {
return mBuffer;
} else {
return NULL;
}
}
}
These changes to the reading system should all be transparent. Except one.
Chapter 9
[ 319 ]
4. Background music was previously played through an asset descriptor. Now, we
provide a real le. So, in jni/SoundService.cpp, change the data source by
replacing the SLDataLocator_AndroidFD structure with SLDataLocation_URI.
The le locaon has to be prexed with file://, when it comes from the sdcard
(it could also be, for example, http://, if the le was coming from a server). To
help building the nal URI, concatenate the prex and the path using STL strings.
The le is sll an MP3, so the data format does not change:
#include "SoundService.hpp"
#include "Resource.hpp"
#include "Log.hpp"
#include <string>
namespace packt {
...
status SoundService::playBGM(const char* pPath) {
SLresult lRes;
Log::info("Opening BGM %s", pPath);
SLDataLocator_URI lDataLocatorIn;
std::string lPath = std::string("file://") + pPath;
lDataLocatorIn.locatorType = SL_DATALOCATOR_URI;
lDataLocatorIn.URI = (SLchar*) lPath.c_str();
SLDataFormat_MIME lDataFormat;
lDataFormat.formatType = SL_DATAFORMAT_MIME;
...
return STATUS_OK;
ERROR:
return STATUS_KO;
}
...
}
Porng exisng libraries to Android
[ 320 ]
5. Copy resources in your asset directory to your sdcard (or internal memory,
depending on your device) in the directory droidblaster (for example,
/sdcard/droidblaster).
Almost all Android devices can store les in an addional storage locaon
mounted in directory /sdcard. "Almost" is the important word here… Since
the rst Android G1, the meaning of "sdcard" has changed. Some recent
devices have an external storage that is in fact internal (e.g. ash memory
on some tablets), and some others have a second storage locaon at their
disposal (although in most cases, the second storage is mounted inside /
sdcard). Moreover, path /sdcard is not engraved into the marble…
To detect safely the addional storage locaon, the only soluon
is to rely on JNI, by calling android.os.Environment.
getExternalStorageDirectory(). You can also check that storage
is available with getExternalStorageState(). Note that the word
"External" in API method names is here for historical reasons only.
Replace paths to resources in each le that needs one (change the path
if necessary):
/sdcard/droidblaster/tilemap.png in jni/Background.cpp.
/sdcard/droidblaster/tilemap.tmx in jni/Background.cpp.
/sdcard/droidblaster/start.pcm in jni/DroidBlaster.cpp.
/sdcard/droidblaster/bgm.mp3 in jni/DroidBlaster.cpp.
/sdcard/droidblaster/ship.png in jni/Ship.cpp.
6. Run the applicaon. Noced it? Everything runs like before!
Now, let's take advantage of the STL to give some company to our lonely ship.
7. First, let's create a lile randomizaon helper macro in exisng le jni/Type.hpp:
#ifndef _PACKT_TYPES_HPP_
#define _PACKT_TYPES_HPP_
#include <stdint.h>
#include <cstdlib>
namespace packt {
...
}
#define RAND(pMax) (float(pMax) * float(rand()) / float(RAND_MAX))
#endif
Chapter 9
[ 321 ]
8. The random value generator has to be inialized rst, with a seed. A possible
soluon is to set the seed value to the current me in jni/TimeService.cpp:
#include "TimeService.hpp"
#include "Log.hpp"
#include <cstdlib>
namespace packt {
TimeService::TimeService() :
mElapsed(0.0f),
mLastTime(0.0f) {
srand(time(NULL));
}
...
}
9. Create a new header le jni/Asteroid.hpp, similar to the one used for the
Ship game object, to represent a dangerous and frightening asteroid:
#ifndef _DBS_ASTEROID_HPP_
#define _DBS_ASTEROID_HPP_
#include "Context.hpp"
#include "GraphicsService.hpp"
#include "GraphicsSprite.hpp"
#include "Types.hpp"
namespace dbs {
class Asteroid {
public:
Asteroid(packt::Context* pContext);
void spawn();
void update();
private:
packt::GraphicsService* mGraphicsService;
packt::TimeService* mTimeService;
packt::GraphicsSprite* mSprite;
packt::Location mLocation;
float mSpeed;
};
}
#endif
Porng exisng libraries to Android
[ 322 ]
10. Implement the Asteroid class in jni/Asteroid.cpp. An asteroid is represented
with a sprite loaded at construcon me.
The Asteroid game object itself is inialized in spawn(), above the top of the
screen (that is, they are inially hidden). Asteroids are distributed randomly over
screen width and have a random animaon and movement speed.
During frame processing in update(), asteroids fall from top to boom, according
to their speed. When they reach the boom, they are recreated.
#include "Asteroid.hpp"
#include "Log.hpp"
namespace dbs {
Asteroid::Asteroid(packt::Context* pContext) :
mTimeService(pContext->mTimeService),
mGraphicsService(pContext->mGraphicsService),
mLocation(), mSpeed(0.0f) {
mSprite = pContext->mGraphicsService->registerSprite(
mGraphicsService->registerTexture(
"/sdcard/droidblaster/asteroid.png"),
64, 64, &mLocation);
}
void Asteroid::spawn() {
const float MIN_SPEED = 4.0f;
const float MIN_ANIM_SPEED = 8.0f, ANIM_SPEED_RANGE = 16.0f;
mSpeed = -RAND(mGraphicsService->getHeight()) - MIN_SPEED;
float lPosX = RAND(mGraphicsService->getWidth());
float lPosY = RAND(mGraphicsService->getHeight())
+ mGraphicsService->getHeight();
mLocation.setPosition(lPosX, lPosY);
float lAnimSpeed = MIN_ANIM_SPEED + RAND(ANIM_SPEED_RANGE);
mSprite->setAnimation(8, -1, lAnimSpeed, true);
}
void Asteroid::update() {
mLocation.translate(0.0f, mTimeService->elapsed() * mSpeed);
if (mLocation.mPosY <= 0) {
spawn();
}
}
}
Chapter 9
[ 323 ]
11. Open the jni/DroidBlaster.hpp header and include the vector header, the
most common STL container that encapsulates C arrays. Then, declare a vector of
asteroid pointers (prexed with the std namespace):
#ifndef _PACKT_DROIDBLASTER_HPP_
#define _PACKT_DROIDBLASTER_HPP_
#include "ActivityHandler.hpp"
#include "Asteroid.hpp"
#include "Background.hpp"
#include "Context.hpp"
...
#include "Types.hpp"
#include <vector>
namespace dbs {
class DroidBlaster : public packt::ActivityHandler
{
...
private:
...
Background mBackground;
Ship mShip;
std::vector<Asteroid*> mAsteroids;
packt::Sound* mStartSound;
};
}
#endif
12. Finally, open jni/DroidBlaster.cpp. Include this new container in the
constructor inializaon list and insert Asteroid instances with method
push_back().
Then, in the destructor, we can iterate through the vector using an iterator to
release every vector entry. Syntax is a bit more tedious, but gives more exibility:
#include "DroidBlaster.hpp"
#include "Log.hpp"
namespace dbs {
DroidBlaster::DroidBlaster(packt::Context* pContext) :
Porng exisng libraries to Android
[ 324 ]
mGraphicsService(pContext->mGraphicsService),
mInputService(pContext->mInputService),
mSoundService(pContext->mSoundService),
mTimeService(pContext->mTimeService),
mBackground(pContext), mShip(pContext), mAsteroids(),
mStartSound(mSoundService->registerSound(
"/sdcard/droidblaster/start.pcm")) {
for (int i = 0; i < 16; ++i) {
mAsteroids.push_back(new Asteroid(pContext));
}
}
DroidBlaster::~DroidBlaster() {
std::vector<Asteroid*>::iterator iAsteroid =
mAsteroids.begin();
for (; iAsteroid < mAsteroids.end() ; ++iAsteroid) {
delete *iAsteroid;
}
mAsteroids.clear();
}
...
13. Sll in jni/DroidBlaster.cpp, apply the same iteraon technique to inialize
asteroid game objects (in onActivate()) and iterate each frame (in onStep()):
...
packt::status DroidBlaster::onActivate() {
...
mBackground.spawn();
mShip.spawn();
std::vector<Asteroid*>::iterator iAsteroid =
mAsteroids.begin();
for (; iAsteroid < mAsteroids.end() ; ++iAsteroid) {
(*iAsteroid)->spawn();
}
mTimeService->reset();
return packt::STATUS_OK;
}
...
Chapter 9
[ 325 ]
packt::status DroidBlaster::onStep() {
mTimeService->update();
mBackground.update();
mShip.update();
std::vector<Asteroid*>::iterator iAsteroid =
mAsteroids.begin();
for (; iAsteroid < mAsteroids.end(); ++iAsteroid) {
(*iAsteroid)->update();
}
// Updates services.
...
return packt::STATUS_OK;
}
...
}
14. Copy the asteroid.png sprite sheet to your droidblaster storage directory.
File asteroid.png is provided with this book in Chapter9/Resource.
What just happened?
We have seen how to access a binary le located on the SD-Card through STL streams. All
asset les became simple les on the addional storage. This change can be made almost
transparent at the excepon of OpenSL ES MIME player, which needs a dierent locator.
We have also seen how to manipulate STL strings and avoid using the complex C string
manipulaon primives.
Finally, we have implemented a set of Asteroid game objects managed inside an STL
container vector, instead of a raw C array. STL containers automacally handle memory
management (array resizing operaons and so on). File access happens like on any Unix
le systems, SD-Card being available from a mount point (located generally, but not always,
in /sdcard).
SD-card storage should always be considered for applicaons with heavy resource
les. Indeed, installing heavy APK causes trouble on memory-limited devices.
Porng exisng libraries to Android
[ 326 ]
Android and endianness
Beware of plaorm and le endianness with external les. Although all
ocial Android devices are lile-endian, there is no guarantee this will
remain true (for example, there exist some unocial ports for Android on
other CPU architectures). ARM supports both lile-and big-endian encoding,
whereas x86 (available since NDK R6) are lile-endian only. Endian encoding
is converble, thanks to POSIX primives declared in endian.h.
We have linked STLport as a stac library. But, we could have linked it dynamically, or linked
to the GNU STL. Which choice to make depends on your needs:
No excepons or RTTI needed, but STL required by several libraries: In that case, if a
consequent subset of STL features is necessary, stlport_shared should be used.
No excepons or RTTI needed and STL used by a single library or only a small subset
required: Consider using stlport_static instead, as memory usage may be
lower.
Excepon handling or RTTI are needed: Link against gnustl_static.
Since NDK R7, RTTI are supported by STLport, but not excepons.
STL is denitely a huge improvement that avoids repeve and error-prone code. Many
open source libraries require it and can now be ported without much trouble. More
documentaon about it can be found at http://www.cplusplus.com/reference/stl
and on SGI's website (publisher of the rst STL), at http://www.sgi.com/tech/stl.
Static versus shared
Remember that shared libraries need to be loaded manually at runme. If you forget to
load one of them, an error is raised, as soon as dependent libraries (or the applicaon)
are loaded. As it is not possible to predict in advance which funcons are going to be
called, they are loaded enrely in memory, even if most of their contents remain unused.
On the other hand, stac libraries are de facto loaded with dependent libraries. Indeed, stac
libraries do not really exist as such. They are copied into dependent libraries during linking.
The drawback is that binary code may get duplicated in each library, and memory is thus
wasted. However, since the linker knows precisely which part of the library gets called from the
embedding code, it can copy only what is needed, resulng in a limited size aer compilaon.
Chapter 9
[ 327 ]
Also remember that a Java applicaon can load shared libraries only (which can be
themselves linked against either shared or stac libraries). With a nave acvity, the main
shared library is specied through the android.app.lib_name property, in the applicaon
manifest. Libraries referenced from another library must be loaded manually before. The
NDK does not do this itself.
Shared libraries can be loaded easily, using System.loadLibrary() in a JNI applicaon.
But, a NativeActivity is transparent. So, if you decide to use shared libraries, then
the only soluon is to write your own Java acvity, inhering from NativeActivity
and invoking the appropriate loadLibrary() direcves. For instance, below is what
DroidBlaster acvity would look like, if we were using stlport_shared instead:
package com.packtpub.droidblaster
import android.app.NativeActivity
public class MyNativeActivity extends NativeActivity {
static {
System.loadLibrary("stlport_shared");
System.loadLibrary("droidblaster");
}
}
STL performances
When developing for performance, a standard STL container is not always the best
choice, especially in terms of memory management and allocaon. Indeed, STL is an
all-purpose library, wrien for common cases. Alternave libraries should be considered
for performance-crical code. A few examples are:
EASTL: An STL replacement library, developed by Electronic Arts, and developed
with gaming in mind. Only 50 percent of the projects have been released (as part
of the EA open source program), which are nevertheless highly interesng. An
extract is available in the repository https://github.com/paulhodge/EASTL.
A must-read paper detailing EASTL technical details can be found on the Open
Standards website at http://www.open-std.org/jtc1/sc22/wg21/docs/
papers/2007/n2271.html.
RDESTL: It is an open source subset of the STL, based on the EASTL technical paper,
which was published several years before EASTL code release. The code repository
can be found at http://code.google.com/p/rdestl/.
Google SparseHash: For a high performance associave array library (note that
RDESTL is also quite good at that).
This is far from exhausve. Just dene your exact needs to make the most appropriate choice.
Porng exisng libraries to Android
[ 328 ]
Compiling Boost on Android
If STL is the most common framework among C++ programs, Boost probably comes right
aer. A real Swiss army knife, this toolkit contains a profusion of ulies to handle most
common needs, and even more! The most popular features of Boost are smart pointers, an
encapsulaon of raw pointers in a reference-counng class to handle memory allocaon, and
deallocaon automacally. They avoid most memory leaks or pointer misuse for almost free.
Boost, like STL, is mainly a template library, which means that no compilaon is needed for
most of its modules. For instance, including the smart pointer header le is enough to use
them. However, a few of its modules need to be compiled as a library rst (for example, the
threading module).
We are now going to see how to build Boost on the Android NDK and replace raw,
unmanaged pointers with smarter ones.
Project DroidBlaster_Part9-1 can be used as a starng
point for this part. The resulng project is provided with this book,
under the name DroidBlaster_Part9-2.
Time for action – embedding Boost in DroidBlaster
1. Download Boost from http://www.boost.org/ (version 1.47.0, in this book).
The Boost 1.47.0 archive is provided with this book in
directory Chapter09/Library.
2. Uncompress the archive into ${ANDROID_NDK}/sources. Name the
directory boost.
3. Open a command line window and go to the boost directory. Launch bootstrap.
bat on Windows or the ./bootstrap.sh script on Linux and Mac OS X, to build
b2. This program, previously named BJam, is a custom building tool similar to Make.
4. Open the le boost/tools/build/v2/user-config.jam. This le is, like
its name suggests, a conguraon le that can be set up to customize Boost
compilaon.
Update user-config.jam. Initial content contains only comments
and can be erased:
import os ;
Chapter 9
[ 329 ]
if [ os.name ] = CYGWIN || [ os.name ] = NT {
androidPlatform = windows ;
}
else if [ os.name ] = LINUX {
androidPlatform = linux-x86 ;
}
else if [ os.name ] = MACOSX {
androidPlatform = darwin-x86 ;
}
...
5. Compilaon is performed stacally. BZip is deacvated, because it is unavailable,
by default, on Android (we could however compile it separately):
...
modules.poke : NO_BZIP2 : 1 ;
...
6. Compiler is recongured to use the NDK GCC toolchain (g++, ar, and ranlib) in
stac mode (the ar archiver being in charge of creang the stac library). Direcve
sysroot indicates which Android API release to compile and link against. The
specied directory is located in the NDK and contains include les and libraries
specic to this release:
...
ANDROID_NDK = ../.. ;
using gcc : android4.4.3 :
$(ANDROID_NDK)/toolchains/arm-linux-androideabi-4.4.3/
prebuilt/$(androidPlatform)/bin/arm-linux-androideabi-g++ :
<archiver>$(ANDROID_NDK)/toolchains/arm-linux-
androideabi-4.4.3/prebuilt/$(androidPlatform)/bin/arm-linux-
androideabi-ar
<ranlib>$(ANDROID_NDK)/toolchains/arm-linux-androideabi-4.4.3/
prebuilt/$(androidPlatform)/bin/arm-linux-androideabi-ranlib
<compileflags>--sysroot=$(ANDROID_NDK)/platforms/android-9/
arch-arm
<compileflags>-I$(ANDROID_NDK)/sources/cxx-stl/gnu-libstdc++/
include
<compileflags>-I$(ANDROID_NDK)/sources/cxx-stl/gnu-libstdc++/
libs/armeabi/include
...
Porng exisng libraries to Android
[ 330 ]
7. A few opons have to be dened to tweak Boost compilaon:
NDEBUG to deacvate debug mode
BOOST_NO_INTRINSIC_WCHAR_T to indicate the lack of support for wide
chars
BOOST_FILESYSTEM_VERSION is set to 2, because the latest version of
Boost FileSystem module (version 3) brings incompable changes related
to wide chars
no-strict-aliasing to disable opmizaons related to type aliasing
-02 to specify opmizaon level
...
<compileflags>-DNDEBUG
<compileflags>-D__GLIBC__
<compileflags>-DBOOST_NO_INTRINSIC_WCHAR_T
<compileflags>-DBOOST_FILESYSTEM_VERSION=2
<compileflags>-lstdc++
<compileflags>-mthumb
<compileflags>-fno-strict-aliasing
<compileflags>-O2
;
8. With the previously opened terminal, sll in the boost directory, launch
compilaon using the command line below. We need to exclude two modules
not working with the NDK:
The Serializaon module, which requires wide characters (not supported
by the ocial NDK yet)
Python, which requires addional libraries not available on the NDK
by default
b2 --without-python --without-serialization toolset=gcc-
android4.4.3 link=static runtime-link=static target-os=linux
--stagedir=android
9. Compilaon should take quite some me, but eventually it will fail! Launch
compilaon a second me to nd the error message hidden inside thousands of
lines the rst me. You should get a ::statvfs has not been declared... This problem
is related to boost/libs/filesystem/v2/src/v2_operations.cpp. This
le, normally at line 62, includes the sys/statvfs.h system header. However,
the Android NDK provides sys/vfs.h instead. We have to include it in v2_
operations.cpp:
Chapter 9
[ 331 ]
Android is (more or less) a Linux with its own specicies. If a library does
not take them into account (yet!), expect to encounter these kinds of
annoyances frequently.
...
# else // BOOST_POSIX_API
# include <sys/types.h>
# if !defined(__APPLE__) && !defined(__OpenBSD__) \
&& !defined(__ANDROID__)
# include <sys/statvfs.h>
# define BOOST_STATVFS statvfs
# define BOOST_STATVFS_F_FRSIZE vfs.f_frsize
# else
#ifdef __OpenBSD__
# include <sys/param.h>
#elif defined(__ANDROID__)
# include <sys/vfs.h>
#endif
# include <sys/mount.h>
# define BOOST_STATVFS statfs
...
10. Compile again. No message …failed updang X targets… should appear this me.
Libraries are compiled in ${ANDROID_NDK}/boost/android/lib/.
11. Several other incompabilies may appear when using the various modules of
Boost. For example, if you prefer to generate a random number with Boost and
decide to include boost/random.hpp, you will encounter a compilaon error
related to endianness. To x it, add a denion for Android in boost/boost/
detail/endian.hpp, at line 34:
...
#if defined (__GLIBC__) || defined(__ANDROID__)
# include <endian.h>
# if (__BYTE_ORDER == __LITTLE_ENDIAN)
# define BOOST_LITTLE_ENDIAN
...
The patches applied in previous steps are provided with this
book in directory Chapter09/Library/boost_1_47_0_
android, along with compiled binaries.
Porng exisng libraries to Android
[ 332 ]
12. Sll in the boost directory, create a new Android.mk le to declare the newly
compiled libraries as Android modules. It needs to contain one module declaraon
per module. For example, dene one library boost_thread, referencing the
stac library android/lib/libboost_thread.a. Variable LOCAL_EXPORT_C_
INCLUDES is important to automacally append boost includes when referenced
from a program:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= boost_thread
LOCAL_SRC_FILES:= android/lib/libboost_thread.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
include $(PREBUILT_STATIC_LIBRARY)
More modules can be declared in the same le with the same set of lines (for
example, boost_iostreams, etc.).
Android.mk is provided in Chapter09/Library/
boost_1_47_0_android.
Now, let's use Boost in our own project.
13. Go back to the DroidBlaster project. To include Boost in an applicaon, we need to
link with an STL implementaon supporng excepons. Thus, we need to replace
STLport with GNU STL (available as a stac library only) and acvate excepons:
APP_STL := gnustl_static
APP_CPPFLAGS := -fexceptions
14. Finally, open your Android.mk le and include a Boost module to check that
everything works. For example, try the Boost thread module:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LS_CPP=$(subst $(1)/,,$(wildcard $(1)/*.cpp))
LOCAL_MODULE := droidblaster
LOCAL_SRC_FILES := $(call LS_CPP,$(LOCAL_PATH))
LOCAL_LDLIBS := -landroid -llog -lEGL -lGLESv1_CM -lOpenSLES
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
Chapter 9
[ 333 ]
LOCAL_STATIC_LIBRARIES := android_native_app_glue png boost_thread
include $(BUILD_SHARED_LIBRARY)
$(call import-module,android/native_app_glue)
$(call import-module,libpng)
$(call import-module,boost)
DroidBlaster is now Boost-enabled! First, let's see if excepons work.
15. Edit jni/GraphicsTilemap.cpp. Remove the RapidXML error handling block
and replace the call to setjmp() with a C++ try/catch. Catch a parse_error
excepon:
...
namespace packt {
...
int32_t* GraphicsTileMap::loadFile() {
...
mResource.close();
}
try {
lXmlDocument.parse<parse_default>(lFileBuffer);
} catch (rapidxml::parse_error& parseException) {
packt::Log::error("Error while parsing TMX file.");
packt::Log::error(parseException.what());
goto ERROR;
}
...
}
}
Now, we could use smart pointers to manage memory allocaon and deallocaon
automacally.
16. Boost and STL tends to cause a proliferaon of unreadable denions. Let's simplify
their use by dening custom smart pointer and vector types with the typedef
keyword in jni/Asteroid.hpp. The vector type contains smart pointers instead
of raw pointers:
#ifndef _DBS_ASTEROID_HPP_
#define _DBS_ASTEROID_HPP_
#include "Context.hpp"
#include "GraphicsService.hpp"
Porng exisng libraries to Android
[ 334 ]
#include "GraphicsSprite.hpp"
#include "Types.hpp"
#include <boost/shared_ptr.hpp>
#include <vector>
namespace dbs {
class Asteroid {
...
public:
typedef boost::ptr <Asteroid> ptr;
typedef std::vector<shared> vec;
typedef vec::iterator vec_it;
}
}
#endif
17. Open jni/DroidBlaster.hpp and remove the vector header inclusion (now
included in jni/Asteroid.hpp). Use the newly dened type Android::vec:
...
namespace dbs {
class DroidBlaster : public packt::ActivityHandler {
...
private:
...
Background mBackground;
Ship mShip;
Asteroid::vec mAsteroids;
packt::Sound* mStartSound;
};
}
#endif
18. Every iterator declaraon involving asteroids now needs to be switched with the
new 'typedefed" types. Code is not much dierent except one thing… Look carefully:
the destructor is now empty! All pointers are deallocated automacally by Boost:
#include "DroidBlaster.hpp"
#include "Log.hpp"
namespace dbs {
DroidBlaster::DroidBlaster(packt::Context* pContext) :
Chapter 9
[ 335 ]
... {
for (int i = 0; i < 16; ++i) {
Asteroid::ptr lAsteroid(new Asteroid(pContext));
mAsteroids.push_back(lAsteroid);
}
}
DroidBlaster::~DroidBlaster()
{}
packt::status DroidBlaster::onActivate() {
...
mBackground.spawn();
mShip.spawn();
Asteroid::vec_it iAsteroid = mAsteroids.begin();
for (; iAsteroid < mAsteroids.end() ; ++iAsteroid) {
(*iAsteroid)->spawn();
}
mTimeService->reset();
return packt::STATUS_OK;
}
...
packt::status DroidBlaster::onStep() {
mTimeService->update();
mShip.update();
Asteroid::vec_it iAsteroid = mAsteroids.begin();
for (; iAsteroid < mAsteroids.end(); ++iAsteroid) {
(*iAsteroid)->update();
}
if (mGraphicsService->update() != packt::STATUS_OK) {
...
return packt::STATUS_OK;
}
}
Porng exisng libraries to Android
[ 336 ]
What just happened?
We have xed a minor issue with Boost code and wrien the proper conguraon to compile
it. Finally, we have discovered one of Boost's most famous (and helpful!) features: smart
pointers. But Boost provides much more. See its documentaon, located at http://www.
boost.org/doc/libs, to discover its full richness. You can nd informaon about Android
issues on the bug tracker.
We have compiled Boost manually, using its dedicated building tool b2, customized to use
the NDK tool chain. Then, prebuilt stac libraries have been published using an Android.
mk and imported into a nal applicaon with NDK import-module direcve. Every me
Boost is updated or a modicaon is made, code has to be manually compiled again with b2.
Only the nal prebuilt library is imported into client applicaon with PREBUILT_STATIC_
LIBRARY direcve (and the shared library equivalent PREBUILT_SHARED_LIBRARY). On the
other hand, BUILD_STATIC_LIBRARY and BUILD_SHARED_LIBRARY would recompile the
whole module each me a new client applicaon imports it or changes its own compilaon
sengs (for example, when switching APP_OPTIM from debug to release in Application.
mk).
To make Boost work, we have switched from STLport to GNU STL, which is currently the
only one to support excepons. This replacement occurs in the Application.mk le,
by replacing stlport_static with gnustl_static. Excepons and RTTI are acvated
very easily by appending -fexceptions and -frtti, respecvely, to the APP_CPPFLAGS
direcve in the same le, or the LOCAL_CPPFLAGS of the concerned library. By default,
Android compiles with -fno-exceptions and -fno-rtti ags.
A problem? Clean!
It happens oen, especially when switching from one STL to another, that
libraries do not get recompiled well. Sadly, this results in rather weird and
obscure undened link errors. If you have a doubt, just clean your project from
the Eclipse menu | Project/Clean... or the command ndk-build clean, in
your applicaon root directory.
Excepons have the reputaon of making the compiled code bigger and less ecient.
They prevent the compiler from performing some clever opmizaons. However, whether
excepons are worse than error checking or even no check at all is a highly debatable queson.
In fact, Google's engineers dropped them in rst releases because GCC 3.x generated poor
excepon handling code for ARM processors. However, the build chain now uses GCC 4.x,
which does not suer from this aw. Compared to manual error checking and handling of
exceponal cases, penalty should not be signicant most of the me, assuming excepons
are used for exceponal cases only. Thus, the choice of excepons or not is up to you
(and your embedded libraries)!
Chapter 9
[ 337 ]
Excepon handling in C++ is not easy and imposes a strict discipline!
They must be used strictly for exceponal cases and require carefully
designed code. Have a look at the Resource Acquision Is
Inializaon (abbreviated RAII) idiom to properly handle them.
Have a go hero – threading with Boost
DroidBlaster is now a bit safer, thanks to smart pointers. However, smart pointers are
based on template les. There is no need to link against Boost modules to use them. So,
to check if this works, modify the DroidBlaster class to launch a Boost thread updates
asteroids in the background. The thread must be run in a separate method (for example,
updateBackground()). You can launch the thread itself from onStep() and join it (that is,
wait for the thread to terminate its task) before the GraphicsService draws its content:
...
#include <boost/thread.hpp>
...
void DroidBlaster::updateThread() {
Asteroid::vec_it iAsteroid = mAsteroids.begin();
for (; iAsteroid < mAsteroids.end(); ++iAsteroid) {
(*iAsteroid)->update();
}
}
packt::status DroidBlaster::onStep() {
mTimeService->update();
boost::thread lThread(&DroidBlaster::updateThread, this);
mBackground.update();
mShip.update();
lThread.join();
if (mGraphicsService->update() != packt::STATUS_OK) {
...
}
...
The nal result is available in project DroidBlaster_Part9-2-Thread,
provided with this book.
Porng exisng libraries to Android
[ 338 ]
If you have experience with threads, this piece of code will probably make you jump out of
your chair. Indeed, this is the best example of what should not be done with threads because:
Funconal division (for example, one service in its own thread) is generally not the
best way to achieve threading eciently.
Only a few mobile processors are mul-cores (but this fact is changing really fast).
Thus, creang a thread on a single processor will not improve performance, except
for blocking operaons such as I/O.
Mul-cores can have more than just 2 cores! Depending on the problem to solve,
it can be a good idea to have as many threads as cores.
Creang threads on demand is not ecient. Thread pools are a beer approach.
Threading is a really complex maer and should be taken it into account
early in your design. The Intel developer website (http://software.
intel.com/) provides lots of interesng resources about threading and
a library named Threading Building Block, which is a good reference in
design terms (but not ported on Android, yet, despite some progress).
Porting third-party libraries to Android
With the Standard Template Library and Boost in our basket, we are ready to port almost any
library to Android. Actually, many third-party libraries have been already ported and many
more are coming. But when nothing is available, we have to rely on our own skills to port
them. In this nal part, we are going to compile two of them:
Box2D: It is a highly popular open source physics simulaon engine, embedded
in many 2D games such as Angry Birds (quite a good reference!). It is available in
several languages, Java included. But, its primary language is C++.
Irrlicht: It is a real-me open source 3D engine. It is cross-plaorm and oers
DirectX, OpenGL, and GLES bindings.
We are going to use them in the next chapter to implement the DroidBlaster physics layer
and brings graphics to the third dimension.
Project DroidBlaster_Part9-2 can be used as a starng point for
this part. The resulng project is provided with this book, under
the name DroidBlaster_Part9-3.
Chapter 9
[ 339 ]
Time for action – compiling Box2D and Irrlicht with the NDK
First, let's try to port Box2D on the Android NDK.
The Box2D 2.2.1 archive is provided with this book, in
directory Chapter09/Library.
1. Go to http://www.box2d.org/ and download the Box2D source archive (2.2.1
in this book). Uncompress it into ${ANDROID_NDK}/sources/ and name the
directory box2d.
2. Create and open an Android.mk le in the root of the box2d directory.
Save the current directory inside the LOCAL_PATH variable. This step is always
necessary, because an NDK build system may switch to another directory at
any me during compilaon.
LOCAL_PATH:= $(call my-dir)
...
3. Then, list all Box2D source les to compile. We are interested in source le name
only, which can be found in ${ANDROID_NDK}/sources/box2d/Box2D/Box2D.
Use the LS_CPP helper funcon to avoid copying each lename.
...
LS_CPP=$(subst $(1)/,,$(wildcard $(1)/$(2)/*.cpp))
BOX2D_CPP:= $(call LS_CPP,$(LOCAL_PATH),Box2D/Collision) \
$(call LS_CPP,$(LOCAL_PATH),Box2D/Collision/Shapes) \
$(call LS_CPP,$(LOCAL_PATH),Box2D/Common) \
$(call LS_CPP,$(LOCAL_PATH),Box2D/Dynamics) \
$(call LS_CPP,$(LOCAL_PATH),Box2D/Dynamics/Contacts) \
$(call LS_CPP,$(LOCAL_PATH),Box2D/Dynamics/Joints) \
$(call LS_CPP,$(LOCAL_PATH),Box2D/Rope)
...
4. Then, write the Box2D module denion for a stac library. First, call the $
(CLEAR_VARS) script. This script has to be included before any module denion,
to remove any potenal change made by other modules and avoid any unwanted
side eects. Then, dene the following sengs:
Module name in LOCAL_MODULE: Module name is suxed with
_static to avoid a name clash with the shared version we are going
to dene right aer.
Module source les in LOCAL_SRC_FILES (using BOX2D_CPP dened
previously).
Porng exisng libraries to Android
[ 340 ]
Include le directory provided to clients in LOCAL_EXPORT_C_INCLUDES.
Include le used internally for module compilaon in LOCAL_C_INCLUDES.
Here, client include les and compilaon include les are the same (and are
oen the same in other libraries), so reuse LOCAL_EXPORT_C_INCLUDES,
dened previously:
...
include $(CLEAR_VARS)
LOCAL_MODULE:= box2d_static
LOCAL_SRC_FILES:= $(BOX2D_CPP)
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
LOCAL_C_INCLUDES := $(LOCAL_EXPORT_C_INCLUDES)
...
5. Finally, request Box2D module compilaon as a stac library, as follows:
...
include $(BUILD_STATIC_LIBRARY)
...
6. The same process can be repeated to build a shared library by selecng
a dierent module name and invoking $(BUILD_SHARED_LIBRARY), instead:
...
include $(CLEAR_VARS)
LOCAL_MODULE:= box2d_shared
LOCAL_SRC_FILES:= $(BOX2D_CPP)
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
LOCAL_C_INCLUDES := $(LOCAL_EXPORT_C_INCLUDES)
include $(BUILD_SHARED_LIBRARY)
Android.mk is provided in Chapter09/Library/
Box2D_v2.2.1_android.
7. Open DroidBlaster Android.mk and link against box2d_static, by appending it to
LOCAL_STATIC_LIBRARIES. Provide its directory with direcve import-module.
Remember that modules are found, thanks to the NDK_MODULE_PATH variable,
which points by default to ${ANDROID_NDK}/sources:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
Chapter 9
[ 341 ]
LS_CPP=$(subst $(1)/,,$(wildcard $(1)/*.cpp))
LOCAL_MODULE := droidblaster
LOCAL_SRC_FILES := $(call LS_CPP,$(LOCAL_PATH))
LOCAL_LDLIBS := -landroid -llog -lEGL -lGLESv1_CM -lOpenSLES
LOCAL_STATIC_LIBRARIES:=android_native_app_glue png boost_thread \
box2d_static
include $(BUILD_SHARED_LIBRARY)
$(call import-module,android/native_app_glue)
$(call import-module,libpng)
$(call import-module,boost)
$(call import-module,box2d)
8. Oponally, acvate include le resoluon for Box2D (as seen in Chapter 2, Creang,
Compiling, and Deploying Nave Projects). To do so, in Eclipse Project properes,
go to secon C/C++ General/Paths and Symbols and then the Includes tab, and
add Box2d directory ${env_var:ANDROID_NDK}/sources/box2d.
9. Launch DroidBlaster compilaon. Box2D gets compiled without errors.
Now, let's compile Irrlicht. Irrlicht is currently not supporng Android in its ocial branch.
The iPhone version, which implements an OpenGL ES driver, is sll on a separate branch
(and does not include Android support). However, it is possible to adapt this branch to
make it work with Android (let's say, in a few hours, for experienced programmers).
But there is another soluon: an Android fork iniated by developers from IOPixels (see
http://www.iopixels.com/). It is ready to compile with the NDK and takes advantage
of a few opmizaons. It works quite well, but is not as up-to-date as the iPhone branch.
10. Check out the Irrlicht for Android repository, from Gitorious. This repository can
be found at http://girotious.org/irrlichtandroid/irrlichtandroid.
To do so, install GIT (git package, on Linux) and execute the following command:
> git clone git://gitorious.org/irrlichtandroid/irrlichtandroid.git
The Irrlicht archive is provided with this book, in
directory Chapter09/Library.
11. The repository is on the disk. Move it to ${ANDROID_NDK}/sources and name
it irrlicht.
Porng exisng libraries to Android
[ 342 ]
12. The main directory contains a ready-to-use Android project that makes use of JNI
to communicate with Irrlicht on the nave side. Instead, we are going to adapt this
package to make use of NDK R5 nave acvies.
13. Go to ${ANDROID_NDK}/sources/irrlicht/project/jni and open
Android.mk.
14. Again, makele starts with a $(call my-dir) direcve, to save the current path,
and $(CLEAR_VARS), to erase any pre-exisng values:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
...
15. Aer that, we dene all the source les to compile. And there are lots of them!
Nothing needs to be changed, apart from the Android variable. Indeed, this port
of Irrlicht communicates with a Java applicaon through JNI and gives you some
places to append your own simulaon code.
But, what we want is to compile Irrlicht as a module. So, let's get rid of the useless
JNI binding and rely on the client applicaon for EGL inializaon. Update the
ANDROID direcve to keep only:
importgl.cpp, which gives the opon to bind dynamically to GLES runme.
CIrrDeviceAndroid.cpp, which is an empty stub. It delegates EGL
inializaon to the client. In our case, it is going to be performed by our
GraphicsService:
...
IRRMESHLOADER = CBSPMeshFileLoader.cpp CMD2MeshFileLoader.cpp ...
...
ANDROID = importgl.cpp CIrrDeviceAndroid.cpp
...
16. Then comes the module denion. Variable LOCAL_ARM_MODE can be removed, as
these sengs will be set globally, in our own applicaon, with the Application.
mk le. Of course, it is not forbidden to use a custom seng when needed:
...
LOCAL_MODULE := irrlicht
#LOCAL_ARM_MODE := arm
...
Chapter 9
[ 343 ]
17. Remove the -03 ag from LOCAL_CFLAGS, in the original le. This opon species
the level of opmizaon (here, aggressive). However, it can be set up at applicaon
level too.
ANDROID_NDK ag is specic to this Irrlicht port and is necessary to set up OpenGL.
It works in conjuncon with DISABLE_IMPORTGL, which disables the dynamic
loading of the OpenGL ES system library, at runme. This would be useful if we
wanted to let users choose the renderer at runme (for example, to allow
selecng GLES 2.0 renderer). In that case, the GLES 1 system library would
not be loaded uselessly:
...
LOCAL_CFLAGS := -DANDROID_NDK -DDISABLE_IMPORTGL
LOCAL_SRC_FILES := $(IRRLICHT_CPP)
...
18. Insert LOCAL_EXPORT_C_INCLUDES and LOCAL_C_INCLUDES, to indicate
which include directory to use for library compilaon and which one client
applicaons need. The same goes for linked libraries (LOCAL_EXPORT_LDLIBS
and LOCAL_LDLIBS). Keep only GLESv1_CM. The Irrlicht source folder, which
contains include les needed during Irrlicht compilaon only, is not appended
to the export ags:
...
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../include \
$(LOCAL_PATH)/libpng
LOCAL_C_INCLUDES := $(LOCAL_EXPORT_C_INCLUDES) $(LOCAL_PATH)
LOCAL_EXPORT_LDLIBS := -lGLESv1_CM -lz -ldl –llog
LOCAL_LDLIBS := $(LOCAL_EXPORT_LDLIBS)
...
19. Finally, modify Irrlicht to compile as a stac library. We could also compile it as a
shared library. But, because of Irrlicht's size aer compilaon, stac mode is advised.
In addion, it is going to be linked with DroidBlaster.so only:
...
include $(BUILD_STATIC_LIBRARY)
Android.mk is provided in Chapter09/Library/
irrlicht_android.
20. Now, we need to congure what parts of Irrlicht we want to keep and which part we
are not interested in. Indeed, size is an important maer with mobile development,
and the raw Irrlicht library is actually more than 30mb.
Porng exisng libraries to Android
[ 344 ]
As we are basically going to read OBJ meshes and PNG les and display them with
GLES 1.1, everything else can be deacvated. To do so, use #undef direcves in
${ANDROID_NDK}/irrlicht/project/include/IrrCompileConfig.h,
and keep only a few #define where needed:
Target Android with GLES1 only (no GLES 2 or soware renderer).
DroidBlaster requires only non-compressed les read from the le system:
#define _IRR_COMPILE_WITH_ANDROID_DEVICE_
#define _IRR_COMPILE_WITH_OGLES1_
#define _IRR_OGLES1_USE_EXTPOINTER_
#define _IRR_MATERIAL_MAX_TEXTURES_ 4
#define __IRR_COMPILE_WITH_MOUNT_ARCHIVE_LOADER_
Irrlicht embeds a few libraries of its own, such as, libpng, lijpeg,
and so on:
#define _IRR_COMPILE_WITH_OBJ_WRITER_
#define _IRR_COMPILE_WITH_OBJ_LOADER_
#define _IRR_COMPILE_WITH_PNG_LOADER_
#define _IRR_COMPILE_WITH_PNG_WRITER_
#define _IRR_COMPILE_WITH_LIBPNG_
#define _IRR_USE_NON_SYSTEM_LIB_PNG_
#define _IRR_COMPILE_WITH_ZLIB_
#define _IRR_USE_NON_SYSTEM_ZLIB_
#define _IRR_COMPILE_WITH_ZIP_ENCRYPTION_
#define _IRR_COMPILE_WITH_BZIP2_
#define _IRR_USE_NON_SYSTEM_BZLIB_
#define _IRR_COMPILE_WITH_LZMA_
Debug mode can be undef when a applicaon gets released:
#define _DEBUG
The modied IrrCompileConfig.h is provided with this book,
in directory Chapter09/Library/irrlicht_android.
21. Finally, append Irrlicht library to DroidBlaster. We need to remove libpng from
LOCAL_LDLIBS because, from now, DroidBlaster is going to use Irrlicht's libpng,
instead of the one we compiled (which is too recent for Irrlicht):
...
LOCAL_STATIC_LIBRARIES:=android_native_app_glue png boost_thread \
box2d_static irrlicht
Chapter 9
[ 345 ]
include $(BUILD_SHARED_LIBRARY)
$(call import-module,android/native_app_glue)
$(call import-module,libpng)
$(call import-module,boost)
$(call import-module,box2d)
$(call import-module,irrlicht/project/jni)
22. Oponally, acvate include le resoluon for Irrlicht (as done with Box2D
previously). The directory is ${env_var:ANDROID_NDK}/sources/irrlicht.
23. Launch compilaon and watch Irrlicht geng compiled. It may take quite some me!
What just happened?
We have compiled two open source libraries with the Android NDK, thus reusing the many
wheels already created by the community! We will see, in next chapter, how to develop code
with them. There are two main steps involved when porng a library to Android:
1. Adapng library code to Android if necessary.
2. Wring build scripts (that is, makeles) to compile code with the NDK toolchain.
The rst task is generally necessary for libraries accessing system libraries, such as Irrlicht with
OpenGL ES. It is obviously the hardest and most non-trivial task. In that case, always consider:
Making sure required libraries exist. If not, port them before. For instance, the main
Irrlicht branch cannot be used on Android because renderers are only DirectX and
OpenGL (not ES). Only the iPhone branch provides a GLES renderer.
Looking for the main conguraon include le. One is oen provided (such as
IrrCompileConfig.h for Irrlicht) and is a good place to tweak enabled/disabled
features or remove unwanted dependencies.
Giving aenon to system-related macros (that is, #ifdef _LINUX ...), which
are one of the rst places to change in code. Generally, one will need to dene
macros such as _ANDROID_ and insert them where appropriate.
Commenng non-essenal code, at least to check if the library can compile and its
core features work.
The second task, building scripts, is easier, although tedious. You should choose building the
import module dynamically, when compiling your applicaon, as opposed to a prebuilt library,
like we did with Boost. Indeed, on-demand compilaon allows tweaking compilaon ags on
all included libraries (like opmizaon ags or ARM mode) from your main Application.mk
project le.
Porng exisng libraries to Android
[ 346 ]
Prebuilt libraries are only interesng to redistribute binaries, without delivering code, or
to use a custom build system. In the laer case, the NDK toolchain is used in the so-called
standalone mode (that is, Do It Yourself mode!) detailed in the Android NDK documentaon.
But, the default ndk-build command is, of course, considered a beer pracce, to make
future evoluons simpler.
Libraries are produced in <PROJECT_DIR>/libs. Intermediate binary les are available in
<PROJECT_DIR>/obj. Module size in the laer place is quite impressive. That would not
be viable if NDK toolchain was not stripping them when producing nal APK. Stripping is the
process of discarding unnecessary symbols from binaries. Combined with stac linking, this
reduces the size of DroidBlaster binaries from 60 MB to just 3 MB.
GCC optimization levels
There are 5 main opmizaon levels in GCC:
1. -O0: It disables any opmizaon. This is automacally set by the NDK when
APP_OPTIM is set to debug.
2. -O1: It allows basic opmizaons without increasing compilaon me too much.
These opmizaons do not require any speed-space tradeos, which mean that
they produce faster code without increasing executable size.
3. -O2: It allows advanced opmizaon (including -O1), but at the expense of
compilaon me. Like –O1, these opmizaons do not require speed-space
tradeos. This is the default level when APP_OPTIM is set to the release opon,
when releasing an applicaon.
4. -O3: To perform aggressive opmizaons (including -O2), which can increase
executable size, such as funcon inlining. This is generally protable, but
somemes, counterproducve (for example, increasing memory usage can also
increase cache misses).
5. -Os: To opmize compiled code size (a subset of –O2) before speed.
Although -O2 is generally the way to go for release mode, -O3 should also be considered
for performance crical code. -0 ags being just shortcuts for the various GCC opmizaon
ags, enabling –O2 and with addional ne-grain ags (for example, -finline-
functions) is an opon. Anyway, the best way to nd the best choice is sll performing
benchmarking! To get more informaon about the numerous GCC opmizaon opons,
have a look at http://gcc.gnu.org/.
Mastering Makeles
Android makeles are an essenal piece of the NDK building process. Thus, it is important
to understand the way they work, to build and manage a project properly.
Chapter 9
[ 347 ]
Makele variables
Compilaon sengs are dened though a set of predened NDK variables. We have already
seen the three most important ones: LOCAL_PATH, LOCAL_MODULE, and LOCAL_SRC_FILES.
But many others exist. We can dierenate four types of variables, each with a dierent prex:
LOCAL_, APP_, NDK_, and PRIVATE_.
APP_ variables refer to applicaon-wide opons and are set in Application.mk
LOCAL_ variables are dedicated to individual module compilaon and are dened
in Android.mk les
NDK_ are internal variables that usually refer to environment variables (for example,
NDK_ROOT, NDK_APP_CFLAGS or NDK_APP_CPPFLAGS)
PRIVATE_ prexed variables are for NDK internal use only
Here is an almost exhausve list:
LOCAL_PATH To specify the source les, root locaon. Must be
dened before include $(CLEAR_VARS).
LOCAL_MODULE To dene module name.
LOCAL_MODULE_FILENAME
To override default name of the compiled module,
that is,
lib<module name>.so for shared libraries
lib<module name>.a for stac libraries.
No custom le extensions can be specied, so that .so
or.a remains appended.
LOCAL_SRC_FILES To dene source les to compile, each separated by a
space and relave to LOCAL_PATH.
LOCAL_C_INCLUDES
To specify header le directories for both C and
C++ languages. The directory can be relave to the
${ANDROID_NDK} directory, but unless you need
to include a specic NDK le, you are advised to use
absolute path (which can be built from Makele
variables such as $(LOCAL_PATH)).
LOCAL_CPP_EXTENSION
To change default C++ le extension that is.cpp (for
example, cc or cxx). Extension is necessary for GCC to
discriminate between les, according to their language.
LOCAL_CFLAGS,
LOCAL_CPPFLAGS,
LOCAL_LDLIBS
To specify any opons, ags, or macro denions, for
compilaon and linking. The rst one works for both
C and C++, the second one is for C++ only, and the last
one is for the linker.
Porng exisng libraries to Android
[ 348 ]
LOCAL_SHARED_LIBRARIES,
LOCAL_STATIC_LIBRARIES
To declare a dependency with other modules
(not system libraries), shared and stac modules,
respecvely.
LOCAL_ARM_MODE,
LOCAL_ARM_NEON,
LOCAL_DISABLE_NO_
EXECUTE,
LOCAL_FILTER_ASM
Advanced variables dealings with processors and
assembler/binary code generaon. They are not
necessary for most programs.
LOCAL_EXPORT_CFLAGS,
LOCAL_EXPORT_CPPFLAGS,
LOCAL_EXPORT_LDLIBS
To dene addional opons or ags in import modules
that should be appended to clients opons. For
example, if a module A denes
LOCAL_EXPORT_LDLIBS := -llog
because it needs an Android logging module, then
a module B that depends on A will be automacally
linked to –llog.
LOCAL_EXPORT_ variables are not used when
compiling the module that exports them. If required,
they also need to be specied in their LOCAL
counterpart.
Makele Instructions
Although these advanced features are marginally needed, Makele is a real language with
programming instrucons and funcons. First, know that makeles can be broken down into
several sub-makeles, included with the instrucon include.
Variable inializaon comes in two avours:
Simple aectaon: This expands variables at the me that they are inialised.
Recursive aectaon: This re-evaluates the aected expression, each me it is
called.
The following condional and loop instrucons are available: ifdef/endif, ifeq/endif,
ifndef/endif, for…in/do/done. For example, to display a message only when a variable
is dened, do:
ifdef my_var
# Do something...
endif
Chapter 9
[ 349 ]
More advanced stu, such as funconal if, and, or, are at your disposal, but are rarely
used. Make also provides some useful built-in funcons:
$(info <message>)
Allows prinng messages to the standard output. This is the
most essenal tool when wring makeles! Variables inside
informaon messages are allowed.
$(warning <message>),
$(error <message>)
Allows prinng a warning or a fatal error that stops
compilaon. These messages can be parsed by Eclipse.
$(foreach <variable>,
<list>, <operation>)
To perform an operaon on a list of variables. Each element
of the list is expanded in the rst argument variable, before
the operaon is applied on it.
$(shell <command>)
To execute a command outside of Make. This brings all the
power of Unix Shell into Makeles but is heavily system-
dependent. Avoid it if possible.
$(wildcard <pattern>) Select les and directory names according to a paern.
$(call <function>)
Allows evaluang a funcon or macro. One macro we
have seen is my-dir, which returns the directory path
of the last executed Makele. This is why LOCAL_PATH
:= $(call my-dir) is systemacally wrien at the
beginning of each Android.mk le, to save in the current
Makele directory.
With the call direcve, custom funcons can easily be wrien. These funcons look
somewhat similar to recursively aected variables, except that arguments can be dened:
$(1) for rst argument, $(2) for second argument, and so on. A call to a funcon can be
performed in a single line:
my_function=$(<do_something> ${1},${2})
$(call my_function,myparam)
Strings and les manipulaon funcons are available too:
$(join <str1>, <str2>) Concatenates two strings.
$(subst <from>,
<replacement>,<string>),
$(patsubst <pattern>,
<replacement>,<string>)
Replaces each occurrence of a substring by another.
The second one is more powerful, because it allows
using paerns (which must start with "%").
Porng exisng libraries to Android
[ 350 ]
$(filter <patterns>,
<text>)
$(filter-out <patterns>,
<text>)
Filter strings from a text matching paerns. This is
useful for ltering les. For example, the following
line lters any C le:
$(lter %.c, $(my_source_list))
$(strip <string>) Removes any unnecessary whitespace.
$(addprefix
<prefix>,<list>),
$(addsuffix <suffix>,
<list>)
Append a prex and sux, respecvely, to each
element of the list, each element being separated by
a space.
$(basename <path1>,
<path2>, ...)
Returns a string from which le extensions are
removed.
$(dir <path1>, <path2>),
$(notdir <path1>,
<path2>)
Extracts respecvely the directory and the lename
in a path, respecvely
$(realpath <path1>,
<path2>, ...),
$(abspath <path1>,
<path2>, ...)
Return both canonical paths of each path argument,
except that the second one does not evaluate
symbolic links.
This is just really an overview of what Makeles are capable of. For more informaon, refer
to the full Makele documentaon, available at http://www.gnu.org/software/make/
manual/make.html. If you are allergic to Makeles, have a look at CMake. CMake is a
simplied Make system, already building many open source libraries on the market. A port
of CMake on Android is available at http://code.google.com/p/android-cmake.
Have a go hero – mastering Makeles
We can play in a variety of ways with Makeles:
Try the aectaon operator. For example, write down the following piece of code
which uses the = operator in your Android.mk le:
my_value := Android
my_message := I am an $(my_value)
$(info $(my_message))
my_value := Android eating an apple
$(info $(my_message))
Chapter 9
[ 351 ]
Watch the result when launching compilaon. Then do the same using =.Print
current opmizaon mode. Use APP_OPTIM and internal variable, NDK_APP_
CFLAGS, and observe the dierence between release and debug modes:
$(info Optimization level: $(APP_OPTIM) $(NDK_APP_CFLAGS))
Check that variables are properly dened, for example:
ifndef LOCAL_PATH
$(error What a terrible failure! LOCAL_PATH not defined...)
endif
Try to use the foreach instrucon to print the list of les and directories
inside the project's root directory and its jni folder (and make sure to use
recursive aectaon):
ls = $(wildcard $(var_dir))
dir_list := . ./jni
files := $(foreach var_dir, $(dir_list), $(ls))
Try to create a macro to log a message to the standard output and its me:
log=$(info $(shell date +'%D %R'): $(1))
$(call log,My message)
Finally, test the my-dir macro behaviour, to understand why LOCAL_PATH :=
$(call my-dir) is systemacally wrien at the beginning of each Android.mk:
$(info MY_DIR =$(call my-dir))
include $(CLEAR_VARS)
$(info MY_DIR =$(call my-dir))
Summary
The present chapter introduced a fundamental aspect of the NDK: portability. Thanks to the
recent improvements in the building toolchain, the Android NDK can now take advantage
of the vast C/C++ ecosystem. It unlocks the door of a producve environment where code is
shared with other plaorms with the aim of creang new cung-edge applicaons eciently.
More specically, we learnt how to enable, include, and compile STL and Boost and use them
in our own code. We also enabled excepons and RTTI, and selected the appropriate STL
implementaon. Then, we ported Open Source libraries to Android. Finally, we discovered
how to write makeles with advanced instrucons and features.
In the next chapter, these foundaons will allow us to integrate a collision system and to
develop a new 3D graphics system.
10
Towards Professional Gaming
We have seen in the previous chapter how to port third-party libraries to
Android. More specically, we have compiled two of them: Box2D and Irrlicht.
In this chapter, we are going one step further by implemenng them concretely
in our sample applicaon DroidBlaster. This is the outcome of all the eort
made and all the stu learned unl now. This chapter highlights the path
toward the concrete realizaon of your own applicaon. Of course, there
is sll a very long way to go… but if the slope is steep, the road is straight!
By the end of this chapter, you should be able to do the following:
Simulate physics and handle collisions with Box2D
Display 3D graphics with Irrlicht
Simulating physics with Box2D
We have handled collisions or physics and with good cause! This is a rather complex subject,
involving maths, numerical integraon, soware opmizaon, and so on. To answer these
dicules, physics engine have been invented on the model of 3D engine, and Box2D is one
of them. This open source engine, iniated by Erin Cao in 2006, can simulate rigid body
movements and collisions in a 2D environment. Bodies are the essenal element of Box2D
and are characterized by:
A geometrical shape (polygons, circles, and so on)
Physics properes (such as density, fricon, restuon, and so on)
Movement constraints and joints (to link bodies together and restrict
their movement)
Towards Professional Gaming
[ 354 ]
All these bodies are orchestrated inside a World, which steps simulaon according to me.
In previous chapters, we have created GraphicsService, a SoundService, and
InputService. This me, let's implement PhysicsService with Box2D.
Project DroidBlaster_Part9-3 can be used as a starng point for
this part. The resulng project is provided with this book under
the name DroidBlaster_Part10-Box2D.
Time for action – simulating physics with Box2D
Let's encapsulate Box2D simulaon in a dedicated service rst:
1. First, create jni/PhysicsObject.hpp and insert Box2D main include le. Class
PhysicsObject exposes a locaon and a collision ag publicly. It holds various
Box2D properes dening a physical enty:
A reusable body denion to dene how to simulate a body
(stac, with rotaons).
A body to represent a body instance in the simulated world.
A shape to detect collisions. Here use a circle shape.
A xture to bind a shape to a body and dene a few physics properes.
The class PhysicsObject is set up with initialize() and refreshed with
update() aer each simulaon step. Method createTarget() will help us create
a joint for the ship.
#ifndef PACKT_PHYSICSOBJECT_HPP
#define PACKT_PHYSICSOBJECT_HPP
#include "PhysicsTarget.hpp"
#include "Types.hpp"
#include <boost/smart_ptr.hpp>
#include <Box2D/Box2D.h>
#include <vector>
namespace packt {
class PhysicsObject {
public:
typedef boost::shared_ptr<PhysicsObject> ptr;
typedef std::vector<ptr> vec; typedef vec::iterator vec_it;
public:
PhysicsObject(uint16 pCategory, uint16 pMask,
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
Chapter 10
[ 355 ]
int32_t pDiameter, float pRestitution, b2World* pWorld);
PhysicsTarget::ptr createTarget(float pFactor);
void initialize(float pX, float pY,
float pVelocityX, float pVelocityY);
void update();
bool mCollide;
Location mLocation;
private:
b2World* mWorld;
b2BodyDef mBodyDef; b2Body* mBodyObj;
b2CircleShape mShapeDef; b2FixtureDef mFixtureDef;
};
}
#endif
2. Implement jni/PhysicsObject.cpp constructor to inialize all Box2D properes.
The body denion describes a dynamic body (as opposed to stac), awake (that
is, acvely simulated by Box2D), and which cannot rotate (a property especially
important for polygon shapes, meaning that it is always poinng upward).
Also note how we save a PhysicsObject self reference in userData eld, in order
to access it later inside Box2D callbacks
3. Dene body shape, which we approximate to a box. Box2D requires half dimension,
from object's center to its borders.
#include "PhysicsObject.hpp"
#include "Log.hpp"
namespace packt {
PhysicsObject::PhysicsObject(uint16 pCategory, uint16 pMask,
int32_t pDiameter, float pRestitution, b2World* pWorld) :
mLocation(), mCollide(false), mWorld(pWorld),
mBodyDef(), mBodyObj(NULL), mShapeDef(), mFixtureDef() {
mBodyDef.type = b2_dynamicBody;
mBodyDef.userData = this;
mBodyDef.awake = true;
mBodyDef.fixedRotation = true;
mShapeDef.m_p = b2Vec2_zero;
mShapeDef.m_radius = pDiameter / (2.0f * SCALE_FACTOR);
...
Towards Professional Gaming
[ 356 ]
4. Body xture is the glue which brings together body denion, shape, and also physical
properes. We also use it to set body category and mask. This allows us to lter
collisions between objects according to their category (for instance, asteroids must
collide with the ship but not between themselves). There is one category per bit.
Finally, eecvely instanate your body inside the Box2D physical world:
...
mFixtureDef.shape = &mShapeDef;
mFixtureDef.density = 1.0f;
mFixtureDef.friction = 0.0f;
mFixtureDef.restitution = pRestitution;
mFixtureDef.filter.categoryBits = pCategory;
mFixtureDef.filter.maskBits = pMask;
mFixtureDef.userData = this;
mBodyObj = mWorld->CreateBody(&mBodyDef);
mBodyObj->CreateFixture(&mFixtureDef);
mBodyObj->SetUserData(this);
}
...
5. Then take care of mouse joint creaon in createTarget().
When PhysicsObject is inialized, coordinates are converted from DroidBlaster
referenal to Box2D one. Indeed, Box2D performs beer with smaller coordinates.
When Box2D has nished simulang, each PhysicsObject instance converts
coordinates computed by Box2D back into DroidBlaster coordinates referenal:
...
PhysicsTarget::ptr PhysicsObject::createTarget(float pFactor)
{
return PhysicsTarget::ptr(
new PhysicsTarget(mWorld, mBodyObj, mLocation,
pFactor));
}
void PhysicsObject::initialize(float pX, float pY,
float pVelocityX, float pVelocityY) {
mLocation.setPosition(pX, pY);
b2Vec2 lPosition(pX / SCALE_FACTOR, pY / SCALE_FACTOR);
mBodyObj->SetTransform(lPosition, 0.0f);
mBodyObj->SetLinearVelocity(b2Vec2(pVelocityX,
pVelocityY));
}
Chapter 10
[ 357 ]
void PhysicsObject::update() {
mLocation.setPosition(
mBodyObj->GetPosition().x * SCALE_FACTOR,
mBodyObj->GetPosition().y * SCALE_FACTOR);
}
}
6. Now, create jni/PhysicsService.hpp header and again insert the Box2D
include le. Make PhysicsService inherit from b2ContactListener. A contact
listener gets noed about new collisions each me the simulaon is updated. Our
PhysicsService inherits one of its method named BeginContact().
Dene constants and member variables. Iteraon constants determine the
simulaon accuracy. Variable mWorld represents the whole Box2D simulaon
which contains all the physical bodies we are going to create:
#ifndef PACKT_PHYSICSSERVICE_HPP
#define PACKT_PHYSICSSERVICE_HPP
#include "PhysicsObject.hpp"
#include "TimeService.hpp"
#include "Types.hpp"
#include <Box2D/Box2D.h>
namespace packt {
class PhysicsService : private b2ContactListener {
public:
PhysicsService(TimeService* pTimeService);
status update();
PhysicsObject::ptr registerEntity(uint16 pCategory,
uint16 pMask, int32_t pDiameter, float pRestitution);
private:
void BeginContact(b2Contact* pContact);
private:
TimeService* mTimeService;
PhysicsObject::vec mColliders;
b2World mWorld;
static const int32_t VELOCITY_ITER = 6;
static const int32_t POSITION_ITER = 2;
};
}
#endif
Towards Professional Gaming
[ 358 ]
7. In the jni/PhysicsService.cpp source le, write PhysicsService constructor.
Inialize the world, seng the rst parameter to a zero vector (type b2vec). This
vector represents the gravity force, which is not necessary in DroidBlaster. Finally,
register the service as a listener of contact/collision event. This way, each me
simulaon is stepped, PhysicsService gets noed through callbacks.
Destroy Box2D resources in the destructor. Box2D uses its own internal (de)allocator.
Also implement registerEntity() to encapsulate physics object creaon:
#include "PhysicsService.hpp"
#include "Log.hpp"
namespace packt {
PhysicsService::PhysicsService(TimeService* pTimeService) :
mTimeService(pTimeService),
mColliders(), mWorld(b2Vec2_zero) {
mWorld.SetContactListener(this);
}
PhysicsObject::ptr PhysicsService::registerEntity(
uint16 pCategory, uint16 pMask, int32_t pDiameter,
float pRestitution) {
PhysicsObject::ptr lCollider(new PhysicsObject(pCategory,
pMask, pDiameter, pRestitution, &mWorld));
mColliders.push_back(lCollider);
return mColliders.back();
}
...
8. Write the update() method. First, it clears collision ags buered in
BeginContact() during previous iteraon. Then simulaon is performed by calling
Step() with a me period and iteraons constants dene simulaon accuracy. Finally,
PhysicsObject is updated (that is, locaon extracted from Box2D into our own
Location object) according to simulaon results. Box2D is going to handle mainly
collisions and simple movements. So xing velocity and posion iteraons to 6 and 2,
respecvely, is sucient.
...
status PhysicsService::update() {
PhysicsObject::vec_it iCollider = mColliders.begin();
for (; iCollider < mColliders.end() ; ++iCollider) {
(*iCollider)->mCollide = false;
}
Chapter 10
[ 359 ]
// Updates simulation.
float lTimeStep = mTimeService->elapsed();
mWorld.Step(lTimeStep, VELOCITY_ITER, POSITION_ITER);
// Caches the new state.
iCollider = mColliders.begin();
for (; iCollider < mColliders.end() ; ++iCollider) {
(*iCollider)->update();
}
return STATUS_OK;
}
...
9. The method BeginContact() is a callback inherited by b2ContactListener
to nofy about new collisions between bodies, two at a me (named A and B).
Event informaon is stored in a b2contact structure, which contains various
properes, such as fricon and restuon, and the two bodies involved through
their xture, which themselves contain a reference to our own PhysicsObject
(the UserData property set in GraphicsObject). We can use this link to switch
the PhysicsObject collision ag when Box2D detects one:
...
void PhysicsService::BeginContact(b2Contact* pContact) {
void* lUserDataA = pContact->GetFixtureA()->GetUserData();
if (lUserDataA != NULL) {
((PhysicsObject*)(lUserDataA))->mCollide = true;
}
void* lUserDataB = pContact->GetFixtureB()->GetUserData();
if (lUserDataB != NULL) {
((PhysicsObject*)(lUserDataB))->mCollide = true;
}
}
}
10. Finally, create jni/PhysicsTarget.hpp to encapsulate Box2D mouse joints.
The ship will follow the direcon specied in setTarget(). To do so, we need a
mulplier (mFactor) to simulate a target point from the input service output vector.
Mouse joints are usually good to simulate dragging eects or for
test purposes. They are easy to use but implemenng a precise
behavior with them is dicult.
#ifndef PACKT_PHYSICSTARGET_HPP
#define PACKT_PHYSICSTARGET_HPP
Towards Professional Gaming
[ 360 ]
#include "Types.hpp"
#include <boost/smart_ptr.hpp>
#include <Box2D/Box2D.h>
namespace packt {
class PhysicsTarget {
public:
typedef boost::shared_ptr<PhysicsTarget> ptr;
public:
PhysicsTarget(b2World* pWorld, b2Body* pBodyObj,
Location& pTarget, float pFactor);
void setTarget(float pX, float pY);
private:
b2MouseJoint* mMouseJoint;
float mFactor; Location& mTarget;
};
}
#endif
11. The source counterpart is jni/PhysicsTarget.cpp to encapsulate a
Box2D mouse joint. The ship will follow the direcon specied in setTarget()
each frame.
#include "PhysicsTarget.hpp"
#include "Log.hpp"
namespace packt {
PhysicsTarget::PhysicsTarget(b2World* pWorld, b2Body* pBodyObj,
Location& pTarget, float pFactor):
mFactor(pFactor), mTarget(pTarget) {
b2BodyDef lEmptyBodyDef;
b2Body* lEmptyBody = pWorld->CreateBody(&lEmptyBodyDef);
b2MouseJointDef lMouseJointDef;
lMouseJointDef.bodyA = lEmptyBody;
lMouseJointDef.bodyB = pBodyObj;
lMouseJointDef.target = b2Vec2(0.0f, 0.0f);
lMouseJointDef.maxForce = 50.0f * pBodyObj->GetMass();
lMouseJointDef.dampingRatio = 1.0f;
lMouseJointDef.frequencyHz = 3.5f;
mMouseJoint = (b2MouseJoint*)
pWorld->CreateJoint(&lMouseJointDef);
}
Chapter 10
[ 361 ]
void PhysicsTarget::setTarget(float pX, float pY) {
b2Vec2 lTarget((mTarget.mPosX + pX * mFactor) / SCALE_FACTOR,
(mTarget.mPosY + pY * mFactor) / SCALE_FACTOR);
mMouseJoint->SetTarget(lTarget);
}
}
12. Finally, add the PhysicsService to jni/Context.hpp like all the other
services created in previous chapters.
We can now go back to our asteroids and simulate them with our new
physics service.
13. In jni/Asteroid.hpp, replace locaon and speed by PhysicsObject instance:
...
#include "PhysicsService.hpp"
#include "PhysicsObject.hpp"
...
namespace dbs {
class Asteroid {
...
private:
...
packt::GraphicsSprite* mSprite;
packt::PhysicsObject::ptr mPhysics;
};
}
14. Makes use of this new physics object in jni/Asteroid.cpp source le. Physics
properes are registered with a category and mask. Here, Asteroids are declared
as belonging to category 1 (0X1 in hexadecimal notaon) and only bodies in
group 2 (0X2 in hexadecimal) are considered when evaluang collisions.
To spawn an asteroid, replace speed with the noon of velocity (expressed in m/s).
Because asteroid direcon will change when a collision occurs, asteroids are spawn
when they go outside the main area in update():
#include "Asteroid.hpp"
#include "Log.hpp"
namespace dbs {
Asteroid::Asteroid(packt::Context* pContext) :
mTimeService(pContext->mTimeService),
mGraphicsService(pContext->mGraphicsService) {
mPhysics = pContext->mPhysicsService->registerEntity(
Towards Professional Gaming
[ 362 ]
0X1, 0x2, 64, 1.0f);
mSprite = pContext->mGraphicsService->registerSprite(
mGraphicsService->registerTexture(
"/sdcard/droidblaster/asteroid.png"),
64, 64, &mPhysics->mLocation);
}
void Asteroid::spawn() {
const float MIN_VELOCITY = 1.0f, VELOCITY_RANGE=19.0f;
const float MIN_ANIM_SPEED = 8.0f, ANIM_SPEED_RANGE=16.0f;
float lVelocity = -(RAND(VELOCITY_RANGE) + MIN_VELOCITY);
float lPosX = RAND(mGraphicsService->getWidth());
float lPosY = RAND(mGraphicsService->getHeight())
+ mGraphicsService->getHeight();
mPhysics->initialize(lPosX, lPosY, 0.0f, lVelocity);
float lAnimSpeed = MIN_ANIM_SPEED + RAND(ANIM_SPEED_RANGE);
mSprite->setAnimation(8, -1, lAnimSpeed, true);
}
void Asteroid::update() {
if ((mPhysics->mLocation.mPosX < 0.0f) ||
(mPhysics->mLocation.mPosX > mGraphicsService->getWidth())||
(mPhysics->mLocation.mPosY < 0.0f) ||
(mPhysics->mLocation.mPosY > mGraphicsService->getHeight()*2)){
spawn();
}
}
}
15. Modify the jni/Ship.hpp header le in the same way as asteroids:
...
#include "PhysicsService.hpp"
#include "PhysicsObject.hpp"
#include "PhysicsTarget.hpp"
...
namespace dbs {
class Ship {
...
Chapter 10
[ 363 ]
private:
...
packt::GraphicsSprite* mSprite;
packt::PhysicsObject::ptr mPhysics;
packt::PhysicsTarget::ptr mTarget;
};
}
16. Rewrite jni/Ship.cpp with the new PhysicsObject. Ship is added to category
2 and is marked as colliding with category 1 only (that is, asteroids). Velocity and
movement is enrely managed by Box2D. We can now check in update() if an
asteroid collided:
#include "Ship.hpp"
#include "Log.hpp"
namespace dbs {
Ship::Ship(packt::Context* pContext) :
mInputService(pContext->mInputService),
mGraphicsService(pContext->mGraphicsService),
mTimeService(pContext->mTimeService) {
mPhysics = pContext->mPhysicsService->registerEntity(
0x2, 0x1, 64, 0.0f);
mTarget = mPhysics->createTarget(50.0f);
mSprite = pContext->mGraphicsService->registerSprite(
mGraphicsService->registerTexture(
"/sdcard/droidblaster/ship.png"),
64, 64, &mPhysics->mLocation);
mInputService->setRefPoint(&mPhysics->mLocation);
}
void Ship::spawn() {
mSprite->setAnimation(0, 8, 8.0f, true);
mPhysics->initialize(mGraphicsService->getWidth() * 1 / 2,
mGraphicsService->getHeight() * 1 / 4, 0.0f, 0.0f);
}
void Ship::update() {
mTarget->setTarget(mInputService->getHorizontal(),
mInputService->getVertical());
if (mPhysics->mCollide) {
packt::Log::info("Ship has been touched");
}
}
}
Towards Professional Gaming
[ 364 ]
Finally, let's instanate and run our physics service.
17. Modify jni/DroidBlaster.hpp to hold PhysicsService instance:
...
#include "PhysicsService.hpp"
...
namespace dbs {
class DroidBlaster : public packt::ActivityHandler {
...
private:
packt::GraphicsService* mGraphicsService;
packt::InputService* mInputService;
packt::PhysicsService* mPhysicsService;
packt::SoundService* mSoundService;
...
};
}
18. Update PhysicsService each me the game is stepped:
namespace dbs {
...
packt::status DroidBlaster::onStep()
{
...
if (mInputService->update() != packt::STATUS_OK) {
return packt::STATUS_KO;
}
if (mPhysicsService->update() != packt::STATUS_OK) {
return packt::STATUS_KO;
}
return packt::STATUS_OK;
}
...
}
19. Finally, instanate PhysicsService in the applicaon's main method:
...
#include "PhysicsService.hpp"
...
void android_main(android_app* pApplication) {
...
Chapter 10
[ 365 ]
packt::PhysicsService lPhysicsService(&lTimeService);
packt::SoundService lSoundService(pApplication);
packt::Context lContext = { &lGraphicsService, &lInputService,
&lPhysicsService, &lSoundService, &lTimeService };
...
}
What just happened?
We have created a physical simulaon using Box2D physics engine. We have seen how to do
the following:
Dene a physical representaon of enes (ships and asteroids)
Step a simulaon and detect/lter collisions between enes
Extracted simulaon state (that is, coordinates) to feed graphics representaon
The central point of access in Box2D is b2World, which stores a collecon of bodies to
simulate. A Box2D body is composed of the following:
b2BodyDef: This denes the body type (b2_staticBody, b2_dynamicBody, and
so on) and inial properes like its posion, angle (in radians), and so on.
b2Shape: This is used for collision detecon and to derive body mass from its
density and can be a b2PolygonShape, b2CircleShape, and so on
b2FixtureDef: This links together a body shape, a body denion, and its physical
properes, such as density
b2Body: This is a body instance in the world (that is, on per game object), created
from a body denion, a shape, and a xture
Bodies are characterized by a few physical properes:
Shape: This represents a circle in DroidBlaster, although a polygon or box could
also be used.
Density: This is in kg/m2, to compute body mass depending on its shape and size.
Value should be greater or equal to 0.0. A bowling ball has a bigger density than a
soccer ball.
Fricon: This property shows how much a body slides on another (for example, a car
on a road or on an icy path). Values are typically in the range 0.0 to 1.0, where 0.0
implies no fricon and 1.0 means strong fricon.
Restuon: This property shows how much a body reacts to a collision, for example,
a bouncing ball. Value 0.0 means no restuon and 1.0 full restuon.
Towards Professional Gaming
[ 366 ]
When running, bodies are subject to the following:
Forces: This make bodies move linearly.
Torques: This represents rotaonal force applied on a body.
Damping: This is similar to fricon but it does not occur only when a body is in contact
with another. It can be considered as the eect of air fricon slowing down a body.
Box2D is tuned for worlds containing objects at a scale from 0.1 to 10 (unit in meters). When
used outside this range, again numerical approximaon can make simulaon inaccurate.
Thus, it is very necessary to scale coordinates from the Box2D referenal, where object
should to be kept in the (rough) range [0.1, 10] and, to the game or directly to the graphics
referenal. This is where SCALE_FACTOR is used for coordinate transformaon.
Box2D memory management
Box2D uses its own allocators to opmize memory management. So to create
and destroy Box2D objects, one needs to systemacally use the provided
factory methods (CreateX(), DestroyX()). Most of the me, Box2D
will manage memory automacally for you. When an object is destroyed, all
related child objects get destroyed (for instance, the bodies are destroyed
when the world is destroyed). But if you need to get rid of your objects earlier,
and thus manually, then always destroy them.
More on collision detection
Several ways of detecng and handling collisions exist in Box2D. The most basic one consists
in checking all contacts stored in the world or in a body aer they are updated. But this can
result in missed contacts that happen surrepously during Box2D internal iteraons.
A beer way we have seen to detect contacts is the b2ContactListener, which can be
registered on the world object. Four callbacks can be overridden:
BeginContact(b2Contact): This is to detect when two bodies enter in collision.
EndContact(b2Contact): This is the counterpart of BeginContact(), which
indicates when bodies are not in collision any more. A call to BeginContact() is
always followed by a matching EndContact().
PreSolve(b2Contact, b2Manifold): This is called aer a collision is detected
but before collision resoluon, that is, before impulse resulng from the collision
is computed. The b2Manifold structure holds informaon about contact points,
normals, and so on in a single place.
PostSolve(b2Contact, b2ContactImpulse): This is called aer actual
impulse (that is, physical reacon) has been computed by Box2D.
Chapter 10
[ 367 ]
The rst two callbacks are interesng to trigger game logic (for example, enty destrucon).
The last two are interesng to alter physics simulaon (more specically to ignore some
collisions by disabling a contact) while it is being computed or to get more accurate details
about it. For instance, use PreSolve() to create a one-sided plaorm to which an enty
collides only when it falls from above (not when it jumps from below). Use PostSolve()
to detect collision strength and calculate damages accordingly.
Methods PreSolve() and PostSolve() can be called several mes between
BeginContact() and EndContact(), which can be called themselves from zero to
several mes during one world update. A contact can begin during one simulaon step
and terminate several steps aer. In that case, event solving callbacks will be occurring
connuously during in-between steps. As many collisions can occur while stepping
simulaon, callbacks can be called lot of mes and should be as ecient as possible.
When analyzing collisions inside BeginContact() callback, we have buered a collision
ag. This is necessary because Box2D reuses the b2Contact parameter passed when a
callback is triggered. In addion, as these callbacks are called while simulaon is computed,
physics bodies cannot be destroyed at that instance but only aer simulaon stepping is
over. Thus, it is highly advised to copy any informaon gathered there for post-processing
(for example, to destroy enes).
Collision modes
I would like to point out that Box2D oers a so-called bullet mode that can be acvated
on a body denion using corresponding Boolean member:
mBodyDef.bullet = true;
This mode is necessary for fast moving objects like bullets! By default, Box2D uses Discrete
Collision Detecon, which considers bodies at their nal posion for collision detecon,
missing any body located between inial and nal posions. But for a fast moving body, the
whole path followed should be considered. This is more formally called Connuous Collision
Detecon. Obviously, CCD is expensive and should be used with parsimony:
Towards Professional Gaming
[ 368 ]
We somemes want to detect when bodies overlap without generang collisions (like a
car reaching the nish line): this is called a sensor. A sensor can be easily set by seng
isSensor Boolean member to true in the xture:
mFixtureDef.isSensor = true;
A sensor can be queried with a listener through BeginContact() and EndContact()
or by using IsTouching() shortcut on a b2Contact class.
Collision ltering
Another important aspect of collision is about... not colliding! Or more precisely about
ltering collisions… A kind of ltering can be performed in PreSolve() by disabling
contacts. This is the most exible and powerful soluon but also the most complex.
But as we have seen it, ltering can be performed in a more simple way by using categories
and masks technique. Each body is assigned one or more category (each being represented
by one bit in a short integer, the categoryBits member) and a mask describing categories
of body they can collide with (each ltered category being represented by a bit set to 0, the
maskBits member):
Body A
Category
16
0
Category Category Category Category
1
234
0 1 0
1
Category
16
0
Mask Mask Mask Mask
1
234
0
1
01
Category
16
0
Category Category Category Category
0
234
1 0 0
1
Body B
In the preceding gure, Body A is in category 1 and 3 and collide with bodies in categories
2 and 4, which is the case for this poor body B unless its mask lters collision with body A
categories (that is, 1 and 3). In other words, both the bodies A and B must agree to collide!
Chapter 10
[ 369 ]
Box2D also has a noon of collision groups. A body has a collision group set to any
of the following:
Posive integer: This means others bodies with the same collision group value
can collide
Negave integer: This means others bodies with the same collision group value
are ltered
This could have been a soluon, although less exible than categories and masks, to avoid
collision between asteroids in DroidBlaster. Note that groups are ltered before categories.
A more exible soluon than category/group lters is the class b2ContactFilter. This
class has a method ShouldCollide(b2Fixture, b2Fixture) that you can customize to
perform your own ltering. Actually, category/group ltering are themselves implemented
that way.
More resources about Box2D
This was a short introducon to Box2D, which is capable of much more! We have le
the following in the shadow:
Joints: two bodies linked together
Raycasng: to query a physics world (for example, which locaon is a gun
poinng toward).
Contact properes: normals, impulses, manifolds, and so on
Box2D has a really nice documentaon with much useful informaon that can be found at
http://www.box2d.org/manual.html. Moreover, Box2D is packaged with a test bed
directory (in Box2D/Testbed/Tests) featuring many use cases. Have a look to get a beer
understanding of its capabilies. Because physics simulaons can someme be rather tricky,
I also encourage you to visit Box2D forum, which is quite acve, at http://www.box2d.
org/forum/.
Running a 3D engine on Android
DroidBlaster now includes a nice and shiny physics engine. Now, let's run the Irrlicht
engine, created by a game developer Nikolaus Gebhardt in 2002. This engine supports
many features:
OpenGL ES 1 and (parally) Open GL ES 2 support
2D graphics capabilies
Support many images and mesh les formats (PNG, JPEG, OBJ, 3DS, and so on)
Towards Professional Gaming
[ 370 ]
Import Quake levels in BSP format
Skinning to deform and animate meshes with bones
Terrain rendering
Collision handling
GUI system
And even much more. Now, let's add a new dimension to DroidBlaster by running Irrlicht
GLES 1.1 renderer with the xed rendering pipeline.
Project DroidBlaster_Part10-Box2D can be used as a starng point
for this part. The resulng project is provided with this book under
the name DroidBlaster_Part10-Irrlicht.
Time for action – rendring 3D graphics with Irrlicht
1. First, let's get rid of all unnecessary stu. Remove GraphicsSprite,
GraphicsTexture, and GraphicsTileMap and Background header and source
les in the jni folder.
First, we need to clean up the code and rewrite the graphics service.
2. Create a new le jni/GraphicsObject.hpp, which includes Irrlicht.h
header.
GraphicsObject encapsulates an Irrlicht scene node, that is, an object in the 3D
world. Nodes can form a hierarchy, child nodes moving accordingly to their parent
(for example, a turret on a tank) and inhering some of their properes
(for example, visibility).
We also need a reference to a locaon in our own coordinate format (coming from
our Box2D PhysicsService) and the name of the mesh, and texture resources
we need:
#ifndef PACKT_GRAPHICSOBJECT_HPP
#define PACKT_GRAPHICSOBJECT_HPP
#include "Types.hpp"
#include <boost/shared_ptr.hpp>
#include <irrlicht.h>
#include <vector>
namespace packt {
class GraphicsObject {
public:
Chapter 10
[ 371 ]
typedef boost::shared_ptr<GraphicsObject> ptr;
typedef std::vector<ptr> vec;
typedef vec::iterator vec_it;
public:
GraphicsObject(const char* pTexture, const char* pMesh,
Location* pLocation);
void spin(float pX, float pY, float pZ);
void initialize(irr::scene::ISceneManager* pSceneManager);
void update();
private:
Location* mLocation;
irr::scene::ISceneNode* mNode;
irr::io::path mTexture; irr::io::path mMesh;
};
}
#endif
3. In jni/GraphicsObject.cpp, write the class constructor.
Create a spin() method that will be used to animate asteroids with a connuous
rotaon. First, remove any previous animaon potenally set. Then, create a rotaon
animator applied to the Irrlicht node. Finally, free animator resources (with Drop()):
#include "GraphicsObject.hpp"
#include "Log.hpp"
namespace packt {
GraphicsObject::GraphicsObject(const char* pTexture,
const char* pMesh, Location* pLocation) :
mLocation(pLocation), mNode(NULL),
mTexture(pTexture), mMesh(pMesh)
{}
void GraphicsObject::spin(float pX, float pY, float pZ) {
mNode->removeAnimators();
irr::scene::ISceneNodeAnimator* lAnimator =
mNode->getSceneManager()->createRotationAnimator(
irr::core::vector3df(pX, pY, pZ));
mNode->addAnimator(lAnimator);
lAnimator->drop();
}
...
Towards Professional Gaming
[ 372 ]
4. Inialize Irrlicht resources in the corresponding method initialize(). First, load
the requested 3D mesh and its texture according to their path on disk. If resources
are already loaded, Irrlicht takes care of reusing them. Then, create a scene node
aached to the 3D world. It must contain the newly loaded 3D mesh with the newly
loaded texture applied on its surface. Although this is not compulsory, meshes are
going to be lighted dynamically (EMF_LIGHTING ag). Lights will be set up later.
Finally, we need an update() method whose only purpose is to convert coordinates
from DroidBlaster referenal to Irrlicht referenal, which are almost idencal (both
indicate the object center with the same scale), almost because Irrlicht needs a third
dimension. Obviously, it will be possible to use Irrlicht coordinates everywhere:
...
void GraphicsObject::initialize(
irr::scene::ISceneManager* pSceneManager) {
irr::scene::IAnimatedMesh* lMesh =
pSceneManager->getMesh(mMesh);
irr::video::ITexture* lTexture = pSceneManager->
getVideoDriver()->getTexture(mTexture);
mNode = pSceneManager->addMeshSceneNode(lMesh);
mNode->setMaterialTexture(0, lTexture);
mNode->setMaterialFlag(irr::video::EMF_LIGHTING, true);
}
void GraphicsObject::update() {
mNode->setPosition(irr::core::vector3df(
mLocation->mPosX, 0.0f, mLocation->mPosY));
}
}
5. Open exisng le jni/GraphicsService.hpp to replace the older code with
Irrlicht. GraphicsService requires quite some change! Clean up all the stu about
GraphicsSprite, GraphicsTexture, GraphicsTileMap, and TimeService.
Then, insert Irrlicht main include le in place of previous graphics headers.
Replace previous registraon methods with a registerObject() similar to
the one we created in PhysicsService. It takes a mesh and texture le path in
parameters and returns a GraphicsObject dened as follows:
#ifndef _PACKT_GRAPHICSSERVICE_HPP_
#define _PACKT_GRAPHICSSERVICE_HPP_
#include "GraphicsObject.hpp"
#include "TimeService.hpp"
#include "Types.hpp"
#include <android_native_app_glue.h>
Chapter 10
[ 373 ]
#include <irrlicht.h>
#include <EGL/egl.h>
namespace packt {
class GraphicsService {
public:
...
GraphicsObject::ptr registerObject(const char* pTexture,
const char* pMesh, Location* pLocation);
protected:
...
...
6. Declare Irrlicht-related member variables and a vector to store all GraphicsObject
that will be displayed on screen. Irrlicht central class is IrrlichtDevice, which
gives access to any Irrlicht features. IvideoDriver is also an important class which
abstracts 2D/3D graphical operaons and resource management. ISceneManager
handles the simulated 3D world:
...
private:
...
EGLContext mContext;
irr::IrrlichtDevice* mDevice;
irr::video::IVideoDriver* mDriver;
irr::scene::ISceneManager* mSceneManager;
GraphicsObject::vec mObjects;
};
}
#endif
7. In jni/GraphicsService.cpp source le and update class constructor, EGL setup
remains as before. Indeed, the Irrlicht-to-Android glue code (CirrDeviceAndroid)
is an empty stub. Inializaon is le to the client (originally on the Java side) which
is performed by our own code navely in start().
So this part does not change much: just request a depth buer to blend 3D objects
properly and remove loadResources() as Irrlicht now takes care of that.
When applicaon stops, releases Irrlicht resources with a call to Drop():
...
namespace packt {
GraphicsService::GraphicsService(android_app* pApplication,
TimeService* pTimeService) :
...
Towards Professional Gaming
[ 374 ]
mContext(EGL_NO_SURFACE),
mDevice(NULL), mObjects()
{}
...
status GraphicsService::start() {
...
const EGLint lAttributes[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT,
EGL_BLUE_SIZE, 5, EGL_GREEN_SIZE, 6, EGL_RED_SIZE, 5,
EGL_DEPTH_SIZE, 16, EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_NONE
};
...
}
void GraphicsService::stop() {
mDevice->drop();
if (mDisplay != EGL_NO_DISPLAY) {
...
}
...
8. Now comes the interesng part: setup(). First, inialize Irrlicht by invoking
createDevice() factory method. The important parameter is EDT_OGLES1 which
indicates which renderer to use for rendering. The addional parameters describe
window properes (dimensions, bit depth, and so on).
Then, set up Irrlicht so that it accesses resources through les (resources could also
be compressed in an archive) relave to /sdcard/droidblaster directory. Finally,
retrieve the video driver and the scene manager that we are oen going to use:
void GraphicsService::setup() {
mDevice = irr::createDevice(irr::video::EDT_OGLES1,
irr::core::dimension2d<irr::u32>(mWidth, mHeight), 32,
false, false, false, 0);
mDevice->getFileSystem()->addFolderFileArchive(
"/sdcard/droidblaster/");
mDriver = mDevice->getVideoDriver();
mSceneManager = mDevice->getSceneManager();
...
Chapter 10
[ 375 ]
9. In setup(), prepare the scene with a light for dynamic mesh lighng (the last
parameter being the light range) and a camera posioned to simulate a top view
(values are empirical). As you can see, every object of a 3D world is considered
as a node in the scene manager, a light as well as a camera, or anything else:
...
mSceneManager->setAmbientLight(
irr::video::SColorf(0.85f,0.85f,0.85f));
mSceneManager->addLightSceneNode(NULL,
irr::core::vector3df(-150, 200, -50),
irr::video::SColorf(1.0f, 1.0f, 1.0f), 4000.0f);
irr::scene::ICameraSceneNode* lCamera =
mSceneManager->addCameraSceneNode();
lCamera->setTarget(
irr::core::vector3df(mWidth/2, 0.0f, mHeight/2));
lCamera->setUpVector(irr::core::vector3df(0.0f, 0.0f, 1.0f));
lCamera->setPosition(
irr::core::vector3df(mWidth/2, mHeight*3/4, mHeight/2));
...
10. Instead of a le map, we are going to create parcles to simulate a background star
eld. To do so, create a new parcle system node, eming parcles randomly from
a virtual box located on top of the screen. Depending on the rate chosen, more or
less parcles are emied. The lifeme leaves enough me for parcles to cross the
screen from their emission point from the top to the boom. Parcles can have
dierent sizes (from 1.0 to 8.0). When we are done seng up the parcle emier,
we can release it with drop():
...
irr::scene::IParticleSystemSceneNode* lParticleSystem =
mSceneManager->addParticleSystemSceneNode(false);
irr::scene::IParticleEmitter* lEmitter =
lParticleSystem->createBoxEmitter(
// X, Y, Z of first and second corner.
irr::core::aabbox3d<irr::f32>(
-mWidth * 0.1f, -300, mHeight * 1.2f,
mWidth * 1.1f, -100, mHeight * 1.1f),
// Direction and emit rate.
irr::core::vector3df(0.0f,0.0f,-0.25f), 10.0f, 40.0f,
// darkest and brightest color
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
Towards Professional Gaming
[ 376 ]
irr::video::SColor(0,255,255,255),
irr::video::SColor(0,255,255,255),
// min and max age, angle
8000.0f, 8000.0f, 0.0f,
// min and max size.
irr::core::dimension2df(1.f,1.f),
irr::core::dimension2df(8.f,8.f));
lParticleSystem->setEmitter(lEmitter);
lEmitter->drop();
...
11. To nish with the star eld, set up parcle texture (here star.png) and graphical
properes (transparency is needed but not the Z-buer nor lighng). When everything
is ready, you can inialize all GraphicsObjects referenced by game objects:
...
lParticleSystem->setMaterialTexture(0,
mDriver->getTexture("star.png"));
lParticleSystem->setMaterialType(
irr::video::EMT_TRANSPARENT_VERTEX_ALPHA);
lParticleSystem->setMaterialFlag(
irr::video::EMF_LIGHTING, false);
lParticleSystem->setMaterialFlag(
irr::video::EMF_ZWRITE_ENABLE, false);
GraphicsObject::vec_it iObject = mObjects.begin();
for (; iObject < mObjects.end() ; ++iObject) {
(*iObject)->initialize(mSceneManager);
}
}
...
12. The important method of GraphicsService is update(). First, update each
GraphicsObject to refresh its posion in the Irrlicht referenal.
Then, run the device to process nodes (for example, to emit parcles). Then draw
the scene between a call to beingScene() (with a background color set to black
here) and endScene(). Scene drawing is delegated to the scene manager and its
internal nodes.
Finally, rendered scene can be displayed on screen as usual:
...
status GraphicsService::update() {
GraphicsObject::vec_it iObject = mObjects.begin();
for (; iObject < mObjects.end() ; ++iObject) {
(*iObject)->update();
}
Chapter 10
[ 377 ]
if (!mDevice->run()) return STATUS_KO;
mDriver->beginScene(true, true,
irr::video::SColor(0,0,0,0));
mSceneManager->drawAll();
mDriver->endScene();
if (eglSwapBuffers(mDisplay, mSurface) != EGL_TRUE) {
...
}
...
To nish with GraphicsService, implement registerObject() method:
...
GraphicsObject::ptr GraphicsService::registerObject(
const char* pTexture, const char* pMesh, Location* pLocation) {
GraphicsObject::ptr lObject(new GraphicsObject(mSceneManager,
pTexture, pMesh, pLocation));
mObjects.push_back(lObject);
return mObjects.back();
}
}
The graphics module now renders scene with Irrlicht. So let's update game
enes accordingly.
13. Modify jni/Asteroid.hpp to reference a GraphicsObject instead of a sprite:
...
#include "GraphicsService.hpp"
#include "GraphicsObject.hpp"
#include "PhysicsService.hpp"
...
namespace dbs {
class Asteroid {
...
private:
packt::GraphicsService* mGraphicsService;
packt::TimeService* mTimeService;
packt::GraphicsObject::ptr mMesh;
packt::PhysicsObject::ptr mPhysics;
};
}
#endif
Towards Professional Gaming
[ 378 ]
14. Edit jni/Asteroid.cpp counterpart to register a GraphicsObject.
When an asteroid is recreated, its spin is updated with the corresponding method.
We do not need an animaon speed anymore:
...
namespace dbs {
Asteroid::Asteroid(packt::Context* pContext) :
mTimeService(pContext->mTimeService),
mGraphicsService(pContext->mGraphicsService) {
mPhysics = pContext->mPhysicsService->registerEntity(
0X1, 0x2, 64, 1.0f);
mMesh = pContext->mGraphicsService->registerObject(
"rock.png", "asteroid.obj", &mPhysics->mLocation);
}
void Asteroid::spawn() {
...
mPhysics->initialize(lPosX, lPosY, 0.0f, lVelocity);
float lSpinSpeed = MIN_SPIN_SPEED + RAND(SPIN_SPEED_RANGE);
mMesh->spin(0.0f, lSpinSpeed, 0.0f);
}
...
}
15. Also update jni/Ship.hpp header le, as done for asteroids:
...
#include "GraphicsService.hpp"
#include "GraphicsObject.hpp"
#include "PhysicsService.hpp"
...
namespace dbs {
class Ship {
...
private:
...
packt::TimeService* mTimeService;
Chapter 10
[ 379 ]
packt::GraphicsObject::ptr mMesh;
packt::PhysicsObject::ptr mPhysics;
packt::PhysicsTarget::ptr mTarget;
};
}
#endif
16. Change Ship.cpp to register a stac mesh. Remove animaon stu in spawn():
...
namespace dbs {
Ship::Ship(packt::Context* pContext) :
... {
mPhysics = pContext->mPhysicsService->registerEntity(
0x2, 0x1, 64, 0.0f);
mTarget = mPhysics->createTarget(50.0f);
mMesh = pContext->mGraphicsService->registerObject(
"metal.png", "ship.obj", &mPhysics->mLocation);
mInputService->setRefPoint(&mPhysics->mLocation);
}
void Ship::spawn() {
mPhysics->initialize(mGraphicsService->getWidth() * 1 / 2,
mGraphicsService->getHeight() * 1 / 4, 0.0f, 0.0f);
}
...
}
We are almost done. Do not forget to remove references to Background in the
DroidBlaster class.
17. Before running the applicaon, 3D meshes and textures need to be copied on the SD
Card, in /sdcard/droidblaster directory given to Irrlicht at step 8. This path may
have to be adapted depending on your device SD Card mount point (like explained
in Chapter 9, Porng Exisng Libraries to Android).
Resource les are provided with this book in Chapter10/Resource.
Towards Professional Gaming
[ 380 ]
What just happened?
We have seen how to embed and reuse a 3D engine in an Android applicaon to display 3D
graphics. If you run DroidBlaster on your Android device, you should obtain the following
result. Asteroids look nicer in 3D and the star eld gives a simple and nice depth impression:
Irrlicht main entry point is the IrrlichtDevice class, from which we have been able to
access anything in the engine, few of them are as follows:
IVideoDriver, which is a shell around the graphics renderer, managing graphics
resources, such as textures
ISceneManager, which manages the scene through a hierarchical tree of nodes
In other words, you draw a scene using the video driver and indicate the enes to display,
their posion, and properes through the scene manager (which manages a 3D world
through nodes).
Memory management in Irrlicht
Internally, Irrlicht uses reference counng to manage object lifeme
properly. The rule of thumb is simple: when a factory method contains
create (for example, createDevice()) in its name, then there
must be a matching call to drop() to release resources.
Chapter 10
[ 381 ]
More specically, we have used mesh nodes to display ship and asteroids, the later being
animated through an animator. We have used a simple rotaon animator but more are
provided (to animate objects over a path, for collisions, and so on).
3D modeling with Blender
The best open source 3D authoring tool nowadays is Blender.
Blender can model meshes, texture them, export them, generate
lightmaps, and many other things. More informaon and the
program itself can be found at http://www.blender.org/.
More on Irrlicht scene management
Let's linger a bit on the scene manager which is an important aspect of Irrlicht. As exposed
during the step-by-step tutorial, a node basically represents an object in the 3D world, but
not always a visible one. Irrlicht features many kinds of custom nodes:
IAnimatedMeshSceneNode: This is the most basic node. It renders a 3D mesh to
which one or more textures (for mul-texturing) can be aached. As it is stated by
its name, such a node can be animated with key frames and bones (for example,
when using Quake .md2 format).
IBillboardSceneNode: This displays a sprite inside a 3D world (that is, a textured
plane which always faces the camera).
ICameraSceneNode: This is the node through which you can see the 3D world.
Thus, this is a non-visible node.
ILightSceneNode: This illuminates world objects. We are talking here about
dynamic lighng, calculated on meshes per frames. This can be expensive and
should be acvated only if necessary. Light-mapping, which can be described as,
an interesng technique to avoid expensive light calculaon.
IParticleSceneNode: This emits parcles like we have done to simulate a
star eld.
ITerrainSceneNode: This renders an outdoor terrain (with hills, moutains, …)
from an heightmap. It provides automac Level of Detail (or LOD) handling for
depending on the distance of the terrain chunk.
Nodes have a hierarchical structure and can be aached to a parent. Irrlicht also provides
some spaal indexing (to cull meshes quickly) such as Octree or BSP to cull meshes
in complex scenes. Irrlicht is a rich engine and I encourage you to have a look at its
documentaon available at http://irrlicht.sourceforge.net/. Its forum is
also quite acve and helpful.
Towards Professional Gaming
[ 382 ]
Summary
This chapter demonstrated the re-usability possibilies oered by the Android NDK. It
is a step forward to the creaon of the professional applicaons with an emphasize on
something essenal in this fast-moving mobile world: producvity.
More specically, we saw how to simulate a physical world by porng Box2D and how to
display 3D graphics with the exisng engine, Irrlicht. We highlighted the path towards the
creaon of professional applicaons using the NDK as a leverage. But do not expect all
C/C++ libraries to be ported so easily.
Talking about paths, we are almost at the end. The next, and last, chapter introduces
advanced techniques to debug and troubleshoot NDK applicaons and make you fully
prepared for Android development.
11
Debugging and Troubleshooting
This introducon to the Android NDK would not be complete without approaching
some more advanced topics: debugging and troubleshoong code. Indeed, C/C++
are complex languages that can fail in many ways.
I will not lie to you: NDK debugging features are rather rubbish yet. It is oen
more praccal and fast to rely on simple log messages. This is why debugging
is presented in this last chapter. But sll, a debugger can save quite some me
in complex programs or even worse... crashing programs! But even in that case,
there exist alternave soluons.
More specically, we are going to discover how to do the following:
Debug nave code with GDB
Interpret a stack trace dump
Analyze program performances with GProf
Debugging with GDB
Because Android NDK is based on the GCC toolchain, Android NDK includes GDB, the GNU
Debugger, to allow starng, pausing, examining, and altering a program. On Android and
more generally on embedded devices, GDB is congured in client/server mode. The program
runs on a device as a server and a remote client, the developer's workstaon connects to it
and sends debugging commands as for a local applicaon.
GDB itself is a command-line ulity and can be cumbersome to use manually. Hopefully,
GDB is handled by most IDE and especially CDT. Thus, Eclipse can be used directly to add
breakpoints and inspect a program, only if it has been properly congured before!
Debugging and Troubleshoong
[ 384 ]
Indeed, Eclipse can insert breakpoints easily in Java as well as C/C++ source les by clicking
in the guer, to the text editor's le. Java breakpoints work out of the box thanks to the ADT
plugin, which manages debugging through the Android Debug Bridge. This is not true for CDT
which is naturally not Android-aware. Thus, inserng a breakpoint will just do nothing unless
we manage to congure CDT to use the NDK's GDB, which itself needs to be bound to the
nave Android applicaon to debug.
Debugger support has improved among NDK releases (for example, debugging purely nave
threads was not working before). Although it is geng more usable, in NDK R5 (and even
R7), situaon is far from perfect . But, it can sll help! Let's see now concretely how to debug
a nave applicaon.
Time for action – debugging DroidBlaster
Let's enable debugging mode in our applicaon rst:
1. The rst important thing to do but really easy to forget is to acvate the
debugging ag in your Android project. This is done in the applicaon manifest
AndroidManifest.xml. Do not forget to use the appropriate SDK version for
nave code:
<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
<uses-sdk android:minSdkVersion="10"/>
<application ...
android:debuggable="true">
...
2. Enabling debug ag in manifest automacally acvates debug mode in nave code.
However, APP_OPTIM ag also controls debug mode. If it has been manually set in
Android.mk, then check that its value is set to debug (and not release) or simply
remove it:
APP_OPTIM := debug
First, let's congure the GDB client that will connect to the device:
3. Recompile the project. Plug your device in or launch the emulator. Run and leave your
applicaon. Ensure the applicaon is loaded and its PID available. You can check it by
lisng processes using the following command. One line should be returned:
$ adb shell ps |grep packtpub
Chapter 11
[ 385 ]
4. Open a terminal window and go to your project directory. Run the ndk-gdb command
(located in $ANDROID_NDK folder, which should already be in your $PATH):
$ ndk-gdb
This command should return no message and create three les in obj/local/
armeabi:
gdb.setup: This is a conguraon le generated for GDB client.
app_process: This le is retrieved directly from your device. It is a system
executable le (that is, Zygote, see Chapter 2, Creang, Compiling, and
Deploying Nave Projects), launched when system starts up and forked to
start a new applicaon. GBD needs this reference le to nd its marks. It is
in some way the binary entry point of your app.
libc.so: This is also retrieved from your device. It is the Android standard
C library (commonly referred as bionic) used by GDB to keep track of all the
nave threads created during runme.
Append –verbose ag to have a detailed feedback on what
ndk-gdb does. If ndk-gdb complains about an already running
debug session, then re-execute ndk-gdb with the –force ag.
Beware, some devices (especially HTC ones) do not work in debug
mode unless they are rooted with a custom ROM (for example,
they return a corrupt installaon error).
5. In your project directory, copy obj/local/armeabi/gdb.setup and name it
gdb2.setup. Open it and remove the following line which requests GDB client to
connect to the GDB server running on the device (to be performed by Eclipse itself):
target remote :5039
6. In the Eclipse main menu, go to Run | Debug Conguraons... and create a new
Debug conguraon in the C/C++ Applicaon item called DroidBlaster_JNI. This
conguraon will start GDB client on your computer and connect to the GDB Server
running on the device.
7. In the Main tab, set:
Project to your own project directory (for example, DroidBlaster_
Part8-3).
Debugging and Troubleshoong
[ 386 ]
C/C++ Applicaon to point to obj/local/armeabi/app_process using
the Browse buon (you can use either an absolute or a relave path).
8. Switch launcher type to Standard Create Process Launcher using the link Select
other... at the boom of the window:
Chapter 11
[ 387 ]
9. Go to the debugger le and set:
Debugger type to gdbserver.
GDB debugger to ${ANDROID_NDK}/toolchains/arm-linux-
androideabi-4.4.3/prebuilt/linux-x86/bin/arm-linux-
androideabi-gdb.
GDB command le to point to the gdb2.setup le located in obj/
local/armeabi/ (you can use either an absolute or a relave path).
Debugging and Troubleshoong
[ 388 ]
10. Go to the Connecon tab and set Type to TCP. Default value for Host name or IP
address and Port number can be kept (localhost d 5039).
Now, let's congure Eclipse to run GDB server on the device:
11. Make a copy of $ANDROID_NDK/ndk-gdb and open it with a text editor.
Find the following line:
$GDBCLIENT -x `native_path $GDBSETUP`
Comment it because GDB client is going to be run by Eclipse itself:
#$GDBCLIENT -x `native_path $GDBSETUP`
12. In the Eclipse main menu, go to Run | External Tools | External Tools
Conguraons... and create a new conguraon DroidBlaster_GDB.
This conguraon will launch GDB server on the device.
13. In the Main tab, set:
Locaon poinng to our modied ndk-gdb in $ANDROID_NDK. You can use
Variables... buon to dene Android NDK locaon in a more generic way
(that is, ${env_var:ANDROID_NDK}/ndk-gdb).
Working directory to your applicaon directory locaon (for example,
${workspace_loc:/DroidBlaster_Part8-3})
Chapter 11
[ 389 ]
Oponally, set the Arguments textbox:
–-verbose: To see in details what happens in the Eclipse console.
–force: To kill automacally any previous session.
–start: To let GDB Server start the applicaon instead of geng aached
to the applicaon aer it has been started. This opon is interesng if you
debug nave code only and not Java but it can cause troubles with the
emulator (such as to leave the back buon).
We are done with conguraon.
14. Now, launch your applicaon as usual (as shown in Chapter 2, Creang, Compiling,
and Deploying Nave Projects).
15. Once applicaon is started, launch the external tool conguraon DroidBlaster
GDB which is going to start GDB server on the device. GDB server receives debug
commands sent by the remote GDB client and debugs your applicaon locally.
Debugging and Troubleshoong
[ 390 ]
16. Open jni/DroidBlaster.cpp and set a breakpoint on the rst line of onStep()
(mTimeService->update()) by double-clicking on the guer on the text editor's
le (or right-clicking and selecng Toggle breakpoint).
17. Finally, launch DroidBlaster JNI C/C++ applicaon conguraon to start GDB client.
It relays debug commands from Eclipse CDT to GDB server over a socket connecon.
From the developer's point of view, this is almost like debugging a local applicaon.
What just happened?
If set up properly, applicaon freezes aer a few seconds and Eclipse focuses into the break-
pointed line. It is now possible to step into, step out, step over a line of code or resume
applicaon. For assembly-addict, an instrucon stepping mode can also be acvated.
Now, enjoy the benet of this modern producvity tool, that is, a debugger. However, as you
are going or maybe are already experiencing, beware that debugging on Android is rather
slow (because it needs to communicate with the remote Android device) and somewhat
unstable though it works well most of the me.
Chapter 11
[ 391 ]
If the conguraon process is a bit complicated and tricky, the same goes for the launch of
a debug session. Remember the three necessary steps:
1. Start the Android applicaon (whether from Eclipse or your device).
2. Then, launch GDB server on the device (that is, the DroidBlaster_GDB conguraon
here) to aach it to the applicaon locally.
3. Finally, start GDB client on your computer (that is, the DroidBlaster_JNI
conguraon here) to allow CDT to communicate with the GDB server.
4. Oponally, start the GDB server with the –start ag to make it launch the
applicaon itself and omit the rst step.
Beware gdb2.setup may be removed while cleaning your
project directory. When debugging stops working, this should
be the second thing to check, aer making sure that ndk-gdb
is up and running.
However, there is an annoying limitaon about this procedure: we are interrupng the
program while it is already running. So how to stop on a breakpoint in inializaon code
and debug it (for example in jni/DroidBlaster.cpp on onActivate())? There are
two soluons:
Leave your applicaon and launch the GDB client. Android does not manage
memory as it is in Windows, Linux, or Mac OS X: it kills applicaons only when
memory is needed. Processes are kept in memory even aer user leaves. As your
applicaon is sll running, GDB server remains started and you can quietly start
your client debugger. Then, just start your applicaon from your device (not from
Eclipse, which would kill it).
Debugging and Troubleshoong
[ 392 ]
Take a pause when the applicaon starts... in the Java code! However, from a fully
nave applicaon, you will need to create a src folder for Java sources and add a
new Activity class extending NativeActivity. Then you can put a breakpoint
on a stac inializer block.
Stack trace analysis
No need to lie. I know it happened. Do not be ashamed, it happened to all of us... your
program crashed, without a reason! You think probably the device is geng old or Android
is broken. We all made that reecon but ninety-nine percent of the me, we are the ones
to blame!
Debuggers are the tremendous tool to look for problems in your code. But they work in real
me when programs run. They assume you know where to look for. With problems that
cannot be reproduced easily or that already happened, debuggers become sterile.
Hopefully, there is a soluon: a few ulies embedded in the NDK help to analyse ARM stack
traces. Let's see how they work.
Time for action – analysing a crash dump
1. Let's introduce a fatal bug in the code. Open jni/DroidBlaster.cpp and modify
method onActivate() as follows:
...
void DroidBlaster::onActivate() {
...
mTimeService = NULL;
return packt::STATUS_KO;
}
...
2. Open the LogCat view (from Window | Show View | Other...) in Eclipse and then
run the applicaon. Not prey for a candid Android developer! A crash dump
appeared in the logs:
...
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'htc_wwe/htc_bravo/bravo:2.3.3/...
pid: 1723, tid: 1743 >>> com.packtpub.droidblaster <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0000000c
r0 a9df2e71 r1 40815c8d r2 7cb9c28d r3 00000000
...
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
Chapter 11
[ 393 ]
ip a3400000 sp 45102830 lr 00000016 pc 80410a2c cpsr 00000030
d0 6f466e6961476e6f d1 0000000400000390
...
scr 20000012
#00 pc 00010a2c /data/data/com.packtpub.droidblaster/
lib/libdroidblaster.so
#01 pc 00009fcc /data/data/com.packtpub.droidblaster/
lib/libdroidblaster.so
...
#06 pc 00011618 /system/lib/libc.so
code around pc:
80410a0c 00017ad4 00000000 b084b510 9b019001
...
code around lr:
stack:
451027f0 00000000
451027f4 45102870
451027f8 804110f5 /data/data/com.packtpub.droidblaster/lib/
libdroidblaster.so
...
This dump contains useful informaon about the current program state. First it
describes the error that happened: a SIGSEGV, also known as a segmentaon fault.
If you look at the faulty address, that is, 0000000c, you will see that it is close to
NULL. This is an important hint!
Then we have informaon about ARM register states (rX, dX, ip, sp, lr, pc, and so
on). But what we are interested in comes right aer this: informaon about where
the program was when it got interrupted. These lines are highlighted in the extract
above and can be idened by the words pc wrien on the line and an hexadecimal
number aer it. The laer expresses the Program Counter locaon, that is, which
instrucon was executed when problem occurred. Note that this memory address is
relave to the containing library. With this piece of informaon, we know exactly on
which instrucon problem occurred... in the binary code!
3. We need somehow to translate this binary address into something understandable to
a normal human being. The rst soluon is to disassemble completely the .so library.
Open a terminal window and go to your project directory. Then execute the
objdump command located in the executable directory of the NDK toolchain:
$ $ANDROID_NDK/toolchains/arm-linux-androideabi-4.4.3/prebuilt/
linux-x86/bin/arm-linux-androideabi-objdump -S
./obj/local/armeabi/libdroidblaster.so > ~/disassembler.dump
Debugging and Troubleshoong
[ 394 ]
4. This command disassembles the library and outputs each assembler instrucon and
locaon accompanied with the source C/C++ code. Open the output le with a text
editor and if you look carefully, you will nd the same address than the one in the
crash dump, next to pc:
...
void TimeService::update()
{
10a14: b510 push {r4, lr}
10a16: b084 sub sp, #16
10a18: 9001 str r0, [sp, #4]
double lCurrentTime = now();
10a1a: 9b01 ldr r3, [sp, #4]
10a1c: 1c18 adds r0, r3, #0
10a1e: f000 f81f bl 10a60 <_
ZN5packt11TimeService3nowEv>
10a22: 1c03 adds r3, r0, #0
10a24: 1c0c adds r4, r1, #0
10a26: 9302 str r3, [sp, #8]
10a28: 9403 str r4, [sp, #12]
mElapsed = (lCurrentTime - mLastTime);
10a2a: 9b01 ldr r3, [sp, #4]
10a2c: 68dc ldr r4, [r3, #12]
10a2e: 689b ldr r3, [r3, #8]
10a30: 9802 ldr r0, [sp, #8]
10a32: 9903 ldr r1, [sp, #12]
...
5. As you can see, problem seems to occur when execung mService->update() in
jni/TimeService.cpp instrucon because of the wrong object address inserted
in step 1.
6. Disassembled dump le can become quite big. For this version of droidblaster.
so, it should be around 3 MB. But it could become tenth MB, especially when
libraries such as Irrlicht are involved! In addion, it needs to be regenerated each
me library is updated.
Hoperfully, another ulity named addr2line, located in the same directory as
objdump, is available. Execute the following command with the pc address at the
end, where -f shows funcon names, -C demangles them and -e indicates the
input library:
$ $ANDROID_NDK/toolchains/arm-linux-androideabi-4.4.3/prebuilt/
linux-x86/bin/arm-linux-androideabi-addr2line -f –C
-e ./obj/local/armeabi/libdroidblaster.so 00010a2c
Chapter 11
[ 395 ]
This gives immediately the corresponding C/C++ instrucon and its locaon in its
source le:
7. Since version R6, Android NDK provides ndk-stack in its root directory. This ulity
does what we have done manually using an Android log dump. Coupled with the
ADB, which is able to display Android logs while in real me, crashes can be analyzed
without a move (except your eyes!).
Simply run the following command from a terminal window to decipher crash
dumps automacally:
$ adb logcat | ndk-stack -sym ./obj/local/armeabi
********** Crash dump: **********
Build fingerprint: 'htc_wwe/htc_bravo/bravo:2.3.3/
GRI40/96875.1:user/release-keys'
pid: 1723, tid: 1743 >>> com.packtpub.droidblaster <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0000000c
Stack frame #00 pc 00010a2c /data/data/com.packtpub.
droidblaster/lib/libdroidblaster.so: Routine update in /home/
packt/Project/Chapter11/DroidBlaster_Part11/jni/TimeService.cpp:25
Stack frame #01 pc 00009fcc /data/data/com.packtpub.
droidblaster/lib/libdroidblaster.so: Routine onStep in /home/
packt/Project/Chapter11/DroidBlaster_Part11/jni/DroidBlaster.
cpp:53
Stack frame #02 pc 0000a348 /data/data/com.packtpub.
droidblaster/lib/libdroidblaster.so: Routine run in /home/packt/
Project/Chapter11/DroidBlaster_Part11/jni/EventLoop.cpp:49
Stack frame #03 pc 0000f994 /data/data/com.packtpub.
droidblaster/lib/libdroidblaster.so: Routine android_main in /
home/packt/Project/Chapter11/DroidBlaster_Part11/jni/Main.cpp:31
...
What just happened?
We have used ARM ulies embedded in the Android NDK to locate the origin of an
applicaon crash. These ulies constute an inesmable help and should be considered
as your rst-aid kit when a bad crash happens.
Debugging and Troubleshoong
[ 396 ]
However, if they can help you nding the "where", it is another kele of sh to nd the
"why". As you can see in the piece of code at step 4, understanding why LDR instrucon
(whose goal is to load in a register, some data from memory, constants, or other registers)
fails is not trivial. This is where your programmer intuion (and possibly knowledge of
assembly code) comes into play.
More on crash dumps
For general culture, let's linger briey on what is provided in the LogCat crash dump. A crash
dump is not dedicated only to overly talented developers or people seeing red-dressed girl in
binary code, but also to those who have a minimum knowledge of assemblers and the way
ARM processors work. The goal of this trace is to give as much informaon as possible on the
current state of the program at the me it crashed:
The rst line gives the build ngerprint, which is a kind of an idener indicang
the device/Android release currently running. This informaon is interesng when
analyzing dumps from various origins.
The second line indicates the PID, process idener, which uniquely idenfy an
applicaon on Unix system, and the TID, which is the thread idener. It can be
the same as the process idener when crash occurs on the main thread.
The third line shows the crash origin represented as a signal, here a classic
segmentaon fault (SIGSEGV).
Then, processor's register values are dumped, where:
rX: This is an integer register.
dX: This is a oang point register.
fp (or r11): The Frame Pointer holds a xed locaon on the stack during
a roune call (in conjuncon with the Stack Pointer).
ip (or r12): The intra procedure call scratch register may be used with
some subroune calls, for example, when the linker needs a veneer (a small
piece of code) to aim at a dierent memory area when branching (a branch
instrucon to jump somewhere else in the memory requires an oset
argument relave to current locaon, allowing a branching range of a few
MB only, not the full memory).
sp (or r13): This is the stack pointer, which saves locaon of the top of
the stack.
lr (or r14): The link register generally saves program counter's value
temporarily to restore it later. A typical example of its use is a funcon
call which jumps somewhere in the code and then go back to its previous
locaon. Of course, several chained subroune calls requires the link
register to be stacked.
Chapter 11
[ 397 ]
pc (or r15): This represents the program counter which holds the address
of next instrucon to execute. Program counter is just incremented when
execung a sequenal code to fetch next instrucon but is altered by
branching instrucons (if/else, a C/C++ funcon calls, and so on).
cpsr: The Current Program Status Register contains a few ags about the
current processor working mode and some addional bit ags for condion
codes (such as N for an operaon which resulted in a negave value, Z for a 0
or equality result, and so on), interrupts, and instrucon set (Thumb or ARM).
Crash dump also contains a few memory words around PC (that is, the block of
instrucons around) and LR (for previous locaon).
Finally, a dump of the raw call stack is logged.
Just a convenon
Remember that the use of registers is mainly a convenon. For
example, Apple iOS uses r7 as a frame pointer instead of r12...
So always be very careful when reusing exisng code!
Performance analysis
If debugging tools are sll imperfect, I have to advise you that proling tools are rather
immature... when they even work! Actually, there is no real ocial support from Google
for memory or performance proler, except in the emulator. This may change soon or later.
But right now, those who like to tweak code and analyse each instrucon may starve. This
is parcularly true when developing with a non-developer or non-rooted phone.
Hopefully, a few soluons exist and some are coming. Let's cite the following one:
Valgrind: This is probably the most famous open source proler which can monitor
not only performance but also memory and cache usage. This ulity is currently
being ported to Android. With some tweaking, it is possible to make it work on a
developer or rooted phone in ArmV7 mode. It is one of the best hopes for Android.
Android-NDK-Proler: This is a port of Gprof on Android. It is a simple and basic
proler which works by instrumenng and sampling code at runme. It is the
simplest soluon to prole performance and does not require any specic hardware.
OProle is a system-wide proler which inserts its code in the system kernel (which
thus needs to be updated) to collect proling data with a low overhead. It is more
complicated to install and requires a developer or rooted phone to work but works
quite well and does instrument code. It is a much beer soluon to prole code for
free if you have proper hardware at your disposal.
Debugging and Troubleshoong
[ 398 ]
The commercial development suite ARM DS-5 and its StreamLine performance
analyzer may become an interesng opon.
Open GL ES Prolers from manufacturers: Adreno Proler for Qualcomm, PerfHUD
ES for NVidia and PVRTune for PowerVR. These prolers are hardware-specic. The
choice depends on your phone. These tools are however essenal to see what is
happening under the GLES hood.
We are not going to evoke the emulator proler here because of its inability to emulate
programs properly at an eecve speed (especially when using GLES). But know that it exists.
Instead, we are now going to discover the interesng Android-NDK-Proler, an alternave
Gprof-based proler ported on Android by Richard Quirk (see http://quirkygba.
blogspot.com/ for more informaon). Android-NDK-Proler requires a device running
at least Android Gingerbread.
Project DroidBlaster_Part8-3 can be used as a starng point for
this part. The resulng project is provided with this book under
the name DroidBlaster_Part11.
Time for action – running GProf
Let's try to prole our own applicaon code:
1. Open a browser window and navigate to the Android-NDK-Proler homepage at
http://code.google.com/p/android-ndk-profiler/. Go to the Downloads
secon and save the latest release (3.1 at the me of wring) on your computer.
2. Unzip archive in $ANDROID_NDK/sources/android-ndk-profiler. This archive
contains an Android Makele and two libraries: one for Arm V5 and one for Arm V7.
3. Turn Android-NDK-Proler into a full android module (see highlighted lines). The main
missing point is the export of prof.h le that we are going to include in our code.
This Makele uses the $TARGET_ARCH_ABI variable to select the right library
version (Arm V5/V7) automacally according to what is dened in Application.
mk (APP_ABI= armeabi, armeabi-v7a). It also lters some opmizaon opons
which could interfere with it (for Thumb as well as ARM code):
LOCAL_PATH:= $(call my-dir)
TARGET_thumb_release_CFLAGS := $(filter-out -ffunction-
sections,$(TARGET_thumb_release_CFLAGS))
Chapter 11
[ 399 ]
TARGET_thumb_release_CFLAGS := $(filter-out -fomit-frame-
pointer,$(TARGET_thumb_release_CFLAGS))
TARGET_CFLAGS := $(filter-out -ffunction-sections,$(TARGET_
CFLAGS))
# include libandprof.a in the build
include $(CLEAR_VARS)
LOCAL_MODULE := andprof
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libandprof.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/
include $(PREBUILT_STATIC_LIBRARY)
4. Android-NDK-Proler can now be included in a normal nave library. Let's append
it to DroidBlaster_Part8-3 (you can use any other version you want).
Add the opmizaon lter like done in proler's own Makele. Since compilaon
is done in thumb mode by default, keep only related lines. Then include -pg
parameter which inserts addional instrucon necessary to the proler. Finally,
include proler module as usual:
LOCAL_PATH := $(call my-dir)
TARGET_thumb_release_CFLAGS := $(filter-out -ffunction-
sections,$(TARGET_thumb_release_CFLAGS))
TARGET_thumb_release_CFLAGS := $(filter-out -fomit-frame-
pointer,$(TARGET_thumb_release_CFLAGS))
TARGET_CFLAGS := $(filter-out -ffunction-sections,$(TARGET_
CFLAGS))
include $(CLEAR_VARS)
LS_CPP=$(subst $(1)/,,$(wildcard $(1)/*.cpp))
LOCAL_CFLAGS := -DRAPIDXML_NO_EXCEPTIONS -pg
LOCAL_MODULE := droidblaster
LOCAL_SRC_FILES := $(call LS_CPP,$(LOCAL_PATH))
LOCAL_LDLIBS := -landroid -llog -lEGL -lGLESv1_CM -lOpenSLES
LOCAL_STATIC_LIBRARIES := android_native_app_glue png andprof
include $(BUILD_SHARED_LIBRARY)
$(call import-module,android/native_app_glue)
$(call import-module,libpng)
$(call import-module,android-ndk-profiler)
Debugging and Troubleshoong
[ 400 ]
5. To run the proler, we need to include a proler start up and shut down funcon
in the code. Open jni/Main.cpp and insert them at the beginning and end
of android_main(). Set sample frequency to 6000 thanks to a predened
environment variable CPUPROFILE_FREQUENCY:
...
#include <cstdlib>
#include <prof.h>
void android_main(struct android_app* pApplication)
{
setenv("CPUPROFILE_FREQUENCY", "60000", 1);
monstartup("droidblaster.so");
// Run game services and event loop.
...
lEventLoop.run(&lDroidBlaster, &lInputService);
moncleanup();
}
6. Finally, allow applicaon to write on a storage in AndroidManifest.xml:
<?xml xmlns:android="http://schemas.android.com/apk/res/android"
package="com.packtpub.droidblaster" android:versionCode="1"
android:versionName="1.0">
...
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_
STORAGE"/>
</manifest>
7. Recompile DroidBlaster project. It now includes all the necessary instrucons
to start proler and generate proling informaon.
8. Run project on a device. Log messages are generated between proler startup
and shutdown. Make sure applicaon completely dies by pressing the back buon,
a pause being not sucient:
INFO/threaded_app(3553): Start: 0x97270
INFO/PROFILING(3553): Profile droidblaster.so 80400000-8043d000: 0
INFO/PROFILING(3553): 0: parent: carrying on
INFO/PACKT(3553): Creating GraphicsService
Chapter 11
[ 401 ]
INFO/PACKT(3553): Exiting event loop
INFO/PROFILING(3553): parent: moncleanup called
INFO/PROFILING(3553): 1: parent: done profiling
INFO/PROFILING(3553): writing gmon.out
INFO/PROFILING(3598): child: finished monitoring
INFO/PACKT(3553): Destructing DroidBlaster
9. Aer applicaon is terminated, retrieve le gmon.out generated in the /sdcard
folder of your device (depending on your device, storage may be mounted in
another directory) and save it in your project directory. Do not forget to acvate
USB Mass Storage mode to see les from your computer.
10. From a terminal window located in your project directory where gmon.out
is saved, open a terminal and run gprof analyser located beside NDK ARM
toolchain binaries:
$ ANDROID_NDK/toolchains/arm-linux-androideabi-4.4.3/prebuilt/
linux-x86/bin/arm-linux-androideabi-gprof obj/local/armeabi/
libdroidblaster.so
This command generates a textual output that you can redirect to a le. It contains
all proling results. The rst part (at prole) is the consolidated result with top
funcons which seem to take me. The second part is the raw index from which the
rst part is calculated:
Flat profile:
Each sample counts as 1.66667e-05 seconds.
% cumulative self self total
time seconds seconds calls us/call us/call name
18.64 0.00 0.00 png_read_
filter_row
13.56 0.00 0.00 15847 0.01 0.02 packt::Graph
icsService::update()
10.17 0.00 0.00 15847 0.01 0.01 packt::Graph
icsSprite::draw(float)
10.17 0.00 0.00 1 100.00 566.67
packt::EventLoop::run(...)
8.47 0.00 0.00 15847 0.01 0.03
dbs::DroidBlaster::onStep()
5.08 0.00 0.00 15847 0.00 0.00
packt::GraphicsTileMap::draw()
...
Debugging and Troubleshoong
[ 402 ]
index % time self children called name
<spontaneous>
[1] 57.6 0.00 0.00 android_main [1]
0.00 0.00 1/1
packt::EventLoop::run(...) [2]
0.00 0.00 1/1 packt::EventLoop:
:EventLoop(android_app*) [469]
0.00 0.00 1/1 packt::Sensor::Se
nsor(packt::EventLoop&, int) [466]
0.00 0.00 1/1 packt::TimeServic
e::TimeService() [433]
0.00 0.00 1/1 packt::GraphicsSe
rvice::GraphicsService(...) [456]
...
What just happened?
We have compiled Android-NDK-Proler project as an NDK module and appended it to our
own project. We turned proling on with the help of two exported methods monstartup()
and moncleanup(). The proling result is wrien to gmon.out le on the SD Card (thus
requiring write access) that can be parsed by the NDK gprof ulity.
The output le contains a summary for each funcon hit by the sampler: the at prole.
More specically, it indicates the following:
index: This idenes an entry in the index computed from and wrien aer the
at prole.
% time: This represents the fragment of me spent in the funcon compared to
the total program execuon mes.
cumulative seconds: This is the accumulated total me spent in the funcon
and all the funcon above in the table (using self seconds).
self seconds: This is the accumulated total me spent in the funcon itself
over its mulple execuon.
calls: This represents the total number of calls to a funcon. This is the only
informaon which is really accurate.
self s/call: This is the average me spent in one execuon of the funcon.
This column depends on sample hits and is not reliable.
total s/call: This is the same as self s/call but cumulated with the me
spent in sub-funcons too. This column is also depends on sample hits.
Note that funcons in which no apparent me is spent (which does not mean they are
never called) are not menoned unless -z is appended to command-line opons.
Chapter 11
[ 403 ]
How it works
To prole a piece code, GCC compiler instruments your code when opon -pg is
appended to compilaon opons. Instrumentaon relies on a roune named mcount()
(more formerly __gnu_mcount_nc()) which is inserted at the beginning of each funcon
to gather informaon about its caller and compute call count indicator. The role of
Android-NDK-Proler here is to implement this roune which is not provided by the
Android NDK.
More advanced proling informaon is extracted by sampling the PC counter at constant
intervals (100hz by default), in order to detect which funcon the program is currently
running (and derive the call stack). From a theorecal point of view, the more a funcon
takes me to run, the bigger is the probability that a sample hits it.
To do so, Android-NDK-Proler creates a separate thread to collect ming informaon
and a new fork process to interrupt nave code and record samples. To do so, it requires
the ability to aach to a parent process which only works from Android 2.3 Gingerbread.
Thus, if you see the following message in Android logs, proling informaon will not get
collected accurately:
INFO/PROFILING(3588): child: could not attach 3584
GProf is a mature (not to say anc) tool which has limitaons. First, GProf instrumentaon
is intrusive. It aects performance and potenally cache usage which result in perturbaons.
Moreover, it does not measure me spent in I/O which is oen a good place to look for
bolenecks and does not handle recursion. Finally, because it uses sampling and makes
some assumpon about code (for example, a funcon is assumed to use more or less the
same me to run for each call), GProf does not give very accurate results and needs many
samples to increase accuracy. This makes it dicult to analyze results properly, when they
are not misleading.
Although it is far from perfect, GProf is sll easy to set up and can be a good start in proling.
ARM, thumb, and NEON
Compiled nave C/C++ code on current Android ARM devices follows an Applicaon Binary
Interface (ABI). An ABI species the binary code format (instrucon set, calling convenons,
and so on). GCC translates code into this binary format. ABIs are thus strongly related to
processors. The target ABI can be selected in the Application.mk le with the property
APP_ABI. There exist four main ABIs supported on Android:
thumb: This is the default opon which should be compable with all ARM devices.
Thumb is a special instrucon set which encodes instrucons on 16-bit instead of 32
to improve code size (useful for devices with constrained memory). The instrucon
set is severely restricted compared to ArmEABI.
Debugging and Troubleshoong
[ 404 ]
armeabi (Or Arm v5): This should run on all ARM devices. Instrucons are encoded
on 32-bit but may be more concise than Thumb code. Arm v5 does not support
advanced extensions like oang point acceleraon and is thus slower than Arm v7.
armeabi-v7a: This supports extensions such as Thumb-2 (similar to Thumb but with
addional 32-bit instrucons) and VFP plus some oponal extensions such as NEON.
Code compiled for Arm V7 will not run on Arm V5 processors.
x86: This is for PC-like architectures (that is, Intel/AMD). There is no ocial
device that existed at the me this book was wrien but an unocial open
source iniave exists.
It is possible to compile code, for example, for Arm V5 and V7 at the same me, the most
appropriate binaries are selected at installaon me.
Android provides a cpu-features.h API (with android_
getCpuFamily() and android_getCpuFeatures()
methods) to detect available features on the host device at
runme. It helps in detecng the CPU (ARM, X86) and its
capabilies (ArmV7 support, NEON, VFP).
Performance is one of the main criteria to develop with the Android NDK. To achieve this,
ARM created a SIMD instrucon set (acronym Single Instrucon Mulple Data, that is, process
several data in parallel with one instrucon) called NEON which has been introduced along
with the VFP (the oang point accelerated unit).
NEON is not available on all chips (for example, Nvidia Tegra 2 does not support it) but is
quite popular in intensive mulmedia applicaon. They are also a good way to compensate
the weak VFP unit of some processors (for example, Cortex-A8).
NEON code can be wrien in a separate assembler le, in a
dedicated asm volatile block with assembler instrucons or
in a C/C++ le or as intrinsics (NEON instrucons encapsulated in a
GCC C roune). Intrinsics should be used with much care as GCC is
oen unable to generate ecient machine code (or requires lots of
tricky hints). Wring real assembler code is generally advised.
NEON and modern processors are not easy to master. The Internet is full of examples to get
inspiraon from. For example, have a look at code.google.com/p/math-neon/ for an
example of math library implemented with NEON. Reference technical documentaon can
be found on the ARM website at http://infocenter.arm.com/.
Chapter 11
[ 405 ]
Summary
In this last chapter, we have seen advanced techniques to troubleshoot bugs and performance
issues. More specically, we have debugged our code with the nave code debugger, which is
slow and complex to set up but is a real life saver.
We have also executed NDK Arm ulies to decipher crash dumps. They are the ulmate
soluon when a crash already occurred.
Finally, we have proled our code to analyze performances with GProf. This soluon is
limited but can give an interesng overview.
With these tools in hand, you are now ready to venture out into the NDK jungle. And if
you are adventurous, you can dive head rst in ARM assembler to improve performances
drascally . However, beware this is useful only when targeng the right pieces of code
(the famous 20%!). Do not forget that opmizing a bad algorithm will never make it good,
and a good algorithm even without opmizaon can make a huge dierence.
Afterword
Throughout this book, you have learnt the essenals to get started and overlooked the paths
to follow to go further. You now know the key elements to tame these lile powerful monsters
and start exploing their full power. However, there is sll a lot to learn, but the me and space
lacks. Anyway, the only way to master a technology is to pracce and pracce again. I hope you
enjoy the journey and that you feel armed up for the mobile challenge. So my best advice now
is to gather your fresh knowledge and all your amazing ideas, beat them up in your mind and
bake them with your keyboard!
Where we have been
We have seen concretely how to create nave projects with Eclipse and the NDK. We have
learnt how to embed a C/C++ library in Java applicaons through JNI and how to run nave
code without wring a line of Java.
We have tested mulmedia capabilies of the Android NDK with OpenGL ES and OpenSL ES,
which are becoming a standard in mobility (of course, aer oming Windows Mobile). We
have even interacted with our phone input peripherals and apprehended the world through
its sensors.
Moreover, the Android NDK is not only related to performance but also to portability. Thus,
we have reused the STL framework, its best companion Boost, and ported third-party libraries
almost seamlessly. We now have powerful 3D and physics engines in our hands!
Finally, we have seen how to debug nave code (and that was not so simple) and analyze
program crashes and performance.
Aerword
[ 408 ]
Where you can go
Eclipse with ADT and CDT plugins is great. But their integraon is not absolutely natural.
Debugging operaons are a bit complex and not everybody will be sased with the lack
of advanced proling tools. But some alternaves are emerging, such as the Nvidia Tegra
Development Pack (http://developer.nvidia.com/tegra-android-development-
pack) for glad Tegra device owners. ARM DS-5 (http://www.arm.com/products/
tools/software-tools/ds-5/) can also become an interesng opon for professional
development. An open source iniave exists to bring Android features to Visual Studio
(http://code.google.com/p/vs-android/). The Android ecosystem is full of life and
quickly evolving.
A subject that is parally outside the scope of this book is the emulaon of applicaon on a
PC. Here I am not talking about the Android emulator, which runs an Android OS image on a
system virtualizer, I am talking about nave emulaon, that is, running an applicaon directly
on your Linux, Mac, or Windows computer. This is the best soluon to make all your usual
programming tools available, Valgrind (to analyze memory leaks) being probably the most
useful example. Have a look at the PowerVR SDK (http://www.imgtec.com/powervr/
insider/) to emulate OpenGL ES on your PC. Obviously, there is no real alternave to
emulate the nave Android framework. This approach works quite well but requires a real
design eort to keep apart common code from OS-specic code. But this is worth the eort
as you can denitely gain some producvity and, even beer, ease the porng of your C/C++
to other OS (you know what I am talking about!).
We have ported a few libraries, but a lot more are out there and waing to get ported.
Actually, many of them work without the need of a code revamp. They just need to be
recompiled. For those interested in 3D physics, Bullet (http://bulletphysics.org/)
is an example of the engine that can be ported right away in a few minutes. The C/C++
ecosystem has existed for several decades now and is full of richness. Some libraries have
been specically designed for mobile devices. A great framework that you should denitely
have a look at if you want to write mobile games, is Unity (http://unity3d.com/).
And if you really want to get your hand dirty in the guts of Android, I encourage you to have
a look at the Android plaorm code itself, available at http://source.android.com/.
It is not a piece of cake to download, compile, or even deploy it, but this is the only way to
get an in-depth understanding of Android internals and somemes the only way to nd out
where these annoying bugs are coming from!
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
Aerword
[ 409 ]
Where to nd help
The Android community is really acve and following are the places to nd useful informaon:
The Android Google group (http://groups.google.com/group/android-
developers) and the Android NDK group (http://groups.google.com/
group/android-ndk) where you can get some help, somemes from the Android
team member.
The Android Developer BlogSpot (http://android-developers.blogspot.
com/) where you can nd fresh and ocial informaon about Android development.
Google IO (http://www.google.com/events/io/2011, 2009 and 2010 sessions
are also available) for great Android video talks performed by Google's engineers.
Google Code (http://code.google.com/hosting/) for lots of NDK example
applicaons. Just type NDK in the search engine and let Google be your friend.
The NVidia Developer Centre (http://developer.nvidia.com/category/
zone/mobile-development) for Tegra but also general resources about Android
and the NDK.
The Qualcomm Developer Network (https://developer.qualcomm.com/) to
nd informaon about the NVidia main competor. The Qualcomm's Augmented
Reality SDK is especially promising.
Anddev (http://www.anddev.org/) is an acve Android forum with an
NDK secon.
Stack Overow (http://stackoverflow.com/) is not dedicated to Android but
here you can ask quesons and get accurate answers.
Marakana Website (http://marakana.com/tutorials.html) provides many
interesng resources about Android and especially video talks.
Packt Website (http://www.packtpub.com/), a bit of self-promoon for the
many resources available there about Android, Irrlicht, and open source soware.
Aerword
[ 410 ]
This is just the beginning
Creang an applicaon is only part of the path. Publishing and selling is another. This is, of
course, outside the scope of this book but handling fragmentaon and tesng compability
with various target devices can be a real diculty that needs to be taken seriously. Beware,
problems start occurring when you start dealing with hardware specicies (and there are
lots of them) like we have seen with input devices. These issues are, however, not specic
to the NDK. If incompabilies exist in a Java applicaon, then nave code will not do
beer. Handling various screen sizes, loading appropriately sized resources, and adapng
to device capabilies are things that you will eventually need to deal with. But that should
be manageable.
In few words, there are a lot of marvellous but also painful surprises to discover. But Android
and mobility is sll a fallow land that needs to be modelled. Look at the evoluon of Android
from its earliest version to the latest one to be convinced. Revoluon does not take place
every day so do not miss it!
Good luck.
Index
Symbols
-02 opon 330
3D engine
about 353
features 369, 370
running, on Android 369, 370
3D graphics
rendering, with Irrlicht 370-381
3D modeling, Blender 381
3DS 369
-force ag 385
-verbose ag 385
A
AAsetMAnager opaque pointer 196
AAsset_close() 197
AAssetManager_open() 197
AASSET_MODE_BUFFER 197
AASSET_MODE_RANDOM 197
AASSET_MODE_STREAMING 197
AASSET_MODE_UNKNOWN mode 197
AAsset_read() 197
ABI
about 403
armeabi 404
armeabi-v7a 404
thumb 403
x86 404
accelerometer 273
acvate() method 159
acvityCallback() 156, 160
acvity events
handling 155-166
AcvityHandler interface 162
Acvity Manager 48
acvity state
saving 171
ADB shell
about 52
ags 52
opons 52
addr2line ulity 394
Adreno Proler 398
ADT plugin 33, 59
AInputEvent_getSource() method 286
AInputEvent_getType() method 286
AInputEvent structure 276
AInputQueue_aachLooper() 169
AInputQueue_detachLooper() 169
AKeyEvent_getAcon() 291, 296
AKeyEvent_getDownTime() 296
AKeyEvent_getFlags() 296
AKeyEvent_getKeyCode() 291, 296
AKeyEvent_getMetaState() 296
AKeyEvent_getRepeatCount() 296
AKeyEvent_getScanCode() 296
allocateEntry() 80, 94
ALooper_addFd() 169
ALooper_pollAll() behavior 158
ALooper_pollAll() method 154, 169
ALooper_prepare() 169
am command 48
AMoonEvent_getAcon() 287, 296
AMoonEvent_getDownTime() 287
[ 412 ]
AMoonEvent_getEventTime() 287
AMoonEvent_getHistoricalX() 287
AMoonEvent_getHistoricalY() 287
AMoonEvent_getHistorySize() 287
AMoonEvent_getPointerCount() 287
AMoonEvent_getPointerId() 287
AMoonEvent_getPressure() 287
AMoonEvent_getSize() 287
AMoonEvent_getX() 281, 287, 296
AMoonEvent_getY() 281, 287, 296
ANaveAcvity_nish() method 158
ANaveAcvity_onCreate() method 166-168
ANaveAcvity structure 167
ANaveWindow_Buer structure 176
ANaveWindow_lock() 177
ANaveWindow_setBuersGeometry() 176
ANaveWindow_unlockAndPost() method 177
Android
3D engine, running 369, 370
Boost, compiling on 328
device sensors, probing 298, 299
hardware sensors 273
input peripherals 273
interacng with 274
soware keyboard, displaying 297, 298
third-party libraries, porng to 338
touch events, handling 276-286
android_app_entry() method 169
about 170, 277
contextual informaon 170
android_app_write_cmd() method 167
Android debug bridge
about 51-53
le, transfering SD card from command line 53
Android Debug Bridge (ADB) 39
Android development
device, troubleshoong 42, 43
geng started 7
kit, installing, on Linux 27, 28
kit, installing on Mac OS X 20
kit, installing on Windows 12
Mac OS X, seng up 18, 19
plaorms 7
soware requisites 8
Ubuntu Linux, installing 22-26
Windows, seng up 8-12
Android development kits
installing, on Linux 27
installing, on Mac OS X 20
installing, on Windows 12
Android device
seng up, on Mac OS X 37-39
seng up, on Ubuntu 42
seng up, on Ubuntu Linux 39-41
seng up, on Windows 37-39
android_getCpuFamily() method 404
android_getCpuFeatures() method 404
Android Gingerbread 398
ANDROID_LOG_DEBUG 151
ANDROID_LOG_ERROR 151
ANDROID_LOG_WARN 151
android_main() method 154
Android Makeles 65, 66, 346
AndroidManifest.xml le 384
Android.mk le 83
android_nave_app_glue module 166
Android NDK
about 171, 383
Box2D, compiling 339-345
installing, on Ubuntu 28
installing, on Windows 13-16
Irrlicht, compiling 339-345
Android-NDK-Proler 397
android_poll_source 300
android_poll_source structure 154
android project
creang, eclipse used 56
java project, iniang 56-58
Android SDK
Android virtual device, creang 33-36
emulang 33
installing, on Mac OS X 20
installing, on Ubuntu 27
installing, on Windows 13
Android SDK tools
Android debug bridge 51
exploring 51
project conguraon tool 54
Applicaon Binary Interface. See ABI
apply() method 204
APP_OPTIM ag 384
app_process le 385
[ 413 ]
ARM DS-5 398
armeabi 404
armeabi-v7a 404
ArmV7 mode 397
ArrayIndexOutOfBoundsExcepon() 101
array types
handling 107
ASensorEventQueue_disableSensor() 305
ASensorEventQueue_enableSensor() 304
ASensorEventQueue_setEventRate() 305
ASensor_getMinDelay() 305, 311
ASensor_getName() 311
ASensor_getVendor() 311
ASensorManager_createEventQueue() 302
ASensorManager_destroyEventQueue() 302
ASensorManager_getDefaultSensor() 304
ASensorManager_getInstance() 302
asset manager 193
AachCurrentThread() 115
AudioPlayer object 256
AudioTrack 239
B
back buer 189
background music
playing 249
background thread
running 111-118
BeginContact() method 357-359, 366
beingScene() method 376
bionic 385
bitmaps
processing from nave code 135
bitmaps, processing from nave code
camera feed, decoding 136-145
BJam 328
Blender
3D modeling 381
about 381
bodies
about 353, 354
characteriscs 353, 365
body denion 354
body xture 356
Boost
about 328
compiling, on Android 328
embedding, in DroidBlaster 328-336
threading with 337, 338
URL, for documentaon 336
URL, for downloading 328
boost directory 330, 332
BOOST_FILESYSTEM_VERSION opon 330
BOOST_NO_INTRINSIC_WCHAR_T opon 330
Box2D
about 338, 353
Box2Dresources 369
collision detecon 366, 367
collision ltering 368, 369
collision modes 367, 368
compiling, with Android NDK 339-345
memory management 366
physics, simulang with 354-366
URL 339
Box2D 2.2.1 archive 339
Box2D body
about 365
b2Body 365
b2BodyDef 365
b2CircleShape 365
b2FixtureDef 365
b2PolygonShape 365
b2Shape 365
BSP 381
BSP format 370
buerize() method 224, 317
bullet mode 367
C
C 96, 315
C++ 96, 315
C99 standard library 84
callback_input() 276
callback_read() 198, 200, 203
callback_recorder() 269
callbacks 133, 134, 268
callback_sensor() 300
CallBooleanMethod() 131
[ 414 ]
CallIntMethod() 130
CallStacVoidMethod(). 130
CallVoidMethod() 130
camera feed
decoding, from nave code 136-144
cat command 52
C/C++
java, interfacing with 60
C++ class 184
C code
calling, from java 60
cd command 52
CDT 383
chmod command 52
chrominance components 143
clamp() method 141
class loader 115
clear() method 190
clock_geme() 173, 181
CLOCK_MONOTONIC 181
CMake 350
collision detecon 366, 367
collision ltering 368, 369
collision groups 369
collision modes 367, 368
Color data type 85
Color() method 141
com.example.hellojni 48
command
execung 56
com_myproject_MyAcvity.c 62
Connuous Collision Detecon (CCD) 367
connuous integraon 55
Cortex-A8 404
crash dump
about 396, 397
analysing 392-395
createDevice() method 374
CreateOutputMix() method 243
createTarget() method 354, 356
Crystax NDK
about 315
URL 315
Current Program Status Register 397
Cygwin
about 17
char return 18
D
Dalvik
introducing 59
damping 366
deacvate() method 159, 302
debuggers 392
decode() method 141
DeleteGlobalRef() 88, 106, 117
DeleteLocalRef() 95, 106
density property 353, 365
descript() method 249, 317
DetachCurrentThread() 120
device
turning, into joypad 300-308
device sensors
probing 298, 299
Dex 60
DirectX 338
Discrete Collision Detecon 367
display
connecng 186
dmesg command 52
D-Pad
about 288
detecng 288
drawCursor() method 190
DroidBlaster
about 147, 274
Boost, embedding 328-336
debugging 384-392
Gnu STL, embedding 316-326
launching 219
project structure 275
DroidBlaster.hpp le
creang 162
DroidBlaster project
creang 148
drop() method 375
dumpsys command 52
dx tool 60
E
EASTL 327
Eclipse
about 384
conguring 388, 389
[ 415 ]
installing 29-32
nave code, compiling from 67
seng up 29
Eclipse perspecves 56
Eclipse project
seng up 149
Eclipse views 56
EGL 184
eglChooseCong() 187
eglGetCongArib() 187
eglGetCongs() 187
eglGetDisplay() 186
eglInialize() 186
eglSwapBuers() 189
elapsed() method 173
Embedded-System Graphics Library. See EGL
EMF_LIGHTING ag 372
EndContact() method 366
endianness 326
endScene() method 376
event callback 266-268
EventLoop class 156
EventLoop.cpp 157
EventLoop object 160
ExceponCheck() 102, 106
ExceponDescribe() 106
ExceponOccured() 106
excepons
raising, from store 92-94
throwing, from nave code 91
F
features, 3D engine 369, 370
nalizeStore() method 111, 118
FindClass() method 122, 133
ndEntry() method 79
xed pipeline 183
xture 354
forces 366
framebuer 187
fricon property 353, 365
front buer 189
funcon inlining 346
Funcon object 98
G
GCC
about 404
opmizaon levels 346
URL, for opmizaon opons 346
GCC 3.x 336
GCC 4.x 336
GCC, opmizaon levels
-O0 346
-O1 346
-O2 346
-O3 346
-Os 346
GCC toolchain 383
GDB
about 383
nave code, debugging 384-392
gdb.setup le 385
geometrical shape 353
GetArrayLength() 102
getColor() method 88
getExternalStorageState() method 320
getHorizontal() method 279
GetIntArrayRegion() 102, 106
getInteger() 81
getJNIEnv() method 113, 115
getMyData() 60
GetObjectArrayElement() 104, 105
GetObjectClass() method 133
GetPrimiveArrayCrical() 142
GetStringUTFChars() method 79, 82, 84
gemeofday() 181
getVercal() method 279
glBindBuer() 229
glBindTexture() 203, 212
glBuerData() 229
glClear() 189
glClearColor() 189
glColor4f() 216
glDeleteTextures() 204
glDrawElements() 231
glDrawTexfOES() 212
glDrawTexOES() 219
glEnable() 220
[ 416 ]
glEnableClientState() 231
glGenBuers() 229
glGenTextures() 203
GL_LINEAR 203
global references 88
GL_OES_draw_texture 209
glPushMatrix() 231
glTexCoordPointer() 231
glTexParameteriv() 212
GL_TEXTURE_CROP_RECT_OES 212
glTranslatef() 231
glVertexPointer() 231
GNU Debugger 383
GNU STL
about 316
embedding, in DroidBlaster 316-326
Google Guava 97
Google SparseHash 327
Gprof
about 397
running 398-402
working 403
gprof ulity 402
GraphicsObject 370
GraphicsService 232
GraphicsSprite.cpp 212
GrapicsService lifecycle
about 184
start() 184
stop() 185
update() 185
gravity sensor 274
gyroscope 273
H
HDPI (High Density) screen 36
hellojni sample
compiling 46-49
deploying 46-49
hybrid java/C/C++ project
creang 67-70
I
IAnimatedMeshSceneNode 381
IBillboardSceneNode 381
ICameraSceneNode 381
ILightSceneNode 381
import-module direcve 336
index buer 220
info() method 151
inialize() method 354
inializeStore() method 111
int32_t 84
interface 248
interface ID 248
interfaces 241
intra procedure call scratch register 396
IParcleSceneNode 381
Irrlicht
about 338
compiling, with Android NDK 339-345
3D graphics, rendering with 370-381
memory management 380
scene management 381
IrrlichtDevice class 380
ISceneManager 380
isEntryValid() 79, 94
IsTouching() method 368
ITerrainSceneNode 381
IVideoDriver 380
J
Java
calling back, from nave code 122, 127-132
interfacing, with C/C++ 60
Java and nave code lifecycles
about 121
strategies, for overcoming issues 121
Java and nave threads
aaching 120
background thread, running 111-119
detaching 120
synchronizing 110
Java arrays
handling 96
object reference, saving 97-105
Java code
invoking, from nave thread 122-124
JAVA_HOME environment variable 12
javah tool
about 64
running 71
[ 417 ]
java, interfacing with C/C++
Android Makeles 65, 66
C code, calling from java 60-64
java.lang.UnsasedLinkError 64
Java objects
global reference 90, 91
local reference 90, 91
reference, saving 85-89
referencing, from nave code 85
Java primives
nave key/value store, building 75-84
primive types, passing 85
primive types, returning 85
working with 74
java project
iniang 56-58
JavaVM 112
jbooleanArray 105
jbyteArray 105
jcharArray 105
jdoubleArray 105
JetPlayer 239
joatArray 105
jintArray 101
jlongArray 105
JNIEnv 133
JNI excepons
checking 106
JNI, in C++ 96
JNI method denions 134
JNI methods
DeleteGlobalRef() 106
DeleteLocalRef() 106
ExceponDescribe() 106
ExceponOccured() 106
MonitorExit() 106
PopLocalFrame() 106
PushLocalFrame() 106
ReleasePrimiveArrayCrical() 106
Release<Primive>ArrayElements() 106
ReleaseStringChars() 106
ReleaseStringCrical() 106
ReleaseStringUTFChars() 106
JNI_OnLoad() 120
jobject parameter 85, 132
joints 353
JPEG 369
jshortArray 105
jstring parameter 79, 85
jvalue array 134
K
keyboard
detecng 288
handling 289
L
layout_height 50
layout_width 50
LDR instrucon 396
Level of Detail (LOD) 381
libc.so le 385
libpng NDK
integrang 194
libstdc++ 316
libzip 195
light sensor 274
linear acceleraon sensor 274
link register 396
linux
Android development kit, installing 27
Linux
Android device, seng up 39-41
Android NDK, installing 28
Android SDK, installing 27
seng up 22-26
loadFile() 222, 226
loadImage() method 198, 199, 203
loadIndexes() 222
loadLibrary() method 327
loadVerces() 222, 227
LOCAL_ARM_MODE variable 348
LOCAL_ARM_NEON variable 348
LOCAL_CFLAGS variable 347
LOCAL_C_INCLUDES variable 347
LOCAL_CPP_EXTENSION variable 347
LOCAL_CPPFLAGS variable 347
LOCAL_DISABLE_NO_EXECUTE variable 348
LOCAL_EXPORT_CFLAGS variable 348
LOCAL_EXPORT_C_INCLUDES 340
LOCAL_EXPORT_CPPFLAGS variable 348
LOCAL_EXPORT_LDLIBS variable 348
LOCAL_LDLIBS variable 347
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >
[ 418 ]
LOCAL_MODULE_FILENAME variable 347
LOCAL_MODULE variable 65, 347
LOCAL_PATH variable 347
local references 88
LOCAL_SHARED_LIBRARIES variable 348
LOCAL_SRC_FILES variable 179, 347
LOCAL_STATIC_LIBRARIES variable 348
LOCLOCAL_FILTER_ASM variable 348
logcat command 52
LogCat crash dump 396
Looper 169
ls command 52
luminance component 143
M
Mac OS X
and environment variables 21
Android development kit, installing 20
Android device, seng up 37-39
Android SDK, installing 20
seng up, for Android development 18, 19
mAcvityHandler event 156
magnetometer 274
Make 328
makeles
built-in funcons 349
les manipulaon funcons 349
instrucons 348
mastering 346, 350
strings manipulaon funcons 349
variables 347, 348
makeGlobalRef() ulity 126, 129
mAnimFrameCount 211
mAnimSpeed 211
max() method 141
mcount() method 403
MediaPlayer 239
memory management, Box2D 366
memory management, Irrlicht 380
memset() 178
MIME player 325
MIME source 252
mInteger 82
moncleanup() method 402
MonitorEnter() method 116
MonitorExit() method 106, 116
MonkeyRunner 55
monotonic clock 173
monotonic mer 181
monstartup() method 402
MoonEvent 277
movement constraints 353
music les
background music, playing 249-255
playing 249
N
nave acvity
about 147
basic nave acvity, creang 148-154
creang 148
NaveAcvity class 148, 154
Nave App Glue
about 166
acvity state, saving 171
Android_app structure 170
nave thread 168, 169
UI thread 167, 168
nave_app_glue module 286
nave code
compiling, from eclipse 67
Java, calling back from 122-132
debugging, with GDB 384-392
Nave glue module code
locaon 166
nave key/value store
building 75-83
NDEBUG opon 330
ndk-build command 46
ndk-gdb command 385
NDK sample applicaons
compiling 46
deploying 46
hellojni sample, compiling 46-49
hellojni sample, deploying 46-49
ndk-stack- 395
NEON 404
NewGlobalRef() 88
NewIntArray() 101
NewObject() 129
NewStringUTF() 82
nodes 381
[ 419 ]
no-strict-aliasing opon 330
now() method 173
NVidia 398
Nvidia Tegra 2 404
O
OBJ 369
objdump command 393
object 241, 248
Octree 381
onAccelerometerEvent() 313
onAcvate() method 155, 392
onAppCmd 157
onConguraonChanged event 167
onDeacvate() method 156
onDestroy event 167
onDestroy() method 155
onGetValue() method 86
onInputQueueCreated event 167
onInputQueueDestoyed event 167
onKeyboardEvent() 291
onLowMemory event 167
onNaveWindowCreated event 167
onNaveWindowDestroyed event 167
onPause event 167
onPause() method 155
onPreviewFrame() 140
onResume event 167
onResume() method 155
onSaveInstance event 167
onStart event 167
onStart() method 155
onStep() method 156, 191
onStop event 167
onStop() method 155
onTouchEvent() 276, 281
onWindowFocusedChanged event 167
OpenGL 183
OpenGL ES
about 49, 183
inializing 184-192
texture, loading 194--207
OpenGL ES 1 369
OpenGL ES 1.1 183
OpenGL ES 2 183, 369
OpenGL ES inializaon code 184
Open Graphics Library for Embedded Systems.
See OpenGL ES
OpenMAX AL low-level mulmedia API 240
OpenSL ES
about 239, 248
inializing 241
interface 248
interface ID 248
object 248
OpenSL ES engine
creang 241-247
OpenSL ES inializaon
engine, creang 241
OpenSL ES object
seng up 248
OpenSL for Embedded System. See OpenSL ES
OProle 397
opmizaon levels, GCC
-O0 346
-O1 346
-O2 346
-O3 346
-Os 346
OutputMix object 252
P
packt_Log_debug macro 150
page ipping 189
parallax eect 237
parse_error_handler() method 224
pCommand 160
PerfHUD ES 398
performance 404
performance analysis 397, 398
physics
simulang, with Box2D 354-366
PhysicsObject class 354
PID 396
playBGM() method 250, 252
playRecordedSound() 271
playSound() method 259
PNG 194, 369
png_read_image() 202
png_read_update_info() 200
[ 420 ]
PNG textures
loading, in OpenGL ES 194-208
reading, asset manager used 193
PopLocalFrame() 106
Portable Network Graphics. See PNG
Posix APIs 171
PostSolve() method 366
PowerVR 398
PREBUILT_STATIC_LIBRARY direcve 336
PreSolve() method 366
primives array types
jbooleanArray 105
jbyteArray 105
jcharArray 105
jdoubleArray 105
joatArray 105
jlongArray 105
jshortArray 105
prin() method 151
processAcvityEvent() 156, 160
process_cmd() method 169
processEntry() method 113, 117, 130
process idener. See PID
processInputEvent() method 276, 289
process_input() method 169
Program Counter 393, 397
project conguraon tool
about 54
connuous integraon 55
create project opon 54
update project opon 54
proximity sensor 274
ps command 52
pthread_key_create() 120
pthread_setspecic() 120
PushLocalFrame() 106
PVRTune 398
pwd command 52
Python 330
Q
Quake levels 370
Qualcomm 398
R
RAII 337
RapidXml library 221
RDESTL 327
Realize() method 243
recordSound() 271
RegisterCallback() method 266 266
registerEnty() method 358
registerObject() method 372
registerSound() method 259
registerTexture() 205
registerTileMap() 233
releaseEntryValue() 81, 100
ReleasePrimiveArrayCrical() 106
Release<Primive>ArrayElements() 106
ReleaseStringChars() 106
ReleaseStringUTFChars() method 79, 84, 106
Resource Acquision Is Inializaon. See RAII
Resource class 199
ResourceDescriptor class 317
ResourceDescriptor structure 250
restuon property 353, 365
rotaon vector 274
RTTI 315
run() method 152
runWatcher() method 113, 115
S
San Angeles 49
san angeles OpenGL demo
compiling 49
tesng 49, 50
scene management, Irrlicht 381
screen rotaon
handling 312
SD-Card access 43
segmentaon fault 393
sensor 368
Serializaon module 330
setAnimaon() 210, 211
setColorArray() 104
setColor() method 88
SetIntArrayRegion() 101, 102
[ 421 ]
setInteger() 81
setjmp() 200
SetObjectArrayElement() 103, 105
SetStringUTFChars() method 82
setTarget() method 359
setup() method 375
shape 354, 365
shared libraries
versus stac libraries 326
Ship class 217
ship.png sprite 217
SIMD instrucon set 404
simple Java GUI 74
Single Instrucon Mulple Data (SIMD) 404
skinning 370
SLAndroidSImpleBuerQueueI interface 269
slCreateEngine() method 242
SLDataLocator_AndroidSimple
BuerQueue() 261, 270
SLDataSink structure 252
SLDataSource structure 252
SL_IID_PLAY interface 253
SL_IID_SEEK interfaces 253
SLObjectI instance 242
SLObjectI object 249
SLPlayI interface 250
SLRecordI interface 269
SLSeekI interface 250
soware keyboard
displaying 297, 298
SoudService.hpp 259
sound buer queue
creang 257-259
playing 260-265
SoundPool 239
sounds
playing 256, 257
recorded buer, playing 271
recording 268-271
sound buer queue, creang 257-266
sound buer queue, playing 260-265
spawn() method 322
spin() method 371
sprite
drawing 208
Ship sprite, drawing 209-219
sprite images
eding 209
stack pointer 396
stack trace analysis 392
Standard Template Library (STL)
about 316
performances 327
start() method 279, 280
startSoundPlayer() method 259, 260
startWatcher() method 113, 127
stac libraries
versus shared libraries 326
status startSoundRecorder() 269
stay awake opon 38
Step() method 358
STLport 316
stopBGM() method 250, 254
stopWatcher() method 116
StoreAcvity class 76
StoreListener interface 122
StreamLine 398
stringToList () 98
Subversion(SVN) 55
surfaceChanged() method 138
System.loadLibrary() 120
T
terrain rendering 370
texel 227
texture
loading, in OpenGL ES 194-207
third-party libraries
porng, to Android 338
thread idener. See TID
threading 268, 338
Threading Building Block library 338
ThrowNew() 95
throwNotExisngExcepon() 95
thumb 403
TID 396
led map editor 220
le map
about 220
rendering, vertex buer objects used 220
le-based background, drawing 221-237
[ 422 ]
le map technique 237
mer
about 181
implemenng 172-179
toInt() method 141
torques 366
touch events
analyzing 279
handling 276-291
trackball
detecng 288
handling 289
triple buering 189
U
UI thread 167
Unix File Descriptor 169
unload() method 204
update() method 189, 290, 322, 354, 358, 372
userData 157
userData eld 355
UV coordinates 227
V
Valgrind 397
vertex 220
Virtual Machine 59
void playRecordedSound() 269
void recordSound() 269
VSync 189
W
watcherCounter 111
window and me
accessing, navely 171
raw graphics, displaying 172-179
Windows
Android development kit, installing 12
Android device, seng up 37-39
Android NDK, installing 14, 15
Android SDK, installing 13, 15
Ant, installing 11
environment variables 14-16
seng up, for Android development 8-12
X
x86 404
xml_document instance 224
Y
YCbCr 420 SP (or NV21) format 143
Z
Zygote 385
Zygote process 60
Thank you for buying
Android NDK Beginners Guide
About Packt Publishing
Packt, pronounced 'packed', published its rst book "Mastering phpMyAdmin for Eecve MySQL
Management" in April 2004 and subsequently connued to specialize in publishing highly focused
books on specic technologies and soluons.
Our books and publicaons share the experiences of your fellow IT professionals in adapng and
customizing today's systems, applicaons, and frameworks. Our soluon-based books give you the
knowledge and power to customize the soware and technologies you're using to get the job done.
Packt books are more specic and less general than the IT books you have seen in the past. Our unique
business model allows us to bring you more focused informaon, giving you more of what you need to
know, and less of what you don't.
Packt is a modern, yet unique publishing company, which focuses on producing quality, cung-edge
books for communies of developers, administrators, and newbies alike. For more informaon, please
visit our website: www.PacktPub.com.
Wring for Packt
We welcome all inquiries from people who are interested in authoring. Book proposals should be sent
to author@packtpub.com. If your book idea is sll at an early stage and you would like to discuss
it rst before wring a formal book proposal, contact us; one of our commissioning editors will get in
touch with you.
We're not just looking for published authors; if you have strong technical skills but no wring
experience, our experienced editors can help you develop a wring career, or simply get some
addional reward for your experse.
Android User Interface Development
ISBN: 978-1-84951-448-4 Paperback:304 pages
Quickly design and develop compelling user interfaces for
your Android applicaons
1. Leverage the Android plaorm's exibility and
power to design impacul user-interfaces
2. Build compelling, user-friendly applicaons that
will look great on any Android device
3. Make your applicaon stand out from the rest
with styles and themes
4. A praccal Beginner's Guide to take you
step-by-step through the process of developing
user interfaces to get your applicaons noced!
Android Applicaon Tesng Guide
ISBN: 978-1-84951-350-0 Paperback: 332 pages
Build intensively tested and bug free Android applicaons
1. The rst and only book that focuses on tesng
Android applicaons
2. Step-by-step approach clearly explaining the most
ecient tesng methodologies
3. Real world examples with praccal test cases that
you can reuse
Please check www.PacktPub.com for information on our titles
Android 3.0 Animaons
ISBN: 978-1-84951-528-3 Paperback:304 pages
Bring your Android applicaons to life with stunning
animaons
1. The rst and only book dedicated to creang
animaons for Android apps.
2. Covers all of the commonly used animaon
techniques for Android 3.0 and lower versions.
3. Create stunning animaons to give your Android
apps a fun and intuive user experience.
4. A step-by-step guide for learning animaon by
building fun example applicaons and games.
Android 3.0 Applicaon Development
Cookbook
ISBN: 978-1-84951-294-7 Paperback: 272 pages
Over 70 working recipes covering every aspect of Android
development
1. Wrien for Android 3.0 but also applicable to lower
versions
2. Quickly develop applicaons that take advantage of
the very latest mobile technologies, including web
apps, sensors, and touch screens
3. Part of Packt's Cookbook series: Discover ps and
tricks for varied and imaginave uses of the latest
Android features
Please check www.PacktPub.com for information on our titles
Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >Downloa d f r o m W o w ! e B o o k < w w w.woweb o o k . c o m >

Navigation menu