Sunday, January 16, 2011

Lessons Learned with Android

Around the summer of 2010 I decided to switch from hacking around on the Nintendo DS to hacking around on Android.

Android piqued my interest for the reaons I outlined at the time, but also because it has been changing at a breakneck pace especially if you compare it to the DS. I bought a HTC Magic off of ebay to test out my code. At the time the Magic had been out for a year or so, yet it was already considered out of date! To everyone's surprise Google updated the version of Android running on the phone to the very recent Froyo 2.2.1 at the end of December 2010. Meanwhile, Nintendo only updated the firmware on the DS to block flash cartridges that allow homebrew. From a fun-filled hacking point of view Android is a more entertaining prospect.

The distribution method - upload to the Market, and users are automatically notified of updates - is really great. As a user it's always nice to see applications that have been updated and to read the "recent changes" section to see what's new. As a developer, there's no need to advise people that you've released a bug fix. You just upload a new apk file, update the recent changes section, and you're done.

Here are notes on some of the things I've learned while coding for Android.

The emulator is not great for games


When I wrote about my switch to Android I said "coding for Android feels like cheating. The emulator is highly accurate". I must have been viewing the new (to me) platform through rose tinted glasses there. The reality is that the emulator became useless as soon as I started using OpenGL to render Chaos. On hardware I was getting close to 60 frames per second (FPS), but on the emulator I was lucky to get above 10 FPS. This made testing on anything but hardware impossibly slow.

Fortunately I had my experience with emulators from my DS days and I knew that the best way to test embedded code is to get that code running natively on desktop Linux. The majority of the time I compile and test the Linux build, only occasionally switching to Android to check things still work. All the real gameplay testing I do is on hardware though, as the feel of using a touch screen is nothing like the feel of clicking things with a mouse.

There is an inaccuracy in the OpenGL emulation that could catch you out if you never tested on hardware. On the emulator you don't need to call glTexParameterf and set GL_TEXTURE_MIN_FILTER and GL_TEXTURE_MAG_FILTER to GL_LINEAR or similar, which sets up the texture "mip maps". If you fail to do this on a real device, your textures will only show up as white polygons.

I think Android should use an approach more akin to the iPhone or Windows Phone, where you compile to a native simulation of the application. Instead of emulating a full Android phone and running your app, you have the Android APIs call into native code directly. It's probably too late for Google to change this now and I have my own workarounds. Hey, at least if your game runs fast on the emulator, it will run very well on the real deal :-)

Each Android device is a precious snowflake


A game can run well on the phone you have tested with, but this doesn't guarantee anything. Soon after you put the game on the Market, you can be sure someone will post a 1-star review stating that the game doesn't run on their particular Android device. The Market dashboard for developers has a tab for "Error reports" that never shows anything, but it would be nice if it somehow gave a logcat dump of crashes and force closes.

If you want people to get in touch with you instead of posting a 1-star review, you really need to explicitly state that on your game's description. Otherwise you will never get to the bottom of errors. I think the instant gratification of giving a free game 1 star trumps the hassle of getting in touch with the author. It's perfectly understandable really and something other people have noticed with free app users vs paying customers - people using free things actually have higher expectations!

The SoundPool class, which is used to play back sampled sounds and seems ideal for games, will randomly crash on some hardware platforms and some versions of Android if you use MP3. MP3 is one of the supported media formats, but there are some weird issues on some phones. For example Chaos crashed on an HTC Hero, which has almost identical hardware to a Magic, running similar versions of Android (2.2.1 "official" vs 2.2.1 cyanogen mod). Random internet forum posts show that OGG files work better, but that WAV files can crash if looped. When I switched from using MP3 to OGG the crash reports I was seeing dried up.

When managing the life-cycle of a native application, strange things can happen with your OpenGL context. According to the documentation all you need to do is call the "onPause()" and "onResume()" methods of your GLSurfaceView when the user switches away and then returns to the game. However, on Android 1.6 at least you can get in a state were the "onSurfaceChanged()" method is not called again, so you don't have a hook to setting up the correct GL state. This seems to have gone away with Android 2.2, or at least the game's already set-up context is not ruined in the same way. On 1.6 I'd seen Chaos have a wrecked graphical display after the phone went to sleep, but since the 2.2 update this hasn't happened to me.

You should use 30 FPS as a baseline


A few weeks ago I wrote about getting a good frame rate on Android using the NDK. On my HTC Magic I was getting nearly 60 FPS with that approach. Well, it turns out that new devices are limited to 30 fps. So if I assume a 60 fps rate (as seen on the DS), I would make everything run half as fast. The solution is to limit the frame rate to 30 fps even on "faster" (older!) hardware.

An advantage to running at a lower frame rate is that it saves the battery, but it still seems a bit backwards to me.



More ratings after an update


Almost every time I've posted a new version of Chaos it has received a few new ratings on the Market. I imagine that this is because people download the game then go off and play it, but forget to go back to the Market and rate it. When a new version appears, they return to the Market and now they know it's amazing (!) so rate it 5 stars and install the update.

Don't abuse this phenomenon though. If you upload lots of minor versions with no user-visible changes, people will quickly tire and start rating your game worse. I saw this effect in a few comments for the game "My Paper Plane 2", which had dozens of minor updates with no new features. One reviewer said "Minus 2 stars for frequency of updates and no explanation."

Eclipse is not great


I don't know if the issues I've had are related to the ADT plugin, communication with the emulator or Eclipse itself, but the integration of the whole Android SDK + Eclipse IDE is flaky. Often the text editing GUI hangs for 30+ seconds when you type a "." and auto-complete kicks in, with the CPU grinding away at 100%.

Other times Eclipse fails to connect to the emulator to debug or upload new apk files, and you have to restart the emulator. The emulator takes minutes to restart, it is slower to boot than even the hardware phone. Then there's the built in logcat view that will miss messages if they pass by too quickly. The command line adb logcat equivalent will show you what you missed.
I haven't tried the GUI builder nor XML editor, so cannot comment. At least in the last ADT version opening the XML layout files no longer causes a NullPointerException.

As a result of all of this, I continue to do all my development using Vim. For auto-complete I use CTags and Vim's omni completion. This is enough for C code. When editing Java I just write out all my imports the old fashioned way, like we did back before IDEs hid this crufty aspect from us all. I can always say it is helping me learn the API...

I have a Vim command :AdbReinstall that loads the APK file onto the emulator or device. I can build using :make. It's great that the Android SDK is flexible enough to allow me to do this, there are even instructions on how to set parts of it up in the official documentation. I doubt it is as easy to do for iPhone or Windows development.

Filtering based on the hardware used


One interesting characteristic of the Android Market that has matured a lot in the last few months is the way applications are filtered based on the hardware features they use.

As an example, Chaos uses the touch screen and is compiled for arm-eabi. The touch controls are actually optional - if you want you can just about get by with the trackball or d-pad and the buttons - but originally I didn't state that in the manifest. This meant that Chaos would not be seen on x86 Android devices (are there any?) nor on devices without a touch screen (examples?). This information was all auto-detected by the Market server - I didn't have to mention anything anywhere.

Once I decided that Chaos was usable without a touch screen, I added a "uses-feature" section to the manifest stating that the touch screen was optional.

There may be hardware fragmentation, but this part at least is handled in a quite developer-friendly way.

The next game I'm doing will use the compass and accelerometer for tilt controls. As far as I can tell, I won't have to state anything in the manifest to avoid disappointing folk with phones without these features, the Market will add the filters automatically (we'll see!).

Touch screen is bad for controls


The lack of physical buttons on Android means that several genres of games are not really possible, or you need to add a virtual d-pad and buttons on screen that react to touch. Unfortunately there seems to be a horrible bug in the way Android processes events when the user leaves their finger on the touch screen. When you hold your finger on the screen it leads to the touch input completely swamping the CPU to the detriment of the game's frame rate. The onTouchEvent method is called every 10 milliseconds with what amount to non-events, such as subtle changes in pressure detected by the screen's sensor. Even if all you do in the event handler is store the touch position and type, to later process in the main thread, the application will lose out on framerate as the touch event thread is given priority.

Now I may be doing something wrong here, but several similar-looking questions on stackoverflow.com seem to agree with my experience. There's a bug report on the Android project that has been fixed in Gingerbread. Sadly the lower-end devices that really need this fix will never see Android 2.3, at least not officially. Meanwhile, all the "sleep" workarounds proposed don't seem to make things any better.



There we are then. Android has some issues when compared to a closed hardware platform like the DS. You can't be 100% sure that what runs on your device will run on everyone else's. This is scary, but then there's a good chance your game will work OK. And it will probably work fine with different Android versions, screen resolutions, types of processor and hardware features. I think for free games, or if you have a free demo for people to try before they buy, then this is an acceptable compromise.

After my albeit limited experience with this, I understand now why a limited hardware platform is going to be more popular for games. If you compare the amount and variety of games released on the Apple hardware (iP*d/iPhone) to Android, the difference is huge. Giving stuff away for free is obviously not too cost effective (having embedded adverts can mitigate this, but adverts are only viable on huge hits) and creating a free demo takes time away from creating the core game. So given the choice, developers would rather have people pay for the game, right? If you expect people to pay for your game, then you'd like to think it works on their device. When you write for a platform with a limited range of hardware devices, you only have to test on those devices. With the DS, the DSi and DSiXL are identical platforms - Nintendo is very careful about backwards compatibility. You don't need need to test GBA games on a DS to check they run correctly, for example. These things just work.

Now if we consider Android, there's no way any regular developer could test all possible devices. The best you could hope for is to test on maybe some representative devices. If you support old versions of Android, a first generation HTC phone. Then maybe something like a Nexus One, then a Sony phone as they seem to have quite different Open GL hardware, maybe a Motorola and a Samsung due to their popularity. Factor the tablet-sized Android devices and we're talking quite an investment.

This doesn't include the various Android versions that may run on these devices - you probably have to target a lower API level and hope not to slip up and include newer Java API calls. Those would result in run time errors on older versions than your target API. When you write a game primarily in C it's harder to slip up like this, but still possible. For example, getting the name of the SD card directory to write to changed a lot in Froyo. Given all of this, the openness of Android is definitely off-putting if you're only in it for the money. As a tinkerer into this stuff, it certainly keeps me on my toes. Maybe the new Gingerbread SDK with its "look ma! no Java!" C-only Activities and touch screen bug fixes will lead to more Android 2.3-only (paid) games. Especially if the Sony Play-not-station phone runs this version and has any kind of popularity.

Who am I kidding? It's more likely that the iP*s will continue their rise in popularity with gamers, while Android becomes the new N-Gage. Hey I wonder if the 3DS will be hackable? :-)

4 comments:

  1. Anonymous1:54 pm

    Nice man! After testing android recently I have encountered these stuffs you're stating. the worst however I've seen in androd dev is the number of milliseconds it takes to lock the surface handler of the surfaceview.

    You stated that it is recommended to run at 30fps, I agree and that is what I have been trying till now. I am wondering if you have really done it for the android.

    Lets say we just have a new simple app, no logic just fps reading...

    My understanding is we need this surfaceview where we will draw our graphics, texts etc. now on my gameloop i tried getting the system time, making note of the previous system time so I can get the ellapsed time using both values. with this i can see that update in a loop gets called so fast like 16k calls per second.

    now since i want to draw i need to call surfaceholder.lockcanvas which is a given in all tutorials online. now what happens in the ellapsed time is it fluctuates from 35 - 140ms in my emulator and 55-70ms in an 800mhz samsung ace.

    so how was it possible to get 30fps when just locking the canvas consume that much ms. to get 30 fps you'll need 33.33 ms per frame max for both 1 update and draw call assuming we do not skip frames.

    I'd like to hear from you about how you did it if ever. thanks man

    ~Adonis Villamor
    adonis_lee_villamor@yahoo.com.ph

    ReplyDelete
  2. @Adonis I gave up looking at Canvas pretty much right away. It is a real battery killer - even the Lander example provided by Google ran terrible on the emulator and a 1st gen phone.

    I use Open GL ES for rendering - check out the "native C" post, or see Chaos in the Market -, with the usual 3-thread approach: one thread for game logic, the main thread for Android system events (so the game is not force-stopped) and the standard GL rendering thread. It's conceptually more complex, requires a bit of thought to set up, but is closer to how games work on consoles.

    There's no locking this way, the GL thread calls "swap()" to swap the buffers when your rendering is done.

    ReplyDelete
  3. Anonymous1:58 pm

    wow, I tried open GL ES before but could not find a proper guide for it for 2D. and also 3 thread game architecture. so far all I know is the game loop which calls update and draw (with some draw/frame skips in case it's slowing down). do you have a tutorial for those stuffs you metioned? if so where's the link? thanks

    ~Adonis Villamor
    adonis_lee_villamor@yahoo.com.ph

    ReplyDelete
  4. Anonymous9:35 am

    @Adonis

    I have the same problem (surfaceholder.lockcanvas takes too much time). Did you find any work around?

    ReplyDelete

Note: only a member of this blog may post a comment.