Cài đặt Apache Felix trên Android

(soủce: http://lsd.luminis.eu/en/osgi-on-google-android-using-apache-felix/)

UPDATE: A lot has changed in the world of Android & OSGi since writing this post. For the latest developments around this topic, go to: EZdroid community
Since Android 1.5 it is now possible to run Apache Felix without any changes on the phone!

So I was playing around with the Android Google is building … As somebody involved with OSGi for quite some years and a regular contributor to Apache Felix (the OSGi implementation of the Apache Software Foundation) the objective is clear: this platform needs an OSGi framework and Apache Felix is what it is going to be!

The task at hand can be split in two parts namely, figure out how to run the framework itself and extend it to be able to dynamically load bundles.

The former shouldn’t be a big problem because Apache Felix is easily portable and I personally made it run on a quite a number of JVMs. The later qualifies as tricky because for not yet publicly announced reasons Google chooses to not really provide a real JVM but something similar called dalvik.

One of the biggest differences (apart from added API) is that dalvik doesn’t execute Java bytecode directly but needs its own format. Tools are provided to convert from existing Java bytecode to dalvik alas, the conversion is not done at runtime.

Apache Felix on Google Android

The first step is to find the actual runtime on the emulator to save me going through the hassle of rebuilding and redeploying all the time:

> emulator &
> adb shell
# /system/bin/dalvikvm -Xbootclasspath:/system/framework/core.jar -version
    DalvikVM version 0.2.0
    Copyright (C) 2007 Google, Inc.
    Blah blah blah LICENSE blah blah.

Notice the funny last line – I’m not making this up :-) .

So for a straight forward scenario all we need is the felix.jar with the added dalvik bytecode:

> dx --dex --output=classes.dex bin/felix.jar
> aapt add bin/felix.jar classes.dex
> adb push bin/felix.jar /root/felix/bin/felix.jar
> adb shell
# cd root/felix
# /system/bin/dalvikvm -Xbootclasspath:/system/framework/core.jar \
  -classpath bin/felix.jar org.apache.felix.main.Main

Voila (well, in reality I needed to patch a little to get to this point because there was a bug or two – I’ll commit the fix to trunk).

Dynamically Loading Bundles on Google Android

The next part is the most critical one because we will need to do something that is officially not supported. We need to be able to load bundles i.e., dynamically load code. There is a way to do something in this direction in the official api but nothing close to the power of what we need (and want) for OSGi — hence, I spend sometime finding a loop-hole. It turns out that the normal ClassLoader approach will throw an exception saying that it is not possible to load java byte code. Fortunately, there is a class available that allows to load classes from dex files.

private static final Constructor m_dexFileClassConstructor;
private static final Method m_dexFileClassLoadClass;

static
{
    Constructor dexFileClassConstructor = null;
    Method dexFileClassLoadClass = null;
    try
    {
        Class dexFileClass =  Class.forName("android.dalvik.DexFile");
        dexFileClassConstructor = dexFileClass.getConstructor(
            new Class[] { java.io.File.class });
        dexFileClassLoadClass = dexFileClass.getMethod("loadClass",
            new Class[] { String.class, ClassLoader.class });
    }
    catch (Exception ex)
    {
        dexFileClassConstructor = null;
        dexFileClassLoadClass = null;
    }
    m_dexFileClassConstructor = dexFileClassConstructor;
    m_dexFileClassLoadClass = dexFileClassLoadClass;
}

private Object m_dexFile = null;

public synchronized Class getDexFileClass(String name, ClassLoader loader)
    throws Exception
{
    if (m_dexFile == null)
    {
        if ((m_dexFileClassConstructor != null) &&
              (m_dexFileClassLoadClass != null))
        {
            m_dexFile = m_dexFileClassConstructor.newInstance(
                new Object[] { m_file });
        }
        else
        {
            return null;
        }
    }

    return (Class) m_dexFileClassLoadClass.invoke(m_dexFile,
        new Object[] { name.replace('.','/'), loader });
}

This is the equivalent to using com.sun.* classes because the class is not inside the official package namespace but at any rate:

> dx --dex --output=classes.dex \
   bundle/org.apache.felix.shell-1.1.0-SNAPSHOT.jar

> aapt add bundle/org.apache.felix.shell-1.1.0-SNAPSHOT.jar classes.dex

> adb push bundle/org.apache.felix.shell-1.1.0-SNAPSHOT.jar \
  /root/felix/bundle/org.apache.felix.shell-1.1.0-SNAPSHOT.jar

> dx --dex --output=classes.dex \
   bundle/org.apache.felix.shell.tui-1.1.0-SNAPSHOT.jar

> aapt add bundle/org.apache.felix.shell.tui-1.1.0-SNAPSHOT.jar \
   classes.dex

> adb push bundle/org.apache.felix.shell.tui-1.1.0-SNAPSHOT.jar \
  /root/felix/bundle/org.apache.felix.shell-1.1.0-SNAPSHOT.jar

We also need a configuration that will make use of the bundles:

> adb push conf/config.properties /root/felix/conf/config.properties
> adb shell
# cd root/felix
# /system/bin/dalvikvm -Xbootclasspath:/system/framework/core.jar \
  -classpath bin/felix.jar org.apache.felix.main.Main

Welcome to Felix.
=================

-> ps

START LEVEL 1
   ID   State         Level  Name
[   0] [Active     ] [    0] System Bundle (0.0.0)
[   1] [Active     ] [    1] Apache Felix Shell Service (1.1.0.SNAPSHOT)
[   2] [Active     ] [    1] Apache Felix Shell TUI (1.1.0.SNAPSHOT)

-> shutdown

Now, for the added fun and as a prove of concept we are going to use the telnet bundle available form the OBR side. It is a little bit more involved because of an inner jar that needs to be processed as well.

> mkdir unpack && cd unpack
> jar -xf ../bundle/telnetd.jar
> dx --dex --output=classes.dex \
   com/softsell/open/osgi/telnetd/dtw.TelnetD.jar
> aapt add com/softsell/open/osgi/telnetd/dtw.TelnetD.jar  \
   classes.dex
> rm classes.dex
> jar -cmf META-INF/MANIFEST.MF ../bundle/telnetd.jar *
> cd .. && rm -rf unpack
> dx --dex --output=classes.dex bundle/telnetd.jar
> aapt add bundle/telnetd.jar classes.dex
> adb push bundle/telnetd.jar  /root/felix/bundle/telnetd.jar
> adb shell
# cd root/felix
# /system/bin/dalvikvm -Xbootclasspath:/system/framework/core.jar \
  -classpath bin/felix.jar org.apache.felix.main.Main

Welcome to Felix.
=================

-> ps

START LEVEL 1
   ID   State         Level  Name
[   0] [Active     ] [    0] System Bundle (0.0.0)
[   1] [Active     ] [    1] Apache Felix Shell Service (1.1.0.SNAPSHOT)
[   2] [Active     ] [    1] Apache Felix Shell TUI (1.1.0.SNAPSHOT)

-> install file:bundle/telnetd.jar
Bundle ID: 3
-> start 3
[Listening to Port 6623 with a connectivity queue size of 5]

With a little bit of work we are now able to telnet into our framework (we first have to add an redirect of the port):

> telnet localhost 5554
 Trying ::1...
  Connected to localhost.
 Escape character is '^]'.
 Android Console: type 'help' for a list of commands
  redir add tcp:4711:6623
 OK
 quit

Now the framework should be reachable on localhost under port 4711:

> telnet localhost 4711

 Welcome to the SoftSell OSGI Telnet Server

 Available Services:

 Felix

Enter Choice> Felix

Welcome to the OSGI command shell
=================================

-> ps
  ID   State         Level  Name
[   0] [Active     ] [    0] System Bundle (0.0.0)
[   1] [Active     ] [    1] Apache Felix Shell Service (1.1.0.SNAPSHOT)
[   2] [Active     ] [    1] Apache Felix Shell TUI (1.1.0.SNAPSHOT)
[   3] [Active     ] [    1] telnetd (1.0.0)
-> shutdown
connection closed by foreign host.

Success! With a small patch that enables to load classes from a dex file dynamically we have an OSGi framework working on Google’s Android. I will commit the patch for the bugs soon and the binary as well as the real patch are available from our opensource server.

That’s all for today; happy bundle hacking :-)

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s