Reflections on Porting an iOS app to Android

13 Apr 2011

I wanted some Android development experience, so thought it made sense to port an app where I could re-use most of the content. I recently released GoSwedish for Android, and I thought I’d note some of the differences between iOS and Android platforms that surprised me.

Icons

Initially I just quickly glanced at the sizing requirements for Android icons and created the low, medium and high resolution versions from same 512x512 PSD that I used for iOS. The icon looked fine on the app screen that had a black background, but when I moved it to the home screen the icon had a black box around it. Terrible.

After actually reading the spec, I learned that the icons have a transparent background, there are specific requirements for the drop shadow and dead space around the icon. Oh, and they shouldn’t be glossy.

Screenshots

There is no easy way to take a screenshot with Android. You have to connect your device via USB and fire up a java app from the SDK called ddms. On iOS you simply press the power and home buttons at the same time. This seems like a minor complaint, but only when this feature was missing did I realize how much I relied on screenshots as part of my workflow for making a note of bugs or capturing problems with the layout.

Interface

I started development before having actually used an Android device. I didn’t actually know how Android apps were supposed to work, so I was trying to make it work like the iOS app. One huge difference with Android between iOS is that not every control is on screen. Most notably, the Back and Menu buttons.

The interface for GoSwedish is simple: You view a list of lessons and select one to start. Once you’re in a lesson, you can go back to the main lesson list at any time. Here’s the interface, when you’re in a lesson in iOS.

And here is my initial attempt on Android.

I had made my own custom back button, and spent hours (yes, hours!) fiddling with the XML layout to position the buttons. (That’s a whole other blog post.) Once I got my hands on an Android device, I realized that the physical buttons are a core part of the interaction style. So, I removed the on screen Back button and exposed the Back and View all functionality in the menu.

(Personally, I still find the physical button interaction to be less intuitive and requires more cognitive load. And the location of the buttons at the “back” button at the bottom-left of the phone is incredibly awkward, but when in Rome…)

Versions

There are a lot of Android versions out in the wild, and so a key question for any Android dev is, “What platform to target?” Google recently posted information on the breakdown of devices in their Market. There may be a lot of older 1.6 devices out there, but their owners aren’t buying apps.

Source: http://developer.android.com/resources/dashboard/platform-versions.html

I was also advised to not bother with 1.6 as it had problems with large content-heavy apps. It seems Android has an interesting storage system…

Internal/External Storage

Storage is one of those details that users and developers don’t have to think about on iOS. If you have space, the app will install. On Android, you have to be aware of the two storage areas and have to make sure that the smaller internal one doesn’t fill up. If it does, you have to move apps to the external storage. But some apps can’t or won’t run on external.

This system is annoying, but it also causes problems for apps with several MB of assets. To support 1.6, I would have to download assets from within the app to the external storage. In light of the stats of people using the Market (and writing a download manager sounded like a huge amount of work), I decided to only support 2.1 and higher.

Piracy

Xcode and the AppStore handle all the encryption surrounding an app. There are some annoying hoops to jump through, but once you’ve done it a few times, protecting your app against piracy isn’t something you need to worry about. (Of course, there are ways to remove the encryption, but I just want to make it easier to pay for an app than to steal it.)

Android has copy-protection that would only allow an app to be installed on the internal storage. That wouldn’t work for an app that was over a few megs, since it would take up all the precious internal storage. But it doesn’t matter, because the copy-protection system is now deprecated in favour of a licensing check system. The app will periodically validate that it was purchased from the Market for the current account.

The Android docs walk you through adding the licensing code – and it’s mostly boilerplate. For testing, in the Market admin area you can set various response codes to test that the code works.

The only major drawbacks are if the licensing server goes down, legitimate users might not be able to run the app. And if I wanted to submit the app to another app store (e.g. Amazon), I would have to remove all the Android Market license check code.

Animation

GoSwedish has a simple animation when the audio is playing. Both iOS and Android support animations and the way they do it is representative of their respective approaches.

In iOS the code is:

UIImageView *imgAnimate = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,50,50)];
imgAnimate.animationDuration = 1.0f;
imgAnimate.animationImages = [NSArray arrayWithObjects:
			[UIImage imageNamed:@"f1.png"],
			[UIImage imageNamed:@"f2.png"],
			[UIImage imageNamed:@"f3.png"],
			[UIImage imageNamed:@"f4.png"],
			[UIImage imageNamed:@"f5.png"],
			[UIImage imageNamed:@"f6.png"],
			nil];
[imgAnimate startAnimation];

In Android, the animation is defined with XML.

<?xml version="1.0" encoding="utf-8"?>
<animation-list     
    xmlns:android="http://schemas.android.com/apk/res/android" 
    android:oneshot="false">
    <item android:drawable="@drawable/bird_f1" android:duration="150" />
    <item android:drawable="@drawable/bird_f2" android:duration="150" />
    <item android:drawable="@drawable/bird_f3" android:duration="150" />
    <item android:drawable="@drawable/bird_f4" android:duration="150" />
    <item android:drawable="@drawable/bird_f5" android:duration="150" />
    <item android:drawable="@drawable/bird_f6" android:duration="150" />    
</animation-list>

Assuming the XML file was called birdsing.xml, then you can assign the animation with:

ImageButton mute = (ImageButton)findViewById(R.id.buttonMute);
mute.setImageResource(R.anim.birdsing);

Other than the overly verbose XML which I find hard to recall from memory - it’s not too bad. But in practice, the animation sometimes wouldn’t start when the Activity was first loaded, so this hack was required:

final AnimationDrawable frameAnimation = (AnimationDrawable)mute.getDrawable();
mute.post(new Runnable(){
	@Override
	public void run() {
		frameAnimation.start();                
  }            
});

Naming Resources

Android provides a mechanism to access the id of any resources in the app. It’s a nice hack. You can play an audio file simply with:

MediaPlayer mp = MediaPlayer.create(getApplicationContext(), R.raw.woohoo);
mp.start();

However, this also means that filenames can only contain lowercase letters, digits, periods or dashes. I had hundreds of mp3 files, which I didn’t want to have to rename from the iOS app. I was able to put them in the “assets” directory, and then it’s just a bit more work to access them.

AssetManager am = getAssets();
AssetFileDescriptor fd;
fd = am.openFd("mp3/" + name + ".mp3");
mp.reset();		// required to set datasource again
mp.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
mp.prepare();
mp.start();

• • •

So those are some of the biggest issues that jumped out at me while developing for Android. There’s lots more items that I could mention, but I’ll save them for another post. Feel free to ask any specific questions in the comments.


Older: Why In-App-Purchase is a Mistake

Newer: Using a Repeating NSTimer in a UIViewController


View Comments

Related Posts

Recent Posts