About Android and the NDK
Although Android is a Linux-based platform, it has its own totally unique userspace environment that is built on top of a custom Java runtime. Android applications are typically coded in Java and compiled into Google's own bytecode format so that they can be executed on Android's Dalvik virtual machine. Due to the somewhat insular and alien design of the Android userspace stack, porting a conventional Linux application to the platform is a highly complex and non-trivial effort.
Mozilla got the job done with Android's Native Development Kit (NDK). Developers use the NDK to compile C and C++ source code into native ARM binaries that can be loaded and used by standard Java-based Android applications. The purpose of the NDK is principally to make it possible for application developers to use native code for performance-critical computing tasks that would be impractical to perform on Dalvik. One of the secondary advantages of the NDK is that it allows developers to port and reuse existing C and C++ code.
Components that are compiled with the NDK can be used and integrated into an Android Java application via the Java Native Interface (JNI), a framework that serves as a bridge between a Java virtual machine and native code. Native code functions can be exported through JNI and made accessible to higher-level Java applications.
How the Firefox port works
To make Firefox run on Android, Mozilla modified the Firefox browser's Gecko rendering engine so that it could be compiled with the NDK and used in an Android application through the JNI.
Gecko is one of the core components of Firefox's underlying architecture. It handles parsing, layout, drawing, and much of the other functionality that is needed to display and support interaction with a webpage. In addition to rendering Web content, the Gecko engine is also used to render Firefox's user interface, which is described with an XML markup language called XUL.
Mozilla wrote a very small amount of Java code that is used as glue to make the native Gecko components accessible as an application in the Android environment. This code can be found in the embedding/android directory of the Firefox for Android source code. You can see how it works by having a look at the GeckoApp.java file.
The GeckoApp class is an Android Activity that handles the basic window layout and component initialization. The GeckoAppShell class loads the native libraries and starts running a Gecko engine in a thread. The GeckoSurfaceView class is an Android drawing buffer on which Gecko will render the page content and application user interface. The surface is embedded in the GeckoApp activity and painted to the screen. The surface is also responsible for relaying input events, such as screen taps and key-presses, to the underlying engine.
It's really important to understand that Gecko is used to draw the entire browser. In the Android port of Firefox, the menus, toolbars, and dialogs are all coded with XUL and rendered by Gecko instead of being built with standard Android widgets.
If Mozilla wanted to, they could build the user interface with real Android widgets and rely on Gecko solely for rendering Web content (an approach that is similar to the one used by Maemo's MicroB browser), but there are a number of highly compelling advantages offered by XUL that make it more desirable for a mobile Firefox browser. The principal advantage of using XUL for the mobile browser's user interface is that it is more conducive to supporting Firefox's much-loved add-ons.
One of the downsides of XUL as an independent, cross-platform toolkit, is that it doesn't automatically match the look-and-feel of other applications on any given platform. Mozilla compensates for this deficiency on the desktop by using the underlying platform's theming APIs to make the XUL widgets mimic the appearance of their native counterparts.
In mobile environments, Mozilla has taken a somewhat different approach with widget styles. Mozilla's developers have built a unique user interface with XUL that is tailored for small form factors and touch interaction, but doesn't necessarily bear a resemblance to the underlying platform. This tactic has delivered pretty slick results on Maemo.
This custom mobile interface hasn't yet been adapted for use by the Android port, however, which is currently using the same XUL templates as the desktop version of Firefox. This looks silly on a mobile device, but it's a temporary measure that is suitable for testing purposes at the present time. Mozilla says that the style that is used by Firefox Mobile on the N900 will eventually be used for the Android port.
Building the browser
For those who are wise in the ways of mozconfig, building Firefox from source is generally a simple matter. The process is almost entirely automated and requires very little operator intervention. If you are trying to do anything fancy, however, it can be a huge pain. Getting the Android port to compile proved to be a bit challenging. The port hasn't been merged into the main Firefox code tree yet, but it's available from a mozilla-droid branch that is undergoing heavy active development.
The Android page at the Mozilla wiki has some preliminary documentation, including a rough sketch of the steps that are required to obtain and compile the code. The instructions are a bit stale, so I had to improvise in a few places in order to get it to work.
You start by downloading the code from Mozilla's Mercurial-powered version control system. The next step is downloading and decompressing the Android SDK and the NDK. Mozilla had to make several minor changes to the NDK in order to get Gecko to compile, but they have fortunately published a patch on the Wiki page that you can use to apply their changes to your own local installation of the NDK.
Mozilla also helpfully supplies a set of build options that you can put in your mozconfig file. You have to customize the paths on the NDK and SDK lines so that it will know where to look for those components during the build process. You can ignore the section of the wiki page that explains how to build and run the JS/NSPR components—those sections are not applicable when you are compiling the entire browser.
Before you start compiling, you will need to set up a basic Firefox build environment. It's easy to do on Ubuntu, which has packages for all of the relevant dependencies. After you have everything set up, you run the make -f client.mk command in the top-level mozilla-droid directory.
This is when the hard part starts. The build process will periodically crap out and complain about missing bits. You will have to find and supply the necessary pieces in order to continue. I suspect that the exact things you'll need will differ as the code base evolves.
The biggest missing dependency seems to be the header files for Skia, the 2D graphics library that is used by Android. I had to download Skia's source code from its Subversion repository and copy the header files from include/core to mozilla-droid/widget/src/android. In the same directory, I also had to add copies of git://android.git.kernel.org/platform/system/core.git/include/cutils and git://android.git.kernel.org/platform/frameworks/base.git/include/utils.
After I provided those items, the build process made it all the way to the linking step, where it complained that it couldn't find -landroid_runtime and -lskia. I realized that those are Android libraries and was a bit surprised that the NDK itself doesn't include them. It's entirely possible that I missed a step at some point or failed to configure something that provides them. I ended up getting the necessary libraries from a live Android environment within the emulator.
The Android SDK comes with a tool called adb that can be used to interact with an emulated Android environment in various ways. The adb push and adb pull commands are used to copy files into and out of the Android environment. You can also use the adb shell command to get an interactive root shell for controlling the emulated platform. With the help of these commands, I pushed an executable of BusyBox into the emulator and used it to tar up the contents of /system/lib within the emulator shell. I used adb pull to get the tar file out and then I expanded it in android-ndk-1.6_r1/build/platforms/android-4/arch-arm/usr/lib. After I did that, the build process was able to finish successfully.
Under normal circumstances, you would test a Firefox build by running dist/bin/firefox-bin and generate a distributable tarball by running make package in the top-level directory. But that's obviously not going to work for the Android port. The final stage of the build process for Firefox on Android is compiling the Java code and generating an APK file, an Android installation package.
You can do that by going to the mozilla-droid/embedding/android directory that we discussed earlier in the article. All you have to do is run make in that directory and it will spit out your APK files. It will generate gecko.apk and gecko-unaligned.apk. Android has a tool called zipalign that optimizes packages for memory purposes. You are technically only supposed to use aligned packages, but I didn't really have much success getting gecko.apk to run during my tests. I was, however, able to successfully install and run the gecko-unaligned.apk package.
Running Firefox on Android
You can install Firefox into a live emulator instance by using the adb install command. The APK file itself is 15MB, but it uses roughly 53MB of space when it is decompressed. You have to make sure that your emulated Android filesystem has enough room to accommodate it, otherwise it will fail. You can do this by manually starting the emulator from the command-line and setting a high value for the -partition-size parameter.
After you install the package, you can run the program by clicking the GeckoApp item in the applications list. When the program runs, it will display a button labeled "Launch" in the top left corner of the screen. You can click this to start the actual Gecko process. There is generally some lag while it is getting everything set up. It doesn't consistently work and will sometimes crash before the actual browser starts. I had to try a few times before I could get it to work.
I wasn't able to get it to do much during my tests, but I was able to ascertain that it works. As the development is still at an early stage, it hasn't received much optimization yet. The performance consequently leaves a lot to be desired. It didn't seem to respond to keyboard input, but it was able to interpret my clicks. It's not very responsive at this point, so there was a noticeable delay between when I clicked and when it responded to my click.
tested several features, including the bookmark system and page loading. To work around the lack of functioning keyboard input, I modified the Places SQLite database and added several sites to the browser's bookmarks. I was able to use those bookmarks to get the browser to load various sites that I wanted to test. It had no trouble rendering Ars Technica, but it couldn't manage the site's relatively modest JavaScript. The browser popped up a warning window prompting me to terminate the script because it was taking so long to run.
It's important to remember that applications suffer from a performance penalty when they run inside of the emulator. It's likely that the performance will be slightly better on real hardware. The use of the full desktop Firefox interface is also a factor that could be negatively impacting performance. The mobile user interface could be more responsive. If the development history of the Maemo version is any indication, it's a sure bet that we will see Firefox's performance on Android improve considerably as the port matures.
I really want to emphasize the fact that what we tested in this article is NOT a release, a prerelease, or an official build from Mozilla. I copied the code directly from the active working branch of a Mozilla developer and poked it with a sharp stick until I got it to compile. The purpose of this article is to shed some light on the development process and provide a helping hand to other enthusiasts who want to get it to compile. The bugs, performance issues, and other limitations that I've discovered are not indicative of what the final product will be like.
Firefox on Android is obviously not something that you would want to install and run on your handset today, but it has also clearly evolved beyond the proof-of-concept stage. It may not be practical to use yet, but it's a tremendously impressive feat that demonstrates Firefox's flexibility and illuminates the value and potential that can be unlocked by using Android's NDK. When it matures, it will bring more choice to users who are surfing the Web on Android-based devices.