Showing posts with label Android. Show all posts
Showing posts with label Android. Show all posts

Thursday, August 6, 2015

Android JNI and pthreads

Here is a quick gotcha for using pthreads that make JNI calls on Android: the JNIEnv is not shared between threads.

This simple implementation of threads will crash on any JNI call.
struct ThreadArgs
{
GHRunnable* mRunnable;
};

void* threadLaunch(void* arg)
{
ThreadArgs* threadArgs = (ThreadArgs*)arg;
threadArgs->mRunnable->run();
delete threadArgs;
}

void GHAndroidThread::runThread(GHRunnable& runnable)
{
ThreadArgs* threadArgs = new ThreadArgs;
threadArgs->mRunnable = &runnable;

pthread_t myPThread;
if (pthread_create(&myPThread, NULL, threadLaunch, (void*)(threadArgs)))
{
GHDebugMessage::outputString("Error creating pthread");
return;
}
}
 You need to call AttachCurrentThread and DetachCurrentThread to allow JNI access.
struct ThreadArgs
{
GHRunnable* mRunnable;
JavaVM* mJVM;
};

void* threadLaunch(void* arg)
{
ThreadArgs* threadArgs = (ThreadArgs*)arg;
JNIEnv* jniEnv;
bool attachNeeded = true;

int status = threadArgs->mJVM->GetEnv((void**)&jniEnv, JNI_VERSION_1_6);
if (status == JNI_OK)
{
attachNeeded = false;
}

if (attachNeeded)
{
threadArgs->mJVM->AttachCurrentThread(&jniEnv, NULL);
}

threadArgs->mRunnable->run();

if (attachNeeded)
{
threadArgs->mJVM->DetachCurrentThread();
}

delete threadArgs;
}

void GHAndroidThread::runThread(GHRunnable& runnable)
{
ThreadArgs* threadArgs = new ThreadArgs;
threadArgs->mRunnable = &runnable;
threadArgs->mJVM = mJNIMgr.getJVM();

pthread_t myPThread;
if (pthread_create(&myPThread, NULL, threadLaunch, (void*)(threadArgs)))
{
GHDebugMessage::outputString("Error creating pthread");
return;
}
}
 Lastly any JNI calls should probably include the following wrapper to get the correct env for the current thread.  I tried it without this code and didn't have problems but the docs do say it's a different JNIEnv per thread.  Maybe it would crash without this on a different Android version.

JNIEnv& GHJNIMgr::getJNIEnv(void)
{
JNIEnv* env;
int status = mJVM->GetEnv((void**)&env, JNI_VERSION_1_6);
if (status < 0) {
GHDebugMessage::outputString("Failed to get JNIEnv");
}
return *env;
}

Saturday, March 22, 2014

Android Texture Optimization

tldr: We increased the terrain texture resolution for Android Big Mountain Snowboarding from 256 to 1024 by making some changes.

Colt McAnlis gave a talk at GDC on shrinking your png use in Android games that inspired me to go over some of our assets and code.  Our current setup is to use high res hardware compressed textures like pvr4 or dxt on platforms that have consistency, and use lower res uncompressed textures on platforms that do not.  For Big Mountain Snowboarding this means 2048x2048 textures on iOS and Windows are 256x256 on Android.  Sorry Android people!

Part of the size difference is because BMS is still supporting phones that no one uses.  When we originally released the game, phones didn't have enough texture memory to support the larger uncompressed textures.  This appears to not be the case anymore since we have released 4 newer maps with higher res textures without hearing many complaints.

However, file size of the apk prevents us from increasing the size of all maps.  We can't make all 16 maps be 1024x1024 without going over the 50 meg Google Play limit unless we make some changes to how we handle textures.

So at the start of this experiment, our assets folder is 32 megs, and the compressed apk is 33 megs.  We have some room to make the apk bigger, but not enough to increase the terrain resolution.  The first thing I tried was export for web from photoshop.

Export for Web:
8-bit pngs are not going to work.  These are very large pixels in world space due to the way we're handling terrain, so the banding is going to be very obvious.  It's hard to tell in that small image but it's there!  Using save for web with 24 bit pngs was not a big enough savings to worry about.

PNGCrush (lossless):
Using the option -reduce for lossless compression, the largest texture was reduced from 1469KB to 1344KB.  It's the right direction, but not really enough for me to increase the texture resolution.  Using -reduce -brute brought it to 1231KB.  I thought this might be enough to do a (time consuming) full run on all the larger terrain png files to see what it does to the apk, and 2 textures later decided to look for better options.

TinyPNG.com (lossy):
Tinypng.com is a site that provides free lossy compression of png files.  Here we are throwing away color information to favor a smaller disk size.  The site was able to reduce a 1.4 meg png down to 700k.
The difference is noticeable, which is unfortunate.  The unacceptable banding from the 8 bit version does not exist though!  Considering this amount of savings would let us increase the texture size of all maps I consider it worth it.  After a short meeting with our art director (me) and our tech director (me) where there was much arguing and throwing stuff, both departments agreed that it was a worthwhile change to make.

Doing this for all 4 of the large resolution maps brought our assets folder down to 21.9 megs (from 32), and our apk to 23 megs.

Parkinson's Law:
We now have approximately 27 megs to fill up!  Our older maps are using 256x256 textures on Android, and we have 12 of those.  A 1024x1024 mountain takes up about 2 megs of space, so it looks like we have room to bring all the old maps up to 2013.  Our pipeline lets us generate as much detail as we like but I can save time by starting with the OSX versions which are 2048x2048 and shrink them down.  With increased texture sizes our assets folder is 75 megs.  After compression the assets folder is 49 megs, which pushes our apk over the 50 meg limit.  Dropping the resolution of the two biggest maps brings us to 45 megs, but they are still higher resolution than the original game.

But what about the gpu?
None of this stuff matters on the gpu, where the textures will be uncompressed.  We've had some 1024x1024 maps on the market for a while without hearing about too many issues, so the phone quality of the average user has gone up a lot in 4 years.

I stuck in a breakpoint and all our png textures are loading as RGBA_8888.  Our big terrain textures would benefit a lot in memory size from being RGB_565.  The textures are 3 megs as uncompressed 32 bit and we have 4 of them per map.
     //bitmap = BitmapFactory.decodeStream(filestream);
     BitmapFactory.Options bmpOptions = new BitmapFactory.Options();
     bmpOptions.inPreferredConfig = Bitmap.Config.RGB_565;
     bitmap = BitmapFactory.decodeStream(filestream, null, bmpOptions);
 According to my breakpoints, this code loads in all non-alpha textures as 16 bit, which seems to look ok.  If it doesn't look ok for some specific textures we'll have to add a way to disable the code.


Friday, May 17, 2013

Google Play Game Services for Android setup tutorial


Gotchas:
  1. Your app must use Google APIs instead of Android SDK.
  2. You must use a signed release build for testing, with the same SHA entered into the google play console.
  3. You must import the google-play-services_lib project into eclipse.  Just grabbing the lib is not good enough.

Docs: 

Step 1: Get the google-play-services_lib
  • Use the Android SDK Manager to download "Google Play services".  
  • Right click in the Package Explorer window and select Import.
  • Under "Android" select "Existing Android Code Into Workspace". Click next.
  • Under "Root Directory" click browse and select [android-sdk-dir]/extras/google/google_play_services/libproject/google-play-services_lib.
  • Make sure "Copy projects into workspace" is selected.  You want a copy of the version downloaded from google.

Step 2: Link your project to the google-play-services_lib
  • Right click your project and select properties
  • Select "Android"
  • Make sure "Google APIs" is selected.
  • Under library click "Add" and select google-play-services_lib
  • Also set the lib project as a reference.  Right click your project, go to project references, and make sure google-play-services_lib is selected.  Otherwise it won't export with your retail build.

Step 3: Set up the google play console.
  • Go to play.google.com/apps/publish and sign in
  • Click the control pad icon to go to the new Google Play Game Services page.
  • Click "Add a new game" and fill out the details.
  • Select your new game, and click on "Linked apps".
  • Add a new android app and link to your store entry.
  • Click the "Authorize your app now" button after saving.
  • Enter your SHA1 signing certificate.  You won't be able to change this later.  To find your key, export a signed retail apk from eclipse and sign with the key that you use for publishing.  At the end of the process it will tell you the SHA1.
Step 4: Add yourself as a tester

  • Go to the google play games console for your app and click testing.
  • Add your google account as a tester.
If you skip this step you will see this in the logcat console:
Unable to retrieve 1P application 1234567890 from network
Unable to load metadata for game

Step 5: Set your app id in the manifest.
  • Grab your app's id from the google play console.  If you select the controller icon and then select your game you will see something at the top that looks like YourAppName - 1234567890
  • In your project in eclipse open res/values/strings.xml.
  • Add: <string name="app_id">1234567890</string>
  • Open your manifest file, and add this inside your application node: <meta-data android:name="com.google.android.gms.games.APP_ID" android:value="@string/app_id" />

Step 6: At program launch, make sure the player has Google Play Services installed.
Call this function on launch, and then again in onResume.  The first time it will send the player to the store for the package.  When the player comes back in to your app we will grab ahold of it.

// determine if the user has google play services installed.
//  if not, try to install it.
// should be called in onResume so we know that it got installed.
   public void validateGooglePlayServices()
   {
        int checkGPServices = GooglePlayServicesUtil.isGooglePlayServicesAvailable(mActivity.getApplicationContext());
        if (checkGPServices != com.google.android.gms.common.ConnectionResult.SUCCESS)
        {
        mServicesAvailable = false;        Dialog gpsDialog = GooglePlayServicesUtil.getErrorDialog(checkGPServices, mActivity, 1);
        if (gpsDialog != null) {
        gpsDialog.show();
        }
        }
        else
        {
        mServicesAvailable = true;        }
   }

Step 7: Create your GamesClient
private GamesClient mGamesClient = null;
private String mScopes[];
public void initClient()
{
validateGooglePlayServices();
if (!mServicesAvailable) return;Vector scopesVector = new Vector();
        scopesVector.add(Scopes.GAMES);
        mScopes = new String[scopesVector.size()];
        scopesVector.copyInto(mScopes);
      
        GamesClient.Builder gcBuilder = new GamesClient.Builder(mActivity, this, this);
        gcBuilder.setGravityForPopups(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
        gcBuilder.setScopes(mScopes);
        mGamesClient = gcBuilder.create();
}

Step 8: Following the official guide
The rest of the implementation is fairly straight forward. 

Step 9: Testing
  • Export a signed release build
  • Uninstall your app from your device
  • Install your new apk using [android-sdk-dir]/platform-tools/adb install yourgame.apk
  • Launch the game, and watch the log cat window in eclipse.

Friday, January 18, 2013

Cross platform SDK cheat sheet

Android

Build C++
Navigate to directory where the jni/Android.mk file is (there may be multiple projects -- do them in order of dependency).
Type into Terminal:
~/android-ndk-r8b/ndk-build

Get Crash Log Info:
Type into Terminal:

~/src/android-sdk-macosx/platform-tools/adb pull /data/anr/traces.txt .
This will put crash info into traces.txt

Determine where are a crash is in C++:
android-ndk-r7b/toolchains/arm-linux-androideabi-4.4.3/prebuilt/darwin-x86/bin/arm-linux-androideabi-addr2line -C -f -e libBMSnow.so 000708e6

Export a signed android apk:
Right click on the project in eclipse
Go to “Android tools”
Click export signed apk
Uninstall builds from device, and then use adb to install the signed apks for testing

Install a signed android apk:
~/src/android-sdk-macosx/platform-tools/adb install SWNookTab.apk

iOS

Create a pvr texture
Download PVRTexTool from http://www.imgtec.com/powervr/insider/powervr-pvrtextool.asp
From the command line use:
/Applications/PVRTexTool/PVRTexToolCL/MacOS_x86/PVRTexTool -f OGLPVRTC4 -i alley1icon.png -m -yflip0 -o ../GHBowlingiOS/alley1icon.pvr

MAC

Record a video
Go to applications and launch quicktime player
Run the mac build and resize the window to whatever
Quicktime menu bar: file, new screen recording
Click the little down arrow next to the record button to make it use microphone
Click the record button and choose record part of the screen.
Drag over the app window
Click record.

Touch all files in a directory
find . -exec touch {} \;

Get into Sandbox mode in GameCenter
https://devforums.apple.com/thread/168811?tstart=0

Submit a new binary
In dev studio, Product->archive.
When that build finishes, organizer shows up.
Click the new binary in organizer, and click distribute.
Select Mac App Store
Log in to itunes connect when prompted
Select the application.  You must have already set the app to ready for upload on the web site.

To fix “does not contain a single-bundle application” error
From Tim Swast on stackexchange: “Turns out it is an issue with dependent projects in XCode 4. If this happens to you, go through the Build Settings for all your dependent projects (e.g. static libraries) and make sure that the "Skip Install" option under "Deployment" is set to YES.”

To get a receipt during development:
1) Sign the app with a development provisioning profile (not retail)
2) Make the app exit(173); from main.
3) Run the app once from finder (not from xcode)

Windows

Make a DDS file
contrib/texconv.exe -f [format] -o [outputdirectory] [file]
for format, use BC3_UNORM for textures that have alpha, BC1_UNORM for opaque textures

Windows Phone

Add data file(s) to the project (the C# project)
-Make an empty folder in the project to put the file(s) into initially
-Select Add -> Existing Item on the empty folder
-Multiselect all files you wish to add.
-Next to the “Add” button, click the arrow, select “Add as Link”
-Multiselect all the files in the previously empty folder. Right click and select “Properties”
-Set “Copy to Output Directory” to “Copy if Newer” Close the properties window
-Drag the files to the actual folder that the game will be looking for them in (ex: SB in the SBPhone project)

Data file Protip for the WinPhone C# project editor: Let the editor help you pick the right version of the files.
If you are including files that are versioned across platforms, add the highest priority folder first. EG: Add files from the SBWinPhone directory, then the SBWin8 directory, then the SBMac directory, then the SBIphone directory.
When you drag from the dummy folder into the real folder, it will only copy items that do not already exist in the real folder. You can then remove the remaining items from the project.

Query memory use:
Windows::Phone::System::Memory::MemoryManager::ProcessCommittedBytes
(use %llu with printf)

Blackberry

To install a pre-built binary:
You can deploy your signed bar file using the blackberry-deploy utility included in the bin folder.   Follow the instructions here: http://www.tidevs.org/topic/6-how-to-deploy-a-bar-application-to-the-bb10-alpha-dev-device/  Make sure the device is in developer mode.

./blackberry-deploy -installApp -password DEVICE_PASSWORD_HERE -device IP_ADDRESS_OF_DEVICE -package /Users/YOUR_USER_NAME/Downloads/Apps_for_the_Dev_Alpha/Facebook.bar

Sunday, March 11, 2012

Lights-out mode in Android

As I noted in the last post, Google and players like it when you use the "lights out" option to hide the status bar during games. I spent a couple hours today figuring out how to do that.

1) Wrap all of this code in a check to see if we are on Android 3 or higher so we can still run on 2.3.

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {

2) Find the right view. We create a GLView in our startup code, so this part is easy for us. Otherwise you'd want to add an android id to the top view in the layout file, and then use findViewById to find it.

(in layout definition) android:id="@+id/RootView"
(in startup code) View root=findViewById(R.id.RootView);

3) Toggle the visibility flag to SYSTEM_UI_FLAG_LOW_PROFILE

view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);

4) Set up a visibility listener to re-hide the menu bar after a delay when it becomes visible again. Otherwise when you touch the bar it will stay visible forever. We add the delay in because things go wonky if you rehide in the unhide notification.

view.setOnSystemUiVisibilityChangeListener(

new View.OnSystemUiVisibilityChangeListener()

{

public void onSystemUiVisibilityChange(int visibility)

{

if (visibility == 0)

{

Runnable rehideRunnable = new Runnable()

{

public void run() {

view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);

view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);

}

};

Handler rehideHandler = new Handler();

rehideHandler.postDelayed(rehideRunnable, 2000);

}

}

});

Wednesday, March 7, 2012

Tips every Android developer should know, or things we're doing wrong and need to fix in our Android builds.

A couple Google engineers, Dan Galpin and Ian Lewis, gave a talk at GDC this morning on things people do that prevent their apps from getting featured. Here are my notes on what they said.


1) Don't run in compatibility mode. Google hates the menu button on newer Android devices, so set the target SDK version to the newest (15).

2) Games that use the lights out menu option are cool. This is View.Status_Bar_Hidden.

3) Don't override the basic button behavior like volume or home or power. The back button is fair game though. Back should be treated as the escape key and not a quick exit button because it is easy to accidentally press it on honeycomb and ice cream sandwich.

4) Don't use a "do you want to quit" button when hitting back from the main menu. Just exit.

5) Don't play music on the lock screen when coming back from sleep. This one has driven us nuts trying to figure out. You have to overload onWindowFocusChanged as well as the sleep functions, and they can be called in any order.

6) Gracefully cleanup your OGL contexts.

7) For in-app purchases, don't assume your app will be open when the confirmation comes through.

8) Always have a tablet promo graphic. They scale this down to use it for feature spots on phones. This is the big banner that shows up when viewing the game on the web or on a tablet.

9) Localizing the market text is recommended, with the languages EFIGS-CJK.

Friday, October 28, 2011

Android NDK Crash Logs

So there you are working happily with the Android NDK and get a crash. Instead of a handy breakpoint where you can see the state of all the variables, you get something that looks like this:

10-28 15:31:49.333: I/DEBUG(1010): #00 pc 00557718 [heap]

10-28 15:31:49.333: I/DEBUG(1010): #01 pc 000903e6 /data/data/goldenhammer.scribbleworm/lib/libScribbleWormBase.so

10-28 15:31:49.333: I/DEBUG(1010): #02 pc 00072a1a /data/data/goldenhammer.scribbleworm/lib/libScribbleWormBase.so

10-28 15:31:49.333: I/DEBUG(1010): #03 pc 00084222 /data/data/goldenhammer.scribbleworm/lib/libScribbleWormBase.so

10-28 15:31:49.341: I/DEBUG(1010): #04 pc 00096dfa /data/data/goldenhammer.scribbleworm/lib/libScribbleWormBase.so

10-28 15:31:49.341: I/DEBUG(1010): #05 pc 0006d578 /data/data/goldenhammer.scribbleworm/lib/libScribbleWormBase.so

Great! How do I figure out how to turn that into something useful?
~/android-ndk-r5b/toolchains/arm-linux-androideabi-4.4.3/prebuilt/darwin-x86/bin/arm-linux-androideabi-addr2line -C -f -e
~/src/sb/src/AndroidProjects/ScribbleWormLib/jni/libs/armeabi/libScribbleWormBase.so 000903e6
This will spit out something like:
YrgMaterialWarmer::warmMaterials()
??:0
It may be pathetic compared to C++ development on any other platform, but at least that's a starting point to look for problems.

Thursday, August 25, 2011

How to detect a tablet screen in Android

I went to the Android Developer Labs in NYC yesterday. Google talks always kick ass. One of the tips I picked up was how to detect that you are running on a tablet.

We were checking to see if the API level was Honeycomb or newer. This was going to backfire once Ice Cream Sandwich gets released and phones start using newer API levels. It also leaves out the pre-Honeycomb tablets like the popular 7" Galaxy Tab. I'm going to try to push out an update to fix this soon.

The alternative route is a little roundabout but doesn't have either of those problems. You use an id tag in a layout file and only put that tag in layout-large.

res/layout/main.xml

xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent">

LinearLayout>

res/layout-large/main.xml

xml version="1.0" encoding="utf-8"?>

<LinearLayout

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="match_parent">


<FrameLayout android:id="@+id/large_screen_flag"

android:orientation="horizontal" android:layout_weight="2"

android:layout_width="match_parent" android:layout_height="0dp" />

LinearLayout>

Then in your Activity onCreate method:

setContentView(R.layout.main);

boolean isTablet = (null != findViewById(R.id.large_screen_flag));

Sunday, December 12, 2010

Android NDK and Storage Size

I decided to update our Android build of Big Mountain Snowboarding today because we have made a lot of progress in the game and engine while porting to OSX for the upcoming mac app store.

Eventually the build was ready, and uploaded! Somehow updating a patch put us back on the "Just In" tab on the app store for some nice visibility. Reception has been pretty ugly. One guy sent us a support email that just says "U suck". Thanks, not helpful.

Then we got a 1 star review with actual good info in it:
by Joe (December 12, 2010)
Really? More than 20MB used in phone storage even
after moving it to SD? BUH-BYE!

This confused me for a bit. Our APK file is only 13 megs, and it supports moving to the SD card! After some digging I found out that the .so files generated by the NDK are copied out of the APK file on the SD card into main storage. It's also 20 megs!

After some more digging I found out that we were exporting all of our symbols, all 33,000 of them. Time for fvisibility=hidden.

In the makefile:
LOCAL_CFLAGS += -fvisibility=hidden

In our JNI files for any functions we need to call from java:
extern "C"
__attribute__((visibility("default")))
void
Java_goldenhammer_BMSnowBase_YrgEngineInterface_ourFunc(JNIEnv*
env, jobject thiz)
I was happy, surely this was going to solve all problems! After a full rebuild taking 30 minutes our .so file was down to a disappointing 19 megs. More digging in the google groups led to a little command line tool shipped with the NDK.
arm-eabi-strip --strip-debug --strip-unneeded libxxx.so

This brought us down to 1 meg! Woot! arm-eabi-strip is located in android-ndk-r5/toolchains/arm-eabi-4.4.0/prebuilt/windows/bin.

Saturday, August 28, 2010

Supporting iPhone and Android in the same codebase

I've noticed that a lot of people are surprised we have a cross platform engine for iPhone and Android. It also runs on Windows and Mac. Within a short period we could have any of our iPhone games running on any of those other platforms. I thought it would be worthwhile to go into some details about how we did it.

Engine and Platform Philosophy:

The Golden Hammer engine was written from the get-go to be cross platform. Keeping other platforms in mind early on will save a lot of effort when it's time to port. This really boils down to three things:
  • C++ is used for the bulk of the code.
  • Interface classes are used to wrap major systems.
  • No direct calls to the operating system are allowed outside of a platform wrapper.

C++ is supported on PC, Mac, iPhone, Android, XBox, Wii, PS3 and the list goes on and on. The operating system calls on iPhone are in Objective C, but the rest of your code can be in C++. Likewise with Java on Android through use of the NDK.

Major systems like rendering are wrapped in a high level interface. We support DirectX, OpenGL, and OpenGL ES renderers. Keeping the interface high level keeps the nasty high frequency virtual calls at bay when setting render states. For iPhone and Android you can get away with just an OpenGL ES renderer, but not being locked out of platforms like the Wii later on is nice to have. Sound is a place where you will need an interface in order to successfully port from iPhone to Android.

Platform function calls are all abstracted behind interfaces. On the iPhone this means that Objective C calls are not allowed anywhere in the game code outside a minimal set of classes to represent the platform. On Android this means that the game is not allowed to make any JNI calls except from within the platform layer.

Our engine is about 60,000 lines of platform-independent C++ code, with the platform interface layer running about 2,000 lines. In theory we don't need to modify any of those 60k lines in order to add support for a new platform.

Linking C++ and Operating System calls:

Calling Objective C code from C++ is super easy though the use of .mm files, which let you compile both Objective C and C++ within the same class. Provide a C++ interface for the game to use, and then subclass it with a .mm file to make the platform calls.

Communicating between C++ and Java is a bit more work with JNI as your only choice. You create a Java function on one side, and a C function on the other to act as the glue code. Then subclass your C++ interface to call your new wrapper functions. I recommend doing some web searches on JNI for details.

Handling Input:

There are many ways to set up your interface to the platform input. I don't strongly advocate one over another, but I can explain how we do it. The platform code never directly causes the game to do anything. We maintain an input state class with all the current key positions, accelerometer values, etc. When an accelerometer call comes in we update the input state. On the next frame the game looks at the updated input and makes decisions. On iphone this has the added bonus of never doing much work in response to an input event, which prevents the operating system buffer from overflowing and losing events.

The iPhone/Android Platform Layer:
  • Texture loading: On iPhone we use custom code for PVR loading, and UIImage for uncompressed textures. On Android we use BitMap.
  • Sound: On iPhone we use a platform independent OpenAL library with platform code for loading the files for effects, and AVAudioPlayer for music. On Android we tell Java code to start playing a sound.
  • File Access: This is a simple .mm interface on iPhone, but on Android we need to ask Java for a file handle that we can then read in C++.
  • Handling OS input events.
  • Creation of the OpenGL context.
  • Time calculation.
  • Threads.
  • Debug output.
Gotchas:

Android does not have breakpoints for native code. This means you are stuck with printf debugging while doing the port.

Setting up the Android NDK environment is time consuming. Essentially you need to download and set up the NDK, create makefiles, and build your C++ through the command line. You then link into the native library in your eclipse project.

Hardware fragmentation on Android is a real problem, even when ignoring the performance differences between a Galaxy and a Hero. All iPhones support OpenGL 1.1, but some Android phones are still on 1.0. The Hero can't generate mipmaps on the GPU for example. Be prepared to have to test on a wide variety of phones.

File size matters on Android phones before 2.2. The phones have a limited amount of space available for apps, because they can't be installed on the much larger SD card. If your app is too large, owners of older phones will uninstall quickly.

There's is no standard compressed texture format on Android. If you choose to include PVR files, you will also need to include an uncompressed fallback texture. This can be a problem when considering the file size.

Sounds on Android are slow, especially for starting and stopping. Maybe I'm missing something in my implementation, but I had to remove several sound effects during the port for performance reasons.

The NDK does not come with STL included. You will need to use something like STLPort.

The iPhone and Android audiences are not the same! Be prepared to have a new marketing challenge once your port is complete.

Final Notes:

Several of the topics here could easily be expanded into their own posts. If you'd like to see more detail on anything please let me know.




Friday, August 6, 2010

Android Redux

We spent a few days making a free version of snowboarding for android, released it, and got a bunch of complaints that it doesn't work on certain phones. So I fixed the problems and released a newer version called B.M.Snowboard Free. Sales are up and so is ad income. I don't know if either will last very long.

Hardware fragmentation:

We now run on all major 2.0 android phones. The way this was accomplished was to go into AT&T, Verizon, and Sprint stores and install the free version on a bunch of phones for testing. I had to actually buy a Hero which I will be returning in order to get that one working.

There were 4 issues I found:
* Sound - some phones really hate starting/stopping sounds. I had to remove some sound effects to get the blazing fast Samsung Galaxy S phones to work.
* Mipmap generation - The Hero can't generate mipmaps with an openGL code. I had to manually create the mips.
* glGetFloatv - This function does not exist on the Hero. I had to redo some camera code.
* Holy crap some of those old phones are crappy. The Hero has even worse benchmarks than the iPhone 1.

Return rate:

I may have misquoted the numbers. I looked at total installs vs active installs. This tells me how many people still have it installed, but it doesn't tell me how many of those people ended up not paying for it. With better compatibility and a demo version we're getting about a 15% return rate.