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!

Comments

  1. Thanks Jack! Please be sure to see my repository example code too, you can download and play with the code from these posts :)

    http://code.google.com/p/razzlegames-android-ndk-tutorial/source/browse/#svn%2Ftrunk

    ReplyDelete
  2. very informative post indeed .being enrolled in http://www.wiziq.com/course/5776-object-oriented-programming-with-c
    i was looking for such articles online to assist me and your article helped me a lot. i really like that you are providing such information.

    ReplyDelete
  3. Very informative post. As an FYI, this breaks the compiler in the r8b version of the Android NDK, but works fine in the r8 version.

    ReplyDelete
    Replies
    1. Thanks anon. Could you post the compile error?

      Delete
    2. Compile thumb : png <= pngrtran.c
      /extra/home/love/Android/android-ndk-r8b/toolchains/arm-linux-androideabi-4.6/prebuilt/linux-x86/bin/arm-linux-androideabi-gcc -MMD -MP -MF ./obj/local/armeabi/objs-debug/png/libpng/pngrtran.o.d -fpic -ffunction-sections -funwind-tables -fstack-protector -D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5E__ -D__ARM_ARCH_5TE__ -march=armv5te -mtune=xscale -msoft-float -mthumb -Os -fomit-frame-pointer -fno-strict-aliasing -finline-limit=64 -Ijni -DANDROID -g3 -ggdb -gstabs+ -DANDROID_NDK -Wa,--noexecstack -O0 -g -I/extra/home/love/Android/android-ndk-r8b/platforms/android-9/arch-arm/usr/include -c jni/libpng/pngrtran.c -o ./obj/local/armeabi/objs-debug/png/libpng/pngrtran.o
      jni/libpng/pngrtran.c: In function 'png_do_expand':
      jni/libpng/pngrtran.c:3790:1: internal compiler error: in reload, at reload1.c:1061
      Please submit a full bug report,
      with preprocessed source if appropriate.
      See for instructions.

      Delete
    3. Ah, thanks again. Looks like a bug in GCC. Any time you get an internal compiler error or the like, it is indicative of an internal problem. Else, you'd get a productive error message on the source code we were trying to compile.

      http://stackoverflow.com/questions/11980870/error-on-building-android-ndk-assets

      I suggest holding off on that r8b compiler, looks like there is some goofyness in their GCC version.

      Looks like the libpng library won't compile for now on NDK r8b. I'll just pull it out since it isn't even used on the example.

      Delete
  4. This info is rare, I have been searching the web for over an hour for GCC + C++ and Android, thanks Kyle, this has been worth the all the searching!

    ReplyDelete
    Replies
    1. No problem! Let me know if you have any questions.

      I am meaning to do a gdb tutorial for android. However, I recommend sound parallel PC port if doing graphics. A little more work but way easier to debug Game logic locally.

      Delete
    2. Sound = doing. on a phone, auto correct. lol

      Delete
    3. "However, I recommend doing parallel PC port if doing graphics"

      You mean doing your game on a PC then converting it to Android?

      Delete
    4. No, I mean develop both at the same time. It really isn't too much extra effort. I do a joint GLUT/SDL (I am switching to SDL) and Android game. The opengl calls are kept very standard so they work with Opengl ES and for any JNI parts, I ifdef them in for the Android version only.

      E.g.:

      // All JNI call back declarations

      #ifdef ANDROID_NDK
      #ifdef __cplusplus
      extern "C"
      {
      #endif

      JNIEXPORT void JNICALL Java_com_example_Monkey_Poop_MyRenderer_initializeGL(
      JNIEnv* env, jclass cls);
      JNIEXPORT void JNICALL Java_com_example_Monkey_Poop_MyRenderer_resizeGL(
      JNIEnv* env, jobject obj, jint w, jint h);
      JNIEXPORT void JNICALL Java_com_example_Monkey_Poop_MyRenderer_paintGL(
      JNIEnv* env);

      #ifdef __cplusplus
      }
      #endif

      #endif // ANDROID_NDK


      This forces your interface and input code to be loosely coupled also, which is good.

      Delete
    5. I also do this for some functions that are not named conventionally in OpenGL ES on android:

      /// Function name remappings from OpenGL to OpenGLES
      #ifdef ANDROID_NDK
      #define glOrtho glOrthof
      #endif // ANDROID_NDK

      // Now here the orthographic projection acts as expected
      // with the correct function name mapping:
      glOrtho(0, aspect*40.0f, 0, 40.0f, -1.0f, 1.0f);

      Delete
    6. This comment has been removed by the author.

      Delete
    7. BTW, there are not two code bases. I just want to be extra clear about that. They are the same code base. You don't want the develop, merge back, develop, debug, etc headache! :)

      Delete
    8. Thanks Kyle, I understand it now.

      I suppose there's no harm in renaming certain API calls to make your code a little easier to handle too. Good stuff! :)

      Delete
  5. This blog is truly awesome in all aspects.
    a total noob

    ReplyDelete
  6. The stuff you are writing blows out my mind.
    get the facts

    ReplyDelete
  7. I thought that in the new NDK updates JAVA or JNI wasn't necessary at all and we could port directly from C++.

    And yes like someone else said previously, the information about this topic is scarce. Thank you for the info.

    ReplyDelete
    Replies
    1. Yes, you can use NativeActivity, which is available in API level 9 and you can do everything in C++. I was targeting older devices also, so I opted not to go that route also for a few other reasons.

      There have been small bumps in the road going full Native on Android. E.g. getting access to assets in the APK is one issue. To solve it, most examples I have seen use JNI (either calling C++ to store asset manager, copying all your game assets to the sdcard, using libzip force access to the apk, or some other variant: http://stackoverflow.com/questions/7701801/obtaining-the-name-of-an-android-apk-using-c-and-the-nativeactivity-class).

      Another is the use of sound. Which, from what I know, will use OpenSL in NativeActivity. I'd have to look them up, but there were a few surprising bugs I had found on usage of this native API, which discouraged me from using it.

      Right now I call the sound pool in Java using JNI. You could still do that from Native activity, but I just call one of my java methods from C++ that does more setup. It's harder to do so many JNI setup and calls in C++ to just do a few things (think implementing a Java method with no Java, JNI can be a pain lol).

      If you get a good example of using the AssetManager from NDK without Java, please let me know. I wish there was just a getAAssetManagerStatic() or something simple.

      I've found the native-audio example from the NDK samples. But it is so much more complex than just calling SoundPool with JNI and calling it a day. The example also still relies on Java for AssetManager.

      There has to be a better NDK way to do things! With new additions (especially with the new Google I/O this year), maybe there is. Please let me know.

      Delete
    2. Hey, I found a way to get access to the AAssetManager in a pure native activity!

      I guess you can access it from the android_app structure, which is passed to android_main.

      Pretty cool! Just something like this:
      AAssetManager* am = app->activity->assetManager;

      Check out the gamekit android sample for more usage info:
      http://code.google.com/p/gamekit/source/browse/trunk/Samples/AndroidDemo/Shared/Main.cpp

      Delete
    3. FYI, the same restrictions apply to using a pure NativeActivity (must be API >= 9 and all that).

      Delete

Post a Comment

Popular posts from this blog

Creating a Fake Refraction, Bubble Shader for your 2D Godot Game!

How to render Parallax Background and Foreground Layers Completely in the Shader [Tutorial]