Phil Hassey - game dev blog
Phil Hassey as Syndrome
"Look, stores don't sell
costumes like this to just anyone."

Android Day 3: Packaging, Assets, JNI, and OpenGL

I considered using SDL for this port, however, I became concerned that none of the ports were quite ready for prime time. None of them have been really released as complete and stable packages, and there are enough weird things about Android that I think I’ll be better off doing the work directly myself. Galcon is an “OpenGLES” app in essence, so much of what SDL provides is unnecessary anyways. But if you are interested:

There is a summer of code project relating to this:

http://socghop.appspot.com/gsoc/student_project/show/google/gsoc2010/sdl/t127230762779
http://hg.libsdl.org/SDL-gsoc2010_android/summary

And there’s also another SDL port here:

http://www.anddev.org/code-snippets-for-android-f33/sdl-port-for-android-sdk-ndk-1-6-t9218.html

Step 1 – Using command line tools instead of the Eclipse IDE:

I also decided that avoiding Eclipse might be a good move. I’m sure there are those of you who would argue otherwise. But, err, I’m a CLI kind of guy, so we’ll see how it goes.

First I added ~/android/tools to my path, so that I wouldn’t have to keep typing in the whole path 🙂

In my Galcon android folder, I ran this command:

$ android create project -t “android-4” -k com.igalcon.galcon -a Galcon -n Galcon -p .

Which created my other project files.

Then to create a debug build of Galcon:

$ ant debug

Then to install it onto my device:

$ adb devices

List of devices attached

emulator-5554 device

deviceid device

$ adb -s install bin/Galcon-debug.apk

And the app is installed, and I can run it. However, at this point I’ve just got a Hello World thing showing up, so I’ve got a long way to go until I’ve got Galcon appearing 🙂

Step 2 – Including resources (graphics, audio) with your game:

There are a number of options as to how to bring data long for your game. A few are explained on this page:

http://developer.android.com/guide/topics/resources/providing-resources.html

I made a folder called “assets” and placed all my game assets in it. Instead of mp3s, I include ogg files. And instead of .wav, I include ogg files. On the Android handhelds, there is only 256 MB of internal storage. Many games just pack the binary and then download the data separately onto the users flash card. I deem that to be totally unawesome and will ship Galcon with all its data. But I will do my best to pack as little data into my binary as possible. By using oggs instead of mp3s and wavs, I’ve trimmed a couple MB off my data size.

UPDATE: App can request to be stored on the SD card! http://developer.android.com/guide/appendix/install-location.html

To do this, add android:installLocation=”preferExternal” to the manifest tag in AndroidManifest.xml .. And while you’re at it, add in <uses-sdk android:minSdkVersion=”4″ /> to require OpenGLES 1.1 support. Also change target=android-8 in default.properties.  This will give a warning since your minSdkVersion and your target aren’t the same.  Just read that link above for more info.

If you save your files in “res” folders, some kind of Android magic kicks in and it seems you can’t reference anything by a String. You have to use Java IDs, which makes it really hard for C code to say “get me file X” .

Files saved in the assets/ directory are not given a resource ID, so you can’t reference them through theR class or from XML resources. Instead, you can query files in the assets/ directory like a normal file system and read raw data using AssetManager.

At this point, I’ve got a basic project building, and I’ve got assets included in my package. (I can tell this because my package is 3MB, not 200k like a empty Hello World example.) I think 3MB is an acceptable size.

Step 3 – Debugging the game:

Next I need to figure out how to get debugging working. I’m pretty sure I’m going to have bugs, so .. doing on-device debugging will be ideal. Again, if I were to take the wimp’s way out, I’d use Eclipse (I guess? for me actually, Eclipse seems like a greater learning curve..) but I’ll be using CLI methods to debug. I’ve edited Galcon.java and added a Divide by Zero bug, so I can see how it appears. And upon installing it, it crashes.

As a side note, to replace an app you’ve already installed, this command:

$ adb -s install -r bin/Galcon-debug.apk

Error that appears on my device: “Sorry! The application Galcon (process com.galcon.igalcon) has supped unexpectedly. Please try again.”

To debug:

$ adb -s logcat

And I get to see a lovely dump of errors as I have them:

E/AndroidRuntime( 1960): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.galcon.igalcon/com.galcon.igalcon.Galcon}: java.lang.ArithmeticException: divide by zero

Here’s a link to a page that talks more about adb, getting a shell on the device, and other fun goodies:

http://developer.android.com/guide/developing/tools/adb.html

Step 4 – Getting Java to call C functions:

Now I’m going to try and get Java to call out to the C / C++ code I built yesterday. At this point I’m going to send a thanks out to James Brown (Ancient Frog) who lent me a few lines of his attempt at an Android port. There are lots of tutorials and documentation on-line for JNI stuff, but seeing real code that is in the ballpark of what I want to do is a huge help. I’ll be posting a few snippets as I go in this blog, but if you do want to really learn about JNI, there is a ton of information.

First of all, in Galcon.java, in my Galcon class, I’ve sprinkled a handful of references to native functions:

public native void init();
public native void loop();
public native void paint();

In onCreate() I’ve added a call to init().

And then I created a myjni.cpp file which I filled with templates like this. I love all the ludicrous namespacing. I’m not sure who it is helping, but I’m pretty sure it isn’t me.

JNIEXPORT void JNICALL Java_com_galcon_igalcon_Galcon_loop(JNIEnv* env) { /* do stuff here */ }

After building and attempting to run I get this error from logcat:

W/dalvikvm( 2168): No implementation found for native Lcom/galcon/igalcon/Galcon;.init ()

I definitely need to add myjni.cpp to my jni/Android.mk and re-run ndk-build.

Tip: if you want to do some logging to the Android message log:

#include
#define MYLOG(msg) __android_log_write(ANDROID_LOG_ERROR,"MYLOG()",msg);

Seems to do the trick.

I’m still getting the error, so I think my .so isn’t being loaded. I add this before I call init to load the shared library:

System.loadLibrary("igalcon2");

I get a NEW error:

E/AndroidRuntime( 2255): java.lang.UnsatisfiedLinkError: Library igalcon2 not found

I guess libigalcon2.so isn’t being packaged. Gotta check into this. An unzip -v of the package reveals that libigalcon2.so is being included in the package. But I found this error in logcat:

W/dalvikvm( 2338): JNI_OnLoad returned bad version (0) in /data/data/com.galcon.igalcon/lib/libigalcon2.so 0x449e1260

I guess I need to make sure I’m returning the right version in my JNI_OnLoad function. This is my function:

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
  JNIEnv* env;
  if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) {
      MYLOG("JNI_OnLoad(): GetEnv failed");
      return -1;
  }
  return JNI_VERSION_1_4;
}

That seemed to work fine. So I’ve got JNI up and running and talking to my C code. I was able to get my various JNI functions calling my Galcon functions with no trouble.

Step 5 – Setting up an OpenGL context in Java:

logcat reveals that I’m getting a TON of OpenGLES errors similar to: “E/libEGL ( 2463): call to OpenGL ES API with no current context (logged once per thread)” .. This is pretty self explanatory. Time to figure this one out.

This page seems to reveal how to set up an OpenGL context:

http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/graphics/GLSurfaceViewActivity.html

And this seems to be how to set up and OpenGL renderer:

http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/graphics/CubeRenderer.html

Sort of bodging those together got me pretty far. At least, I know my GLES code is being called and I’m not seeing errors.

Step 6 – Getting C to call a Java method (to load a texture):

I gotta get my texture images loading up so that my paint code is rendering something a bit more interesting to the screen. In this case instead of Java calling C, I need my C code calling Java code. So it’s like JNI in reverse. Or something. Here’s the documentation on doing just that:

http://java.sun.com/developer/onlineTraining/Programming/JDCBook/jniref.html#call

You’ll note that no matter how we do this, we gotta have access to an “env” or something. I’m going to set up some kind of awful global variable that will be available to all my code so that I can use “env” to do stuff like this. I put this at the top of my JNI calls:

JNIEnv *_my_jnienv = 0;
void set_jnienv(JNIEnv *env) { _my_jnienv = env; }
JNIEnv *env *get_jnienv() { return _my_jnienv; }

And added set_jnienv(env); to the top of each call. Now in my other “driver” code I can call get_jnienv() to get it back.

Here’s my dummy load_texture method on the Java side:

    public static int load_texture(String fname) {
        Log.v("load_texture",fname);
        return 0;
    }

Here’s my C function that calls it:

int jni_load_texture(const char *s) {
    JNIEnv *env = get_jnienv();
    jclass cls = env->FindClass("com/galcon/igalcon/Galcon");
    jmethodID mid = env->GetStaticMethodID(cls,
	       "load_texture",
	       "(Ljava/lang/String;)I");
    // there could be some exception handling happening here, but there isn't
    jint ret;
    jstring mystr = env->NewStringUTF(s);
    ret = env->CallStaticIntMethod(cls, mid, mystr);
    return ret;
}

Pretty gruesome, but it seems to get the job done. Now if I can fill in my java load_texture method with something that works, I think I’ll have visuals! Here it is:

import android.graphics.BitmapFactory;
import android.graphics.Bitmap;
import android.content.res.AssetManager;
import android.opengl.GLUtils;
import android.opengl.GLES10;
import java.io.InputStream;
import java.io.IOException;

// gl is a GL10 instance, grabbed from within my renderer.  app is my Galcon Activity.

    public static int load_texture(String fname) {
        Log.v("load_texture",fname);

        AssetManager am = app.getAssets(); //new android.content.res.getAssets();

        try {
            InputStream stream = am.open(fname);
            Bitmap bitmap = BitmapFactory.decodeStream(stream);
            int[] textures = new int[1];
            gl.glGenTextures(1, textures, 0);
            int textureID = textures[0];
            gl.glBindTexture(GL10.GL_TEXTURE_2D, textureID);

            // no mipmaps
            gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
            gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);

            GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);

            return textureID;
        } catch(IOException e) {
//             Log.v("load_texture:open() fail");
            return 0;
        }
    }

Not trivial, but this saves me from having to decompress the files in C land. I currently don’t have any image decompression code that I use regularly so this works for me.

6 Responses to “Android Day 3: Packaging, Assets, JNI, and OpenGL”

  1. Simon Brunning Says:

    “Many games just pack the binary and then download the data separately onto the users flash card. I deem that to be totally unawesome”

    Speaking as an android owner with nearly no internal storage, but bags of room on my SD card, I have to disagree. 😉 In fact, it might very well make the difference between my buying a game and not. Which would be a shame, since I love Galcon on my Mac.

    Better still, make the whole thing externally installable: http://developer.android.com/sdk/android-2.2.html#install-loc

  2. philhassey Says:

    @Simon – awesome link, I’m going to try and do that! Thanks a ton!

  3. philhassey Says:

    I’ve added that link into the main blog post.

  4. tomhiggins Says:

    Getting game data, if not the game itself, onto the SD card is VITAL for apps to do on the android. There are so many apps I like to run/have handy and a couple of GB free on the sd….I hear 2.2 is out for the Droid this Friday…I am very much looking forward to that feature.

  5. tomhiggins Says:

    btw, I am loving the daily reporting and progress. It is impressive and as a Droid owner/Galcon player want to thank you for doing this.

  6. philhassey Says:

    2.2 this Friday, ‘eh? That’ll be nice 🙂