Android is the Google OS for mobile phone (and the future GPhone). Android provides a Java-like virtual machine: Dalvik.
So, why not trying to execute an iPOJO-based application on the top of Android?
The idea was to embed an OSGi/iPOJO framework on Android and then to deploy an application on the top of the framework.
You can download the application in two parts:
the Android application embedding Felix/iPOJO :
here
the SpellChecker application :
hereThe application is designed as follow:
- The Android application creates and starts a OSGI/iPOJO framework (Felix + iPOJO + FileInstall)
- The Android application tracks a ViewFactory service. This service is used to display the application GUI (the service allows creating the GUI main component).
- The SpellChecker application (10 minutes tutorial) is deployed on OSGi, and use iPOJO. The only difference with the one form the 10 minutes tutorial is the GUI that uses Android HMI component (rather than Swing)
SpellChecker application bundles are deployed thanks to FileInstall. A specific folder is monitored in order to install/update/uninstall jar files contained in this folder.
Embedding Felix and iPOJO inside an Android Application
First, we need to create an Android application. This application creates a new Felix when the application is instantiated:
public synchronized void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
PrintStream out = new PrintStream(new OutputStream(){
ByteArrayOutputStream output = new ByteArrayOutputStream();
@Override
public void write(int oneByte) throws IOException {
output.write(oneByte);
if (oneByte == '\n') {
Log.v("out", new String(output.toByteArray()));
output = new ByteArrayOutputStream();
}
}});
System.setErr(out);
System.setOut(out);
m_configMap = new StringMap(false);
m_configMap.put(FelixConstants.LOG_LEVEL_PROP,String.valueOf(Logger.LOG_DEBUG));
m_configMap.put(DirectoryWatcher.DEBUG, "1");
// Configure the Felix instance to be embedded.
m_configMap.put(FelixConstants.EMBEDDED_EXECUTION_PROP, "true");
File bundles = new File(FELIX_BUNDLES_DIR);
if (!bundles.exists()) {
if (!bundles.mkdirs()) {
throw new IllegalStateException("Unable to create bundles dir");
}
}
m_configMap.put(DirectoryWatcher.DIR, bundles.getAbsolutePath());
// Add core OSGi packages to be exported from the class path
// via the system bundle.
m_configMap.put(Constants.FRAMEWORK_SYSTEMPACKAGES, ANDROID_FRAMEWORK_PACKAGES);
// Explicitly specify the directory to use for caching bundles.
try {
m_cache = File.createTempFile("felix-cache", null);
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
m_cache.delete();
m_cache.mkdirs();
m_configMap.put(BundleCache.CACHE_PROFILE_DIR_PROP, m_cache.getAbsolutePath());
}
When the application starts, the Felix Framework is started with two Activators :
- one installing bundles from resources
- one installing bundles from a folder (FileInstall)
Then a service tracker is used to track arrivals and departures of the ViewFactory (provided by the SpellChecker GUI).
public synchronized void onStart() {
super.onStart();
setContentView(new View(this));
Resources res = getResources();
try {
List<BundleActivator> activators = new ArrayList<BundleActivator>();
// Plugs the bundle installer (install bundle from application resources)
activators.add(new Installer(res));
// Plugs the FileInstall activator
activators.add(new FileInstall());
m_felix = new Felix(m_configMap, activators);
m_felix.start();
} catch (BundleException ex) {
throw new IllegalStateException(ex);
}
try {
m_tracker = new ServiceTracker(m_felix.getBundleContext(),
m_felix.getBundleContext().createFilter("(" + Constants.OBJECTCLASS
+ "=" + ViewFactory.class.getName() + ")"),
new ServiceTrackerCustomizer() {
@Override
public Object addingService(ServiceReference ref) {
System.out.println("======= Service found !");
final ViewFactory fac =
(ViewFactory) m_felix.getBundleContext().getService(ref);
if (fac != null) {
runOnUiThread(new Runnable() {
public void run() {
setContentView(fac.create(ApacheFelix.this));
}
});
}
return fac;
}
@Override
public void modifiedService(ServiceReference ref,
Object service) {
removedService(ref, service);
addingService(ref);
}
@Override
public void removedService(ServiceReference ref,
Object service) {
m_felix.getBundleContext().ungetService(ref);
runOnUiThread(new Runnable() {
public void run() {
setContentView(new View(ApacheFelix.this));
}
});
}});
m_tracker.open();
} catch (InvalidSyntaxException e) {
e.printStackTrace();
}
}
Note that the iPOJO bundle is deployed from application resource. The bundle was “dexed” and placed in the res/raw folder of the application project. Then, you can load the bundle with:
public void start(BundleContext arg0) throws Exception {
InputStream is = res.openRawResource(R.raw.ipojo);
Bundle bundle = arg0.installBundle(IPOJO_HTTP_PATH, is);
bundle.start();
}
Before the be installed, the application must be exported in a .apk file.
SpellChecker application
The application is basically the same than the one form the 10 minutes tutorial. There are two differences:
- Bundles are “dexed”
- The GUI bundle is implemented with Android GUI components
Another difference is about the optionality of the dependency on SpellChecker service. This dependency is now optional, and use two bind/unbind methods:
public synchronized void bindSpellChecker(SpellChecker sp) {
checker = sp;
if (activity != null) {
activity.runOnUiThread(new Runnable() {
public void run() {
// Enable button
m_button.setEnabled(true);
m_result.setText("Enter words, and click on 'check'");
m_main.invalidate();
System.out
.println("==> Spell checker GUI receives
a new spell checker ...");
}
});
}
}
public synchronized void unbindSpellChecker(SpellChecker sp) {
checker = null;
if (activity != null) {
activity.runOnUiThread(new Runnable() {
public void run() {
// Enable button
m_button.setEnabled(false);
m_result.setText("No Spellchecker available");
m_main.invalidate();
System.out
.println("=====> Spell checker GUI was
unbind from the spell checker");
}
});
}
Note that GUI modifications
MUST be done in the UI Thread.
Starting the emulator
The application uses a SD card storage. So creates an SDCard iso:
mksdcard size filename
Then, launch the emulator (emulator must be accessible from your path) with:
emulator -shell -sdcard dev/android/sdcard1.iso
The emulator starts. When the shell is ready, change the permission access on /data/dalvik-cache:
chmod 777 /data/dalvik-cache
These permissions are needed to correctly load bundles.
Installing the application
To install the application (the .apk file), I use the Eclipse plugin (the apk file can be unsigned) or with the adb command (the apk file must be signed).
Once signed, you can deploy your application with:
adb install application.apk
Once installed, the application is available in the Android emulator menu
If you launch the application, a black screen appears … The Spellchecker application is not deployed!
So, to deploy the application, the bundles files must be placed in the /data/felix/bundles folder.
adb push SpellCheckGui\ for\ Android.jar /data/felix/bundles/SpellcheckGui.jar gui.jar
cd bundles
adb push spell.services.jar /data/felix/bundles/spell.services.jar
If you launch the application, the GUI appears, but you can’t use the check button. No spell checker services are available. Push the others jars in the same folder.
adb push spell.checker.jar \
/data/felix/bundles/spell.checker.jar
adb push spell.english.jar \
/data/felix/bundles/spell.english.jar
Once done, the check button becomes available. If you remove a the English dictionary, the check button becomes disabled:
adb shell rm /data/felix/bundles/spell.english.jar
adb push spell.english.jar /data/felix/bundles/spell.english.jar
Conclusion
This very simple application has shown that it is possible to create dynamic applications on the top of Android with OSGi and iPOJO. It sounds really promising …
References