Translate

Wednesday, May 23, 2012

How to use Makefile wildcards/globbing on Android NDK JNI

Here is a short little thing I ran into a few days ago.  Many of you may know how a normal Android Makefile looks, something like this:

LOCAL_PATH := $(call my-dir)
MY_PATH := $(LOCAL_PATH)
TARGET_ABI      := android-4-armeabi
include $(CLEAR_VARS)


LOCAL_MODULE    := libmain


LOCAL_C_INCLUDES := \
  $(LOCAL_PATH)/Box2D \
  $(LOCAL_PATH)/libpng \
  $(LOCAL_PATH)/libzip \


LOCAL_CFLAGS := \
  -g3 \
  -ggdb \
  -gstabs+ \
  -DANDROID_NDK \


# Look here!
LOCAL_SRC_FILES := \
  main.cpp  \
  cool_file.cpp  \
  
LOCAL_LDLIBS := -lGLESv1_CM -llog -lz
LOCAL_STATIC_LIBRARIES := libBox2D libzip libpng


include $(BUILD_SHARED_LIBRARY)


Ok, notice the section LOCAL_SRC_FILES .  If you have many files in your project this could grow huge.  O(N) where N is the number of files.  I am way to lazy to type all that (though vim does this with :read !ls jni/, but still). I want a better solution.

Makefile Wildcards:


Ah, remember Makefile wildcards?  Yeah, just like command line globs, they can pull in every file that matches your globbing pattern to pull in multiple files.  This is really handy for us, since all our C++ files will be *.cpp (or whatever you like to suffix them with).

So we know from the android docs that LOCAL_PATH is applied to the prefix of all files in LOCAL_SRC_FILES.

So we could (incorrectly) try this:

Attempt 1:


LOCAL_SRC_FILES :=  \
  $(wildcard  *.cpp)

We would assume this would pick up both jni/main.cpp and jni/cool_file.cpp.  But oh no Android make system... you crazy guy, you.  For some reason it globs NOTHING!

Well, ok lets try appending the whole path you say, great let's do that:

Attempt 2:


LOCAL_SRC_FILES :=  \
  $(wildcard  $(LOCAL_PATH)/*.cpp)


Error!  Now make complains about not finding jni/jni/main.cpp.  I didn't mistype that, it appended the prefix twice.  What the heck is that all about?  So it doesn't glob the files if I don't prepend the LOCAL_PATH (to something that should already have LOCAL_PATH prepend to it).

Ok, lets do this, lets prepend the LOCAL_PATH, just like last time, so Make globs the files, but then lets clean up their LOCAL_PATH mess at the end so it will actually compile them.

We do this by deleting the extra "jni/" with a call to "subst".  Then when the Android Make system calls g++, it will have the corrected path without the extra jni.

Attempt 3 (Winner!):


#---------------------------------------------------------
# Nonsense, for some reason if I use a wildcard without local
#   path, it doesn't find the source files.
#     If I do it appends LOCAL_PATH to the prefix twice!
#---------------------------------------------------------
MY_LOCAL_SRC_FILES := \
  $(wildcard $(MY_PATH)/*.cpp)


# This fixes the problem!
LOCAL_SRC_FILES := \
  $(subst jni/, , $(MY_LOCAL_SRC_FILES))


Yay, that did it!  Now we are compiling all cpp files in the jni directory and we don't even have to add them to the Makefile as new ones are created.

Cool!  Happy coding!

You can check out a working example here:
http://code.google.com/p/razzlegames-android-ndk-tutorial/source/browse/trunk/jni/Android.mk

Saturday, May 19, 2012

Development on Android NDK Tutorial - Gluing C++ Native Code to Java

Too busy to draw this time.. for now here's a picture of an Android Toy



Ok, in this post I am going to talk about how to join C++ native code to a thin Android Java code layer.  I am not going to be doing all native code, as I think the easiest route is to use the Android higher level convenience SDK for loading resources/file I/O, setting up OpenGL context and other state management.

I won't be using any IDEs or anything else to complicate things.  This is as easy as it can get, you get the source and type "make" (I assume you are on a system with GNU make, and your environment is setup correctly.  Linux is easiest, but it should run fine on ANY system that can run Make, even Windows!).

So the code will be structured like this:

-----------------------------------
 Android SDK OS Layer code
-----------------------------------
  Java App Thin Layer to Android OS
-----------------------------------
  Native C++ code called by Java
  for each frame                          
-----------------------------------


The Native C++ code is where we are going to be doing the meat of our programming.  This is where we manage any OpenGL calls (graphics stuff, any local matrix manipulations etc), collision detection (borrowing Box2D for this example) and game state management.

I have posted a repository for this posting here, so you can check out the code and play.  I tried to keep it as simple as possible but feel free to ask questions.

You will notice the typical OpenGL context skeleton code in this example (under Physics2d.java), it is something like this:

  ...
 31 public class Physics2d extends Activity
 32 {
 33
 34   private GLSurfaceView glSurface;
 35   private MyRenderer mRenderer;
 36
 37
 38   @Override 
 39   public void onCreate(Bundle savedInstanceState)
 40   {
 41     super.onCreate(savedInstanceState);
 42
 43     glSurface = new GLSurfaceView(this);
 44     mRenderer = new MyRenderer(getApplication());
 45     glSurface.setRenderer(mRenderer);
 46
 47     // Turn off app title
 48     requestWindowFeature(Window.FEATURE_NO_TITLE);
 49
 50     getWindow().setFlags(
 51         WindowManager.LayoutParams.FLAG_FULLSCREEN,
 52         WindowManager.LayoutParams.FLAG_FULLSCREEN);
 53
 54     setContentView(glSurface);
 55
 56     Display display = getWindowManager().getDefaultDisplay();
 57
 58     Log.e("Screen Size", "Screen dimensions: {" +
 59         display.getWidth() + ", " +
 60         display.getHeight() +
 61         "}");
 62   }
 63
 64   @Override
 65   public void onResume()
 66   {
 67     super.onResume();
 68     glSurface.onResume();
 69   }
 70
 71   @Override 
 72   public void onPause()
 73   {
 74     super.onPause();
 75     glSurface.onPause();
 76   }
 77
 78   static
 79   {
 80     System.loadLibrary("main");
 81   }
 82 }
 83
 84 class MyRenderer implements Renderer
 85 {
 86
 87   Application application = null;
 88   private String apk_file = null;
 89
 90   public MyRenderer(Application a)
 91   {
 92
 93     application = a;
 94   }
 95
 96   @Override
 97   public void onSurfaceCreated(GL10 gl, EGLConfig config)
 98   {
 99
100     Texture.setGlContext(gl);
101     ResourceLoadingTools.setApplication(application);
102
103     initializeGL();
104
105   }
...
109   @Override
110   public void onSurfaceChanged(GL10 gl, int width, int height)
111   {
112
113     Texture.setGlContext(gl);
114     ResourceLoadingTools.setApplication(application);
115
116     //Texture.glLoadAlphaStatic("box.png");
117
118     gl.glViewport(0, 0, width, height);
119
120     gl.glMatrixMode(GL10.GL_PROJECTION);
121     gl.glLoadIdentity();
122     float aspect = (float)width / (float)height;
123
124     gl.glMatrixMode(GL10.GL_MODELVIEW);
125     gl.glLoadIdentity();
126     //gl.glTranslatef(0.0f, 0.0f, -20.0f);
127     resizeGL(width, height);
128   }
129
130   @Override
131   public void onDrawFrame(GL10 gl)
132   {
133
134     Texture.setGlContext(gl);
135     ResourceLoadingTools.setApplication(application);
136
137     paintGL();
138     //eglSwapBuffers();
139   }
140
141   private native void initializeGL();
142   private native void resizeGL(int w, int h);
143   private native void paintGL();
144
145 }

You see the usual Android skeleton functions onSurfaceChanged(), *Created(), onPause etc.  However what are these declarations with the keyword native all about?  This is indicator that the implementation for these methods will be defined in Native code via JNI.

Aha, these will be our window to the wonderful world of (potentially) faster native code!

How do these translate to C++ function names?

For example, in Physics2d.java:
141   private native void initializeGL();

Is translated to the C++ equivalent function name of:

JNIEXPORT void JNICALL Java_com_example_Monkey_Poop_MyRenderer_initializeGL(
      JNIEnv* env, jclass cls)


What happened here, how did we get such a convoluted name?


Well, It appears Java is always at the front of a JNI function name.

The middle part, _com_example_Monkey_Poop_  is a reference to the package name (which I picked a ridiculous one, yeah, I'm 5 years old and poop games are funny, Monkeys + Poop_chucking = epic).

The next part MyRenderer is a reference to the Java class the JNI declaration was in, and lastly the initializeGL is the Java function name.

Cool, so how is it all resolved at run time?  Well, I am no Java expert but it appears the line below loads the libmain library (where I have JNI functions):

 78   static
 79   {
 80     System.loadLibrary("main");
 81   }

Thus, it resolves the function at run time and maps our Java call to the C++ native call with the funky name.

What about passing in parameters to C++ via Java?


Oh, I knew you'd ask that.  Ok, lets look at example:

142   private native void resizeGL(int w, int h);

Here we are passing in two integers, but when we see the translated C++ function signature, it looks like this:

JNIEXPORT void JNICALL Java_com_example_Monkey_Poop_MyRenderer_resizeGL(
      JNIEnv* env, jobject obj, jint w, jint h)

Look right after the jobject parameter, we have two more parameters than the last JNI function we looked at, w and h.  Ah, and we can see the primitives translate from Java int to a C++ type of jint, which is actually just a typedef (in most cases) to a standard int.  So we can safely cast to a C++ primitive and use them as needed.

Eg.:
float aspect = (float)w / (float)h; 

If you want to know more about JNI, passing around more crazy types, I suggest checking out some docs on JNI from Sun/Oracle. 

What about passing in parameters to Java Android OS layer code via C++?


Oh, this is more interesting here.  For this example, I wanted to load a texture from the Android package, using normal SDK Java calls, but I want to be able to load these resources from the Game logic in C++ land.

We do loading of APK sources using SDK calls since there really isn't a good Native interface on older Android NDK for this.  Only hacks of using libzip and iterating on your own package name (but I am not sure how portable this is, package location, naming etc).  It seems much more portable to use the native SDK for OS types of things like file I/O.

So first let's code and test a Java OpenGL texture loader.  (I just make life easier and right after loading create the texture in GPU memory and delete image resources).


Texture.java
 34
 35 /**
 36  *   Just a wrapper for creating textures and sending to native
 37  */
 38
 39 public class Texture
 40 {
 41
 42   static GL10 gl_context = null;
 43
 44   private Bitmap bitmap = null;
 45
 46   //**************************************************************************
 47   /**
 48    *  Keep OpenGL context for application (will change)
 49    */
 50
 51   static public void setGlContext(GL10 gl)
 52   {
 53
 54     gl_context = gl;
 55   }
 56
 57   //**************************************************************************
 58   /** Loads the resource to memory
 59    *
 60    */
 61
 62   static public int glLoadAlphaStatic(String file) throws java.io.IOException
 63   {
 64
 65     if(gl_context == null)
 66     {
 67
 68       Log.e("glLoadAlphaStatic(" + file + ")", " gl_context was null!\n");
 69       return -1;
 70
 71     }
 72
 73     GL10 gl = gl_context;
 74
 75     Bitmap bitmap = null;
 76     try
 77     {
 78
 79       bitmap = ResourceLoadingTools.loadImage(file);
 80     }
 81     catch(java.io.FileNotFoundException e)
 82     {
 83       Log.e("ERROR",
 84           "---------------------------------------------------------------\n");
 85       Log.e("ERROR", "Could not FIND image: " + file +
 86           " from apk file!\n");
 87       Log.e("ERROR",
 88           "---------------------------------------------------------------\n");
 89
 90       // Rethrow so application fails with better stack trace
 91       e.printStackTrace();
 92       throw e;
 93       //return -1;
 94     }
 95     catch(java.io.IOException e)
 96     {
 97       Log.e("ERROR",
 98           "---------------------------------------------------------------\n");
 99       Log.e("ERROR", "Could not load image: " + file +
100           " from apk file!\n");
101       Log.e("ERROR",
102           "---------------------------------------------------------------\n");
103       // Rethrow so application fails with better stack trace
104       e.printStackTrace();
105       throw e;
106       //return -1;
107     }
108
109     Log.e("SUCCESS",
110         "---------------------------------------------------------------\n");
111     Log.v("SUCCESS", "Loaded file: " + file + "!\n");
112     Log.e("SUCCESS",
113         "---------------------------------------------------------------\n");
114
115
116     int textureName = -1;
117     int[] texture = new int[1];
118     gl.glGenTextures(1, texture, 0);
119     textureName = texture[0];
120
121     //Log.d(TAG, "Generated texture: " + textureName);
122     gl.glBindTexture(GL10.GL_TEXTURE_2D, textureName);
123     gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
124     gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
125     gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
126         GL10.GL_CLAMP_TO_EDGE);
127     gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
128         GL10.GL_CLAMP_TO_EDGE);
129     //gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_REPLACE);
130     gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_MODULATE);
131
132     ByteBuffer image_buff = ByteBuffer.allocateDirect(
133         bitmap.getHeight() * bitmap.getWidth() * 4);
134     image_buff.order(ByteOrder.nativeOrder());
135     byte buffer[] = new byte[4];
136
137     for(int y = 0; y < bitmap.getHeight(); y++)
138     {
139
140       for(int x = 0; x < bitmap.getWidth(); x++)
141       {
142
143         int color = bitmap.getPixel(x, y);
144         buffer[0] = (byte)Color.red(color);
145         buffer[1] = (byte)Color.green(color);
146         buffer[2] = (byte)Color.blue(color);
147         buffer[3] = (byte)Color.alpha(color);
148         image_buff.put(buffer);
149
150       }
151
152     }
153
154     image_buff.position(0);
155     gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_RGBA,
156         bitmap.getWidth(), bitmap.getHeight(), 0,
157         GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, image_buff);
158
159     //GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
160
161     //    mCropWorkspace[0] = 0;
162     //    mCropWorkspace[1] = bitmap.getHeight();
163     //    mCropWorkspace[2] = bitmap.getWidth();
164     //    mCropWorkspace[3] = -bitmap.getHeight();
165     //
166     //    ((GL11) gl).glTexParameteriv(GL10.GL_TEXTURE_2D,
167     //      GL11Ext.GL_TEXTURE_CROP_RECT_OES, mCropWorkspace, 0);
168
169     int error = gl.glGetError();
170     if (error != GL10.GL_NO_ERROR)
171     {
172
173       Log.e(Logging.TAG, "GL Texture Load Error: " + error);
174       return textureName;
175     }
176
177     int width = bitmap.getWidth();
178     int height = bitmap.getHeight();
179
180     // Reclaim bitmap memory
181     bitmap.recycle();
182     bitmap = null;
183
184     Log.e("SUCCESS",
185         "---------------------------------------------------------------\n");
186     Log.v("SUCCESS", "Loaded file as texture: " + textureName + "!\n");
187     Log.e("SUCCESS",
188         "---------------------------------------------------------------\n");
189
190
191     //Log.d(TAG, "Loaded texture: " + textureName);
192     return textureName;
193   }
194
...
430
431 }

Ok, that wasn't too bad, we loaded an image into OpenGL using easy to use SDK functions and the texture handle is created. Now, how will C++ know about the new texture?

Well, we should call this Java function from C++ and get the return value (texture handle).


Texture.cpp
  //*****************************************************************************
 76 /**
 77  *  Grab a image resource from apk file and load in OpenGL context
 78  *
 79  *  @return texture ID from opengl context
 80  */
 81
 82 int Texture::loadTexture(const char* filename)
 83 {
 84
 85   if(filename == NULL)
 86   {
 87
 88     LOGE("filename passed was NULL!\n");
 89     return -1;
 90   }
 91   LOGI("Texture::loadTexture(%s) called\n",
 92       filename);
 93
 94   const char CLASS_NAME[]  = "Texture";
 95   const char METHOD_NAME[]  = "glLoadAlphaStatic";
 96   int texture_num = -1;
 97   AndroidJNIHelper helper = AndroidJNIHelper::getJNIHelper();
 98   JNIEnv* env = helper.getJNIEnv();
 99   if(env == NULL)
100   {
101
102     LOGE("JNIEnv was NULL!\n");
103     return -1;
104   }
105
106   string class_path = helper.getJavaPackagePath() + CLASS_NAME;
107   jclass cls = env->FindClass(class_path.c_str());
108   LOGD("class_path: %s\n", class_path.c_str());
109
110   jmethodID mid = env->GetStaticMethodID(cls,
111       METHOD_NAME,
112       "(Ljava/lang/String;)I");
113
114   if(mid == 0)
115   {
116
117     LOGE("Unable to load method: %s:%s\n",
118         CLASS_NAME,
119         METHOD_NAME);
120     return -1;
121   }
122
123   jint ret_val = -1;
124   jstring mystr = env->NewStringUTF(filename);
125   ret_val = env->CallStaticIntMethod(cls, mid, mystr);
126   return ret_val;
127
128 }


jni/AndroidJNIHelper.h
  1
  2 #ifndef ANDROIDJNIHELPER_H
  3 #define ANDROIDJNIHELPER_H
  4
  5 #include <string>
  6 #include <jni.h>
  7 #include <GLES/gl.h>
  8 #include <math.h>
  9 #include <new>
 10 #include <zip.h>
 11 #include "def.h"
 12 #include "utils.h"
 13 #include "GLHelpers.h"
 14
 15
 16 /**
 17  *   @file AndroidJNIHelper.cpp
 18  *    
 19  *
 20  *    Create an easy to use way to grab jni environment
 21  *    
 22  *    JNI method and constructor signature cheat sheet
 23  *
 24  *  B=byte
 25  *  C=char
 26  *  D=double
 27  *  F=float
 28  *  I=int
 29  *  J=long
 30  *  S=short
 31  *  V=void
 32  *  Z=boolean
 33  *  Lfully-qualified-class=fully qualified class
 34  *  [type=array of type>
 35  *  (argument types)return type=method type. 
 36  *     If no arguments, use empty argument types: ().
 37  *     If return type is void (or constructor) use (argument types)V.*    
 38  *
 39  *     Example
 40  *     @code
 41  *     constructor:
 42  *     (String s)
 43  *
 44  *     translates to:
 45  *     (Ljava/lang/String;)V
 46  *
 47  *     method:
 48  *     String toString()
 49  *
 50  *     translates to:
 51  *     ()Ljava/lang/String;
 52  *
 53  *     method:
 54  *     long myMethod(int n, String s, int[] arr)
 55  *
 56  *     translates to:
 57  *     (ILjava/lang/String;[I)J
 58  *     @endcode
 59  *
 60  */
 61
 62 //*****************************************************************************
 63 /**
 64  *   Singleton class for helping interface with Java Native Interface
 65  */
 66
 67 class AndroidJNIHelper
 68 {
 69
 70   private:
 71
 72     /// The current JNI context
 73     JNIEnv* jni_env;
 74
 75     const char* java_package_path;
 76
 77     AndroidJNIHelper():
 78       jni_env(NULL)
 79   {
 80
 81   }
 82
 83   public:
 84     static AndroidJNIHelper& getJNIHelper()
 85     {
 86
 87       static const char JAVA_PACKAGE_PATH[] = "com/example/Monkey/Poop/";
 88       static AndroidJNIHelper a;
 89       a.java_package_path = JAVA_PACKAGE_PATH;
 90       return a;
 91
 92     }
 93
 94     std::string getJavaPackagePath()
 95     {
 96
 97       return (std::string)java_package_path;
 98     }
 99
100     void setJNIEnv(JNIEnv* env)
101     {
102
103       jni_env = env;
104     }
105
106     JNIEnv* getJNIEnv()
107     {
108
109       return jni_env;
110     }
111
112     int jniLoadTexture(const char* s);
113
114
115 };
116
117
118 #endif /* ANDROIDJNIHELPER_H */

Hmm, so AndroidJNIHelper singleton class stores the JNIEnv* context, which is updated everytime a JNI function gets called (which in our case is every frame).  This is just in case our entire context gets pulled out from underneath us (which happens all the time for OpenGL context, not sure about JNI so store it anyhow, it costs next to nothing to do so!). Also, I hardcoded the package path there, so you may want to make it more flexible and extend it! :)

We use the helper class to get access to the latest valid JNIEnv context and this context is used to lookup the Java class/method we would like to call from C++:

107   jclass cls = env->FindClass(class_path.c_str());

This line indicates which method you are looking for:

110   jmethodID mid = env->GetStaticMethodID(cls,
111       METHOD_NAME,
112       "(Ljava/lang/String;)I");

This code actually calls the method we found in java code and saves the return value in ret_value:
125   ret_val = env->CallStaticIntMethod(cls, mid, mystr);

ret_val is our OpenGL texture handle that we created in Java/SDK land!  You can use this as you normally would to texture polygons.  I show texturing during my Renderer::drawFrame() C++ function, but note that I hard coded it as "1" for simplicity, and since we are only dealing with 1 texture handle, it will always be "1". (OpenGL is very predictable in how it issues out texture handles/ids).  I will extend it to use the Texture::texture_id for a later example.

The indicator for a Java String parameter ("(Ljava/lang/String;)I")  is odd, I know, but this is what Sun/Oracle decided on how to resolve types.

The (Ljava/lang/String;) part indicates the function we are looking for takes a java.lang.String as a a parameter (the parenthesis indicate this) and the I in the signature,  "(Ljava/lang/String;)I") indicates it returns an integer.

 Here is more info: http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html  I also left some info in my comments on the AndroidJNIHelper.h file, which was borrowed from this cool fellow: http://dev.kanngard.net/Permalinks/ID_20050509144235.html

Ok, so when all is said and done, you should now have the tools to get started with an app that loads a texture via the normal Android Java SDK and can use it within Android Native C++ !!


Grab my code and play around and see how it all works:

svn checkout http://razzlegames-android-ndk-tutorial.googlecode.com/svn/trunk/ razzlegames-android-ndk-tutorial-read-only

Or you can browse here


Next time.... I'll get to GDB debugging!

Sunday, March 25, 2012

Development on Android NDK using VIM and CGDB


Android representing Vim



Using Vim to program Native Android code (NDK).


You've likely found this page since you're like me, a vim editor advocate that loves the productive features of vim and are getting into Android programming, but maybe you aren't quite sure how to get started.  I don't really find much benefit in using IDE's for C/C++ game development since many times I am making custom make targets to create assets or other such customization that are painful or not possible in IDEs.  Also, let's face it, vim and emacs (while haters call old and dated) have features that aren't accessible in other editors at all or without buggy plugins etc.

Here I will document my findings and how I am currently using vim / ctags /cgdb and the typical jdk to develop an Android game.  I will assume you have both the latest Android SDK and NDK installed and your environment is working correctly (also you will probably want the Android source code so you can view SDK definitions).  I also assume you're on a Linux/Unix system, however with Cygwin, this should all work fine also.


Firstly how do you get all those nifty features that IDEs have for jumping around to definitions?  


That's simply a mater of getting your Ctags setup correctly (and the path(s) vim looks for tags).  Please, if you haven't already, get the latest ctags (source/package etc) and install using your favorite method.

You will likely want the tags for the entire SDK and NDK available, since, let's face it, you'll likely be coding parts in Java and C/C++ due to the way Google has setup the Android system.

Since I am partial to Makefiles due to how customizable they are I will show you how I do all this in a Makefile.  Take care that you will only want to recreate the tags when you update the SDK or NDK, not every time you build (unless you like waiting forever, then be my guest).  The other exception is, of course, if you are doing something weird and actually editing the android source code.


I do all these steps by creating a "Makefile" at the root of my project folder, with all these custom targets.  This root Makefile calls ndk-build, ant build and all the regular Android build commands, to save you time and typing (more on that later).

Here is the make target I use for creating SDK/NDK tags:

sdk_tags:
  -ctags -R -o $(HOME)/.vim/ctags/android_java --sort=yes \
    --fields=+iaKsS --extra=+q \
    --c++-kinds=+p \
    $(ANDROID_SDK)/android_src_git/

You would just call this once every time you install a new Androud NDK or SDK.  Notice the directory "android_src_git", this is just where I downloaded the complete android source code to use for my tags (beware it is pretty huge).  It is not downloaded as part of the normal SDK, so be warned you need to use git version control tool to get this.

So in reality when you get a new SDK, just go into that directory with your console and run git pull.  They will both be relatively in sync, depending on what SDK you're building against.

I tie this all into vim by creating a openproj bash script to setup my environment:

 1 #!/bin/bash
 2 # CODE_TAGS is set up in vimrc file as:
 3 #    set tags+=$CODE_TAGS
 4
 5 export "CODE_TAGS=$HOME/.vim/ctags/android_java";  \
 6     vim  src/com/example/Cool/Game/*.java \
 7       jni/*.cpp \
 8       jni/*.h \
 9       jni/Box2D/*/*.cpp

You can save this in the top level if your project directory, and make sure has the execute permission set.   

Notice I set the CODE_TAGS environment variable at run time, not in my bashrc file etc.  This is due to me replicating this technique on many different types of projects from, Gameboy Advance to PSP.

Ok, now we will also want to build new tags each time we change java or C++ code, so we can accomplish that by creating a target based on the tags file, and put all Java and C++ files as this target's dependency.  Then, each time any JAVA or C++ file changes the tags file will be remade by ctags:

Some where near the top of your root Makefile:



17 JAVA_FILES := $(wildcard src/*/*/*.java) $(wildcard src/*/*/*/*.java)
18 CPP_FILES := \
19   $(wildcard jni/*.cpp) \
20   $(wildcard jni/*.h) \
21   $(wildcard jni/*/*.cpp) \
22   $(wildcard **/.cpp) \


Obviously you are going to want to change those paths to where you are keeping cpp and java files. For example, on line 21, notice that two stars, well, if you have cpp files nested 2 levels deep, put in $(wildcard jni/*/*/*.cpp) \

Place this, somewhere after these definitions (but NOT the first target in your makefile, that will be the default target, which I like to use for creating the debug apk file):


68 tags: $(JAVA_FILES) $(CPP_FILES)
69   -ctags -R --sort=yes \
70   --java-types=cfimp \
71     --fields=+iaKsS --extra=+q \
72     --c++-kinds=+p \
73     ./src/ ./


Notice tags is an actual file, not a .PHONY target, so it checks to time stamp of that file and compares against all your source files in $(JAVA_FILES) and $(CPP_FILES) if tags is older than any of those, it is recreated.

Ok, let's use what we have learned and see a working example.

Here's what these new targets will look like in a working Makefile:



 1 PACKAGE_NAME = com.example.Cool.Game
 2 ACTIVITY_NAME = .Physics2d
 3 
 4
 5 DEBUG_TARGET= bin/Physics2d-debug.apk
 6 RELEASE_TARGET= bin/Physics2d.apk
 7
 8 .PHONY: force
 9
10 ANT_FLAGS = -emacs
11 NDK_DEBUG_FLAGS = \
12                   "NDK_DEBUG=1" \
13                   "V=1" \
14
15 NDK_RELEASE_FLAGS =
16
17 JAVA_FILES := $(wildcard src/*/*/*.java) $(wildcard src/*/*/*/*.java)
18 CPP_FILES := \
19   $(wildcard jni/*.cpp) \
20   $(wildcard jni/*.h) \
21   $(wildcard jni/*/*.cpp) \
22   $(wildcard **/.cpp) \
23
24 all: $(DEBUG_TARGET)
25
26 $(DEBUG_TARGET): force
27   $(MAKE) tags
28   ndk-build $(NDK_DEBUG_FLAGS) 
29   ant debug
30
31 $(RELEASE_TARGET): force
32   $(MAKE) tags
33   ndk-build $(NDK_RELEASE_FLAGS) 
34   ant release
35
36 run: install
37
38 install:
39   ant clean
40   ndk-build $(NDK_DEBUG_FLAGS) 
41   ant $(ANT_FLAGS) debug install
42   adb shell "am start -n $(PACKAGE_NAME)/$(ACTIVITY_NAME)"
43
44 install_release:
45   ant clean
46   ndk-build $(NDK_RELEASE_FLAGS) 
47   ant $(ANT_FLAGS) release install
48   adb shell "am start -n $(PACKAGE_NAME)/$(ACTIVITY_NAME)"
49
50 
51
52 uninstall:
53   ndk-build uninstall
54
55 clean: force
56   ant clean
57   ndk-build $(NDK_DEBUG_FLAGS) clean
58
59 sdk_tags:
60   -ctags -R -o $(HOME)/.vim/ctags/android_java --sort=yes \
61     --fields=+iaKsS --extra=+q \
62     --c++-kinds=+p \
63        \
64     $(ANDROID_SDK)/android_src_git/
65
66 #--java-types=cfimp --language-force=java \
67
68 tags: $(JAVA_FILES) $(CPP_FILES)
69   -ctags -R --sort=yes \
70   --java-types=cfimp \
71     --fields=+iaKsS --extra=+q \
72     --c++-kinds=+p \
73     ./src/ ./
74
75
76 


As your attorney, I advise you to...


My codes changed and the program's the same... am I CrAZY??!!!!




Ok, I know you're going to ask about this part:

38 install:
39   ant clean




Why am I cleaning before installing? Waste of time right?  Well version 16 of the Android SDK had a bug in which a new apk file would NEVER be recreated when java code changed (and a previous apk file already existed there).  Therefore you'd change something and the apk file would never change when installing, sending you to bat country wondering if you were crazy or if there was a reason your program was not changing.

It has been reported as fixed in version 17 which was just released. But I haven't verified this.

What Your Workspace should look like

So your Android project directory should look something like this (bold files we've created):


  • AndroidManifest.xml      
  • build.xml
  • libs  
  • obj
  • project.properties  
  • src    
  • assets  
  • gen
  • local.properties  
  • openproj
  • res    
  • tags
  • bin     
  • jni
  • Makefile  
  •  proguard.cfg


Now, provided you have all the environment variables set up for SDK/NDK etc,  if you type "make" you should see the the JNI native code being built in the "ndk-build" step (debug mode), all Java glue code and the debug version of the APK being built.

When you are ready to do some coding just run ./openproj, and provided you set tags+=$CODE_TAGS  in your ~/.vimrc file and all the tag paths correct etc, you should have a sweet vim Android development setup. 



In my next blog entry, I will talk about using CGDB and issues wrestling with OpenGLES context and how we get nutty and link C++/C with Java with JNI!





Here's a running log of Issues ran into in general Android development so far that will be addressed as I have answers:
  • Debug symbols for dwarf are broken in latest NDK (r7)
    • Will say: "gdb.setup:4: Error in sourced command file" and when placing breakpoints in gdb that there is no such line on that file (even though there clearly is).
    • Solution: Needed to add -gstabs+ to LOCAL_CFLAGS in all Android.mk makefiles.
  • Some rendering works in emulator but not on hardware
    • All triangles are not rendered (textured triangle should be fine, but unlit, colored non-textured don't show up in hardware).
    • glColor4f has no effect on hardware (EVO 3D), but works in emulator.
    • Solution: There was something the EVO 3D didn't like about my orthographic projection.  I projected the to the entire pixel space (540x960), and the hardware didn't like that for some reason. Scaled back to screen space projection of (actual_w/actual_height)*40 X 40) and that fixed the rendering issue. 
    • I used: glOrthof(0, aspect*40.0f, 0, 40.0f, -1.0f, 1.0f);
  • ndk-gdb "package not found" error
  • from ndk-gdb :5039: Connection refused.
    • Solution?: Got this error when an emulator process was hanging on with gdb and not being killed.
  • Appears you cannot use integer arrays in strings.xml for your application preferences
  • Not Android related: Not all java errors end up in error file for vim (cn cN error navigation).  But it seems all C++ and errors work fine.  Since most of my dev work will be in C++ I don't care much, but I know you can change how vim parses errors in the vimrc file, but I haven't put in the time to merge the C++/C error regex with the javac error parsing.