Experiences porting iPhone apps to Android
December 2nd, 2009 by David BrackeenThis year I had the oppurtunity to port two iPhone apps to Android. One was a game, another a communication app. I wanted to share some of the gritty details while they’ve been bouncing around my head.
With respect to my clients (well actually it’s because of those crazy NDAs), for the purposes of this blog, I’ll call the two apps I ported “the Game App” and “the Chat App”. Also, I mention some code below, but unfortunately I can’t share that either.
In the (near) future, there may be none of this “porting” nonsense – maybe we’ll all be building apps using cross-platform APIs like PhoneGap or Flash CS5. But in the case of these two apps, they were already written in Objective-C, and they needed to be converted to run on Android. So I got to work.
In some cases, Objective-C can be directly translated to Java, while in other cases new Java code has to be written from scratch. Things like string manipulation, XML handling and SQLite interfaces aren’t that different on the two platforms. However things like UI code can be incredibly different.
UI layout
Neither of the two iPhone apps I ported used Interface Builder.
The Game App used UIViews (mostly UIImageViews) and CoreAnimation. This is an acceptable choice for a simple 2D game. So, for the port, I thought I’d also use Android’s UI scene graph and animation framework. I was able to do it, but it required a lot of supporting classes for layout and animation, and in the end, it proved to be more difficult than it was worth. It would have been faster to write a custom scene graph. Here are some of the issues:
- Creating a custom Layout. Android requires Layouts for placing Views, and there was no existing Layout that could place a View based on a position and an anchor (the iPhone version made heavy use of anchorPoints for placement).
- Creating custom Animation classes. By default, the animation framework couldn’t do the type of key-frame animation that CoreAnimation can do (it took a while just to figure that out). I ended up having to create my own Animation class (and subclass AnimationSet) to get it right. This took a long time to figure out because it was a trial-and-error process. Unfortunately, for this app, Android has much longer build times than iPhone, which greatly impacts any trial-and-error process (on my machine, 20 seconds from hitting the “build” button to getting it to run on the emulator, vs. 2-4 seconds for the iPhone version).
The Chat App was more straightforward, but I needed to use RelativeLayouts on Android, and it was a bit difficult to create a RelativeLayout that matched the absolute positioning on the iPhone. The biggest problem was one of the relative layouts was considered recursive: I was laying out a view group from the bottom up, where the final dimension of the view group was defined by its contents. This was solved by determining the dimension of the view group using trial-and-error at runtime.
The other minor problem with the Chat App was that the iPhone version did some long-running networking in the UI thread. This isn’t the best practice, but it isn’t a problem on iPhone if you don’t have any usable UI controls on the screen at the time (there was just a spinning activity indicator). However, this is a big no-no on Android, which interrupts the user with a dialog asking the user to “Force Close” the app or “Wait”. (You could say iPhone has a physical “Force Close” button – the Home button. On Android the Home button doesn’t force close the running app). Luckily, using Android’s AsyncTask was an easy fix.
Another problem with the Chat App was with contact picking. Unfortunately, the old Contacts API was deprecated in Android 2.0. It still works, but on Android 2.0, the returned values are different from the Android 1.5 version, which required some extra code to fix. Also, at least one device (Motorola CLIQ) has a custom built contact picker which gives the user an error dialog every time it runs (to be fair, this is supposed to be fixed in an upcoming OTA release).
The little things
One thing to keep in mind is Android’s lack of a JIT and it’s slow garbage collection. Eventually this will be fixed, but that fact, and the fact that most Android devices’ hardware is slower than the iPhone, makes the experience of these two apps not as smooth on Android.
One strange problem is that many (or all?) Android devices use 16-bit color. Even on the Droid, which is advertised to have a 24-bit color depth, shows obvious color banding when I displayed a gradient that looked fine on the iPhone. (Apple doesn’t say what the color depth of the iPhone is, but based on how that gradient appeared, I’m guessing it’s 24-bit).
This is a bit off-topic for a moment, but one of the weirdest things about both platforms is launching the email client. Compare iPhone:
NSString* subject = NSLocalizedString(@"Subject", nil); NSString* body = NSLocalizedString(@"Body", nil); NSString* encodedSubject = [subject stringByAddingPercentEscapesUsingEncoding: NSASCIIStringEncoding]; NSString* encodedBody = [body stringByAddingPercentEscapesUsingEncoding: NSASCIIStringEncoding]; NSString* url = [NSString stringWithFormat: @"mailto:?subject=%@&body=%@", encodedSubject, encodedBody]; [[UIApplication sharedApplication] openURL: [NSURL URLWithString: url]];
To Android:
String subject = getString(R.string.subject);
String body = getString(R.string.body);
Intent mail = new Intent(Intent.ACTION_SEND);
mail.setType("text/plain");
mail.putExtra(Intent.EXTRA_TEXT, body);
mail.putExtra(Intent.EXTRA_SUBJECT, subject);
Intent chooser = Intent.createChooser(mail, getString(R.string.email_description));
startActivity(chooser);
Both are awkward, wouldn’t you say? Why not something like this?
String to = ""; String subject = getString(R.string.subject); String body = getString(R.string.body); context.email(to, subject, body);
Distribution
Obviously the Android Market wins in terms of time-to-release. Fifteen minutes and you’re done – no two-week review, no fear of rejection. But there was a couple oddities to deal with.
The Game App had both Full and Lite versions of the game. On the iPhone, both versions used the same codebase, and a few preprocessor conditionals determined which code to execute for each version. Java has no preprocessor conditionals, and also, the Android Market requires that each app have a unique package name. This posed a problem since it would be wise for the two versions to share the same codebase on Android, too. (Creating two projects meant maintaining two codebases!) The problem was exasperated by the fact that Android references image resources by package name, too.
What I did is write a script that programatically copied the project and changed the package name along the way. Everywhere in the manifest, Eclipse project files, and every source file was changed. A single source file named Build.java had a boolean static field named LITE which was programmatically changed as well. The script worked surprisingly well.
Also, the Full version was accidentally marked as “free”. We pulled it, but we couldn’t convert it to a paid app, and we couldn’t re-upload because the package name was already in use. Support to Google didn’t help. We ended up using the package-name script again to create a new Full version that we could upload and make a paid app.
Closing thoughts
If you’re looking to port an iPhone app to Android, it might be a good idea to do some code cleanup first. It’s important to keep the model code clearly separated from the view code. Porting model code, which involves more direct translation rather than re-write, is a lot easier if it’s not mixed in with the views.
Also, watch out when searching for Android example code. I ran into a few examples that use old, pre-1.0 code that doesn’t even compile anymore.
All in all, it’s has been a strange ride. I kind of feel like porting these two apps gave me some new skills that will be obsolete soon, but we’ll see. At least, if I have to do it again, I’ve got a few tricks up my sleeve to make it a bit easier.









