/android



This page is dedicated to native application development for Android (primarily using C/C++).

Note that much of this information was written before the first NDK was released, and is now largely obsolete for most practical purposes.



Contents

  1. Hello, world
  2. Reading the keypad
  3. Building SDL
  4. Dynamic linking
  5. Using OpenGL ES
  6. Debugging
  7. Bitmap color reduction and GIF encoding
  8. A practical example



Required software


Optional software




Hello, world

Let's start off with the trivial "Hello, world!" example. There's nothing special about doing this on Android, other than perhaps how you compile and deploy the program.

hello.c

#include 

int main(int argc, char **argv)
{
	printf("Hello, world!\n");
	return 0;
}

Compile with arm-none-linux-gnueabi-gcc -static -o hello hello.c

After starting the Android device/emulator you need to push your program to the file system using the adb tool located in the tools directory of the Android SDK.
From the command line, do:

adb push hello /data/misc/hello

You can push the program to some other directory if you prefer. If adb complains about the file system being read-only (or "no such file or directory"), try remounting it in read-write mode. For example, if you wanted to be able to push the program to somewhere in /system:

adb shell mount -o remount,rw -t yaffs2 /dev/block/mtdblock0 /system

The next thing to do is to make sure that your program has the right attributes to be executed:

adb shell chmod 777 /data/misc/hello

Then you can execute your program through the shell:

adb shell /data/misc/hello



Reading the keypad

Getting input from the shell can be done simply by reading from stdin. It might be more interesting, however, to get input from the device's keypad, and this requires some extra steps. The keypad is located at /dev/input/event0, so what you want to do is read from that device. Below I show one way of doing this:

andkeys.h

#ifndef ANDKEYS_H
#define ANDKEYS_H

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

typedef struct {
        struct timeval time;
        unsigned short type;
        unsigned short code;
        unsigned int value;
} input_event;


// Opens the Android keypad device for reading
void andkeys_open();


// Polls the Android keypad device for new data. Returns 0 if there was no new data.
// Otherwise non-zero is returned and the input_event struct pointed to by 'ie' is
// filled with the latest data.
int andkeys_get(input_event *ie);


#endif

andkeys.c

#include "andkeys.h"

static int input;


void andkeys_open() {
	// Opens /dev/input/event0, which should be the keypad.
	// Uses non-blocking mode since we don't want andkeys_get to wait until there
	// is any data available.
	input = open("/dev/input/event0", O_RDONLY|O_NONBLOCK);
}


int andkeys_get(input_event *ie) {
	ie->code = 0;

	// Read 16 bytes from /dev/input/event0
	read(input, ie, sizeof(input_event));

	if (ie->code) {
		return 1;
	}

	return 0;
}

The way you'd use this is to call andkeys_open() once when your program starts. Then you'd repeatedly call andkeys_get() whenever you want to poll for new keypad data.
Note that I'm using non-blocking I/O, so that andkeys_get() won't wait for new data to arrive, but rather return zero if no data was available to mean "no new data". It might be desired in some cases to have a function that waits for a key press. In such cases you can open the keypad device without using the O_NONBLOCK flag.

If andkeys_get() returned 1 there's a new keypad event available in the struct you passed to it. If the value member contains 1 it means that a key was pressed, and if it contains 0 it means a key was released. The key scancode will be in the code member (some are the same as on standard PC keyboards, others are not).

Supposedly you can get touchscreen events from /dev/input/event1, though I've had no luck with that so far.



Building SDL

Instead of just outputting text to the shell you may want to show something on the Android device's display. This can be done easily with the SDL library. And as it turns out, compiling SDL for Android is a fairly simple task.

Begin by downloading the SDL source code from http://www.libsdl.org. I'm using version 1.2.13, so the building procedure might differ slightly if you use some other version.

Assuming you've got everything in a directory named SDL-1.2.13, you should start by changing the contents of SDL-1.2.13/Makefile.minimal to use the right version of GCC, the right compiler flags and the right source code directories.

Makefile.minimal

# Makefile to build the SDL library

INCLUDE = -I./include
CFLAGS = -g -O2 $(INCLUDE) -static
CC = arm-none-linux-gnueabi-gcc
AR = arm-none-linux-gnueabi-ar
RANLIB = arm-none-linux-gnueabi-ranlib

CONFIG_H = include/SDL_config.h
TARGET = libSDL.a
SOURCES = \
	src/*.c \
	src/audio/*.c \
	src/cdrom/*.c \
	src/cpuinfo/*.c \
	src/events/*.c \
	src/file/*.c \
	src/joystick/*.c \
	src/stdlib/*.c \
	src/thread/*.c \
	src/timer/*.c \
	src/video/*.c \
	src/joystick/dummy/*.c \
	src/cdrom/dummy/*.c \
	src/thread/generic/*.c \
	src/timer/unix/*.c \
	src/loadso/dummy/*.c \
	src/video/fbcon/*.c \
	src/audio/dma/*.c \
	src/audio/dsp/*.c \

OBJECTS = $(shell echo $(SOURCES) | sed -e 's,\.c,\.o,g')

all: $(TARGET)

$(TARGET): $(CONFIG_H) $(OBJECTS)
	$(AR) crv $@ $^
	$(RANLIB) $@

clean:
	rm -f $(TARGET) $(OBJECTS)

Next, you should modify SDL-1.2.13/include/SDL_config_minimal.h as shown below.

SDL_config_minimal.h

/*
    SDL - Simple DirectMedia Layer
    Copyright (C) 1997-2006 Sam Lantinga

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    Sam Lantinga
    slouken@libsdl.org
*/

#ifndef _SDL_config_minimal_h
#define _SDL_config_minimal_h

#include "SDL_platform.h"

/* This is the minimal configuration that can be used to build SDL */

#include <stdarg.h>

typedef signed char int8_t;
typedef unsigned char uint8_t;
typedef signed short int16_t;
typedef unsigned short uint16_t;
typedef signed int int32_t;
typedef unsigned int uint32_t;
typedef unsigned int size_t;
typedef unsigned long uintptr_t;

#define SDL_AUDIO_DRIVER_OSS 1

#define SDL_CDROM_DISABLED 1

#define SDL_JOYSTICK_DISABLED 1

#define SDL_LOADSO_DISABLED 1

#define SDL_THREADS_DISABLED 1

#define SDL_TIMER_UNIX 1

#define SDL_VIDEO_DRIVER_FBCON 1

#define HAVE_STDIO_H 1

#endif /* _SDL_config_minimal_h */

You also need to make a few changes to SDL-1.2.13/src/video/fbcon/SDL_fbvideo.c. On lines 191 and 499, change "/dev/fb0" to "/dev/graphics/fb0".

If you get a compilation error saying that asm/page.h doesn't exist, comment or remove line 34 (#include <asm/page.h>).
Do the same with line 163 (#error Can not determine system page size).

Build the SDL library by entering the SDL-1.2.13 directory and executing

make -f Makefile.minimal.

Depending on the type of Android device you're using, some things may or may not work at runtime. For example, I was unable to set a 640x480 32-bit video mode when running my application in the Android emulator. Both 320x240 16-bit and 320x240 8-bit worked fine though.




Dynamic linking

In order to cut down the size of your executable you may want to use dynamic linking against libraries instead of static linking. This is possible to do, although it may require changes to your code as the Android version of libc is non-standard and doesn't implement some things that are available in other versions.

You'll have to copy the library files you want to link against to your harddrive. If it's just a couple of libraries you can pull them from the emulator using adb:

adb pull /system/lib/libm.so ./libm.so

Or whatever library it is that you want to use.

Alternatively you can get all the libraries by extracting system.img with the unyaffs tool. There should be a system image available in the Android SDK (in platforms/android-x.y/images). Or you can pull it from the device using the yaffs2 tool:

adb push mkfs.yaffs2 /data/misc/mkfs.yaffs2
adb shell
# cd /data/misc
# ./mkfs.yaffs2 /system /system.img
# exit
adb pull /system.img ./system.img

Copy the library files to some directory on your harddrive, like ~/android/system/lib. You'll also need an additional C file with the entry point function:

start.c

#include <stdlib.h>

extern int main(int argc, char **argv);

void _start(int argc, char **argv)
{
	exit (main (argc, argv));
}

The linking procedure then becomes:

arm-none-linux-gnueabi-ld --entry=_start --dynamic-linker /system/bin/linker
-nostdlib -rpath /system/lib -rpath ~/android/system/lib
-L ~/android/system/lib [libraries] [object files] start.o

Where [libraries] are the libraries to link against, e.g -lc -lm. And [object files] are all the object files for your program. Don't forget to remove -static when compiling them.



Using OpenGL ES

You may want to read the previous section about dynamic linking since some of the things mentioned in it are needed here.


Download the test program

The required libraries can be obtained using the methods described in the previous section (i.e. extract or pull /system/lib). The header files can be downloaded from here (or here).

main.c

#include <stdio.h>
#include <stdlib.h>

#include <egl.h>

NativeWindowType displayWindow;

const EGLint config16bpp[] =
{
EGL_RED_SIZE, 5,
EGL_GREEN_SIZE, 6,
EGL_BLUE_SIZE, 5,
EGL_NONE
};

GLfloat colors[3][4] =
{
    {1.0f, 0.0f, 0.0f, 1.0f},
    {0.0f, 1.0f, 0.0f, 1.0f},
    {0.0f, 0.0f, 1.0f, 1.0f}
};

GLfloat vertices[3][3] =
{
    {0.0f, 0.7f, 0.0f},
    {-0.7f, -0.7f, 0.0f},
    {0.7f, -0.7f, 0.0f}
};


void draw_tri()
{
    glViewport(0,0,displayWindow->width,displayWindow->height);

    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    glEnableClientState(GL_COLOR_ARRAY);
    glEnableClientState(GL_VERTEX_ARRAY);

    glColorPointer(4, GL_FLOAT, 0, colors);
    glVertexPointer(3, GL_FLOAT, 0, vertices);

    // Draw the triangle (3 vertices)
    glDrawArrays(GL_TRIANGLES, 0, 3);

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);
}


int main(int argc, char** argv)
{
    EGLint majorVersion, minorVersion;
    EGLContext eglContext;
    EGLSurface eglSurface;
    EGLConfig eglConfig;
    EGLDisplay eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    int numConfigs;

    // Window surface that covers the entire screen, from libui.
    displayWindow = android_createDisplaySurface();

    eglInitialize(eglDisplay, &majorVersion, &minorVersion);
    printf("GL version: %d.%d\n",majorVersion,minorVersion);


    printf("Window specs: %d*%d format=%d\n",
     displayWindow->width,
     displayWindow->height,
     displayWindow->format);

    if (!eglChooseConfig(eglDisplay, config16bpp, &eglConfig, 1, &numConfigs))
    {
    	printf("eglChooseConfig failed\n");
    	if (eglContext==0) printf("Error code: %x\n", eglGetError());
    }

    eglContext = eglCreateContext(eglDisplay,
     eglConfig,
     EGL_NO_CONTEXT,
     NULL);
    printf("GL context: %x\n", eglContext);
    if (eglContext==0) printf("Error code: %x\n", eglGetError());

    eglSurface = eglCreateWindowSurface(eglDisplay,
     eglConfig,
     displayWindow,
     NULL);
    printf("GL surface: %x\n", eglSurface);
    if (eglSurface==0) printf("Error code: %x\n", eglGetError());

    eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);


    while (1)
    {
    	draw_tri();
    	eglSwapBuffers(eglDisplay, eglSurface);
    }


    return 0;
}

If you get a linker error about -lGLESv1_CM missing when trying to build the test program, try linking against -lGLES_CM instead.



Debugging

By running a gdbserver (see the "Optional software" list at the top of the page) in the Android emulator it's possible to debug a native application, running in the emulator, using gdb. Here's a brief description of the process, assuming a Unix-like environment:

Begin by pushing your application to the file system using adb (make sure that you build the application with debug info enabled). Then run the following commands:

adb push gdbserver /data/misc
adb shell chmod 777 /data/misc/gdbserver
adb shell
# cd /data/misc
# ./gdbserver 10.0.2.2:1234 /data/misc/MyProgram

Change MyProgram to whatever your application is named, and add any arguments that you want to pass to your application.
Then open another terminal and run

telnet localhost 5554

Where you substitute 5554 for whatever port the Android emulator happens to be using (should be in the emulator's title bar). When running this command you should get some output to the terminal that ends with something like

Android Console: type 'help' for a list of commands
OK

Then do this while still in telnet:

redir add tcp:1234:1234

Open a third terminal and do the following:

arm-none-linux-gnueabi-gdb ./MyProgram
(gdb) target remote localhost:1234
(gdb) b
(gdb) c

This should start your application in the emulator.



Bitmap color reduction and GIF encoding

I've written a small native lib for Android to do color quantization (from 2-256 colors) of a Bitmap and save the result as a frame in an animated GIF (you can add as many frames as you like).
You may hack and slash the library code as you wish to fit your needs. The color quantizer used is Anthony Dekker's NeuQuant, with some modifications made by me.

Download the library source code

The Java code to use the library would be something like:

static {
	System.loadLibrary("gifflen");
}

....

public native int Init(String gifName, int w, int h, int numColors, int quality,
                       int frameDelay);
public native void Close();
public native int AddFrame(int[] inArray);

....


// Filename, width, height, colors, quality, frame delay
if (Init("/sdcard/foo.gif", width, height, 256, 100, 4) != 0) {
	Log.e("gifflen", "Init failed");
}

int[] pixels = new int[width*height];
// bitmap should be 32-bit ARGB, e.g. like the ones you get when decoding
// a JPEG using BitmapFactory
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);

// Convert to 256 colors and add to foo.gif
AddFrame(pixels);

Close();


References