Android is missing some APIs.
Android does not include a full set of javax packages. These APIs have been omitted for various reasons:
Although many javax.* APIs aren't included in the core platform, application developers can still use the APIs by repackaging them. Repackaging involves moving classes from the protected javax.* namespace into an application namespace like com.mycompany.*.
This guide describes how to repackage an API. It assumes you are using Ant to build your application.
Complete source code for the finished example used in this guide is available here. You can check out and build a copy with:
svn checkout http://dalvik.googlecode.com/svn/trunk/examples/hello_stax hello_stax cd hello_stax android update project -p . ant debug Copy Jars
We need the .jar files that contain the code of interest. Many JSRs offer code for download on java.net or code.google.com. Copy the .jarfiles to the project's libs directory. At this point, building the project should fail with an error:
$ ant installBuildfile: build.xml [setup] Project Target: Android 2.0.1 [setup] API level: 6 ... -dex: [echo] Converting compiled files and external libraries into /home/jessewilson/svn/dalvik/examples/hello_stax/bin/classes.dex... [echo] [apply] [apply] trouble processing "javax/xml/namespace/NamespaceContext.class": [apply] [apply] Attempt to include a core class (java.* or javax.*) in something other [apply] than a core library. It is likely that you have attempted to include [apply] in an application the core library (or a part thereof) from a desktop [apply] virtual machine. This will most assuredly not work. At a minimum, it [apply] jeopardizes the compatibility of your app with future versions of the [apply] platform. It is also often of questionable legality. [apply] [apply] If you really intend to build a core library -- which is only [apply] appropriate as part of creating a full virtual machine distribution, [apply] as opposed to compiling an application -- then use the [apply] "--core-library" option to suppress this error message. [apply] [apply] If you go ahead and use "--core-library" but are in fact building an [apply] application, then be forewarned that your application will still fail [apply] to build or run, at some point. Please be prepared for angry customers [apply] who find, for example, that your application ceases to function once [apply] they upgrade their operating system. You will be to blame for this [apply] problem. [apply] [apply] If you are legitimately using some code that happens to be in a core [apply] package, then the easiest safe alternative you have is to repackage [apply] that code. That is, move the classes in question into your own package [apply] namespace. This means that they will never be in conflict with core [apply] system classes. If you find that you cannot do this, then that is an [apply] indication that the path you are on will ultimately lead to pain, [apply] suffering, grief, and lamentation. [apply] [apply] 1 error; aborting BUILD FAILED/home/jessewilson/android-sdk-linux_86/platforms/android-2.0.1/templates/android_rules.xml:259: The following error occurred while executing this line: /home/jessewilson/android-sdk-linux_86/platforms/android-2.0.1/templates/android_rules.xml:123: apply returned: 1 Customize the ant build.xml
Before we can fix the above problem, we need to customize our build file. Android's default build.xml file imports build rules from a standard set. Since including a javax.* package isn't supported by the standard rules, we need to copy those rules into a place where we can modify them.
The standard rules are imported from
<SDK>/platforms/<target_platform>/templates/android_rules.xml
To customize some build steps for your project:
This ensures that the properties are setup correctly but that the customized build steps are used.
Repackage the code with jarjar
Jar Jar Links is an easy-to-use repackager that integrates nicely with Ant. Download jarjar-1.0.jar from the project site. Save the file in a new project subdirectory, buildtools.
Create a jarjar target in the build.xml file.
<!-- Converts this project's .class files into .dex files --> <target name="-jarjar" depends="compile"> <taskdef name="jarjar" classname="com.tonicsystems.jarjar.JarJarTask" classpath="buildtools/jarjar-1.0.jar"/> <jarjar jarfile="${out.absolute.dir}/repackagedclasses.jar"> <fileset dir="${out.classes.absolute.dir}" /> <zipgroupfileset dir="${external.libs.absolute.dir}" includes="*.jar" /> <rule pattern="javax.xml.**" result="com.mycompany.@1"/> </jarjar> </target>
At this point you'll need to edit the <rule /> tag to refer to the javax.* appropriate package prefix, and to the rename target. The jarjar Getting Started Guide explains the syntax.
This task builds a jar that contains all of the code, including repackaged library code and application code. We need turn those classes into a Dalvik executable. Edit the dex-helper macro definition to point at the repackaged code. We replace the tags:
<arg path="${out.classes.absolute.dir}" /> <fileset dir="${external.libs.absolute.dir}" includes="*.jar" />
with the repackaged classes:
<fileset file="${out.absolute.dir}/repackagedclasses.jar" />
The complete macro should look something like this:
<!-- Configurable macro, which allows to pass as parameters output directory, output dex filename and external libraries to dex (optional) --> <macrodef name="dex-helper"> <element name="external-libs" optional="yes" /> <element name="extra-parameters" optional="yes" /> <sequential> <echo>Converting compiled files and external libraries into ${intermediate.dex.file}... </echo> <apply executable="${dx}" failonerror="true" parallel="true"> <arg value="--dex" /> <arg value="--output=${intermediate.dex.file}" /> <extra-parameters /> <arg line="${verbose.option}" /> <fileset file="${out.absolute.dir}/repackagedclasses.jar" /> <external-libs /> </apply> </sequential> </macrodef>
Finally, change the -dex target to depend on our new -jarjar target instead of the compile target:
<target name="-dex" depends="-jarjar"> <dex-helper /> </target> Try it out
Use adb logcat to monitor exceptions as you launch the application. For many javax.* packages, these steps above will be sufficient.
Factory Registration
Some packages will fail to initialize due to factory registration problems. This is common in libraries that support pluggable implementations:
D/AndroidRuntime( 557): Shutting down VM W/dalvikvm( 557): threadid=3: thread exiting with uncaught exception (group=0x4001b188) E/AndroidRuntime( 557): Uncaught handler: thread main exiting due to uncaught exception E/AndroidRuntime( 557): com.mycompany.stream.FactoryConfigurationError: Provider com.bea.xml.stream.MXParserFactory not found E/AndroidRuntime( 557): at com.mycompany.stream.FactoryFinder.newInstance(FactoryFinder.java:72) E/AndroidRuntime( 557): at com.mycompany.stream.FactoryFinder.find(FactoryFinder.java:176) E/AndroidRuntime( 557): at com.mycompany.stream.FactoryFinder.find(FactoryFinder.java:92) E/AndroidRuntime( 557): at com.mycompany.stream.XMLInputFactory.newInstance(XMLInputFactory.java:136) E/AndroidRuntime( 557): at com.googlecode.dalvik.stax.HelloStax.onCreate(HelloStax.java:39) E/AndroidRuntime( 557): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047) E/AndroidRuntime( 557): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2444) E/AndroidRuntime( 557): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2497) E/AndroidRuntime( 557): at android.app.ActivityThread.access$2200(ActivityThread.java:119) E/AndroidRuntime( 557): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1848) E/AndroidRuntime( 557): at android.os.Handler.dispatchMessage(Handler.java:99) E/AndroidRuntime( 557): at android.os.Looper.loop(Looper.java:123) E/AndroidRuntime( 557): at android.app.ActivityThread.main(ActivityThread.java:4338) E/AndroidRuntime( 557): at java.lang.reflect.Method.invokeNative(Native Method) E/AndroidRuntime( 557): at java.lang.reflect.Method.invoke(Method.java:521) E/AndroidRuntime( 557): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:860) E/AndroidRuntime( 557): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618) E/AndroidRuntime( 557): at dalvik.system.NativeStart.main(Native Method)
The solution is to add system properties that tell the API where to find its implementation. The properties below configure Sun's implementation of StAX; you will need different properties for different APIs. These properties and their values are usually obtained from the META-INF/services/ directory inside your implementation's .jar file:
public class HelloStax extends Activity { @Override public void onCreate(Bundle savedInstanceState) { /* * Configure which implementation of StAX that will be used by our * application. These properties and their values are obtained from the * META-INF/services/ directory inside the implementation .jar file. */ System.setProperty("javax.xml.stream.XMLInputFactory", "com.sun.xml.stream.ZephyrParserFactory"); System.setProperty("javax.xml.stream.XMLOutputFactory", "com.sun.xml.stream.ZephyrWriterFactory"); System.setProperty("javax.xml.stream.XMLEventFactory", "com.sun.xml.stream.events.ZephyrEventFactory"); ... } } Factory Class Loaders
After setting system properties to specify factory implementations, factory loading may fail due to a class not found problem:
E/AndroidRuntime( 575): com.mycompany.stream.FactoryConfigurationError: Provider com.sun.xml.stream.ZephyrParserFactory not found E/AndroidRuntime( 575): at com.mycompany.stream.FactoryFinder.newInstance(FactoryFinder.java:72) E/AndroidRuntime( 575): at com.mycompany.stream.FactoryFinder.find(FactoryFinder.java:120) E/AndroidRuntime( 575): at com.mycompany.stream.FactoryFinder.find(FactoryFinder.java:92) E/AndroidRuntime( 575): at com.mycompany.stream.XMLInputFactory.newInstance(XMLInputFactory.java:136) E/AndroidRuntime( 575): at com.googlecode.dalvik.stax.HelloStax.onCreate(HelloStax.java:39) E/AndroidRuntime( 575): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047) E/AndroidRuntime( 575): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2444) E/AndroidRuntime( 575): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2497) E/AndroidRuntime( 575): at android.app.ActivityThread.access$2200(ActivityThread.java:119) E/AndroidRuntime( 575): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1848) E/AndroidRuntime( 575): at android.os.Handler.dispatchMessage(Handler.java:99) E/AndroidRuntime( 575): at android.os.Looper.loop(Looper.java:123) E/AndroidRuntime( 575): at android.app.ActivityThread.main(ActivityThread.java:4338) E/AndroidRuntime( 575): at java.lang.reflect.Method.invokeNative(Native Method) E/AndroidRuntime( 575): at java.lang.reflect.Method.invoke(Method.java:521) E/AndroidRuntime( 575): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:860) E/AndroidRuntime( 575): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618) E/AndroidRuntime( 575): at dalvik.system.NativeStart.main(Native Method)
The fix here is a cumbersome one. We need to manually set the class loader that will be used by the factory to the application's class loader.
public class HelloStax extends Activity { @Override public void onCreate(Bundle savedInstanceState) { ... /* * Ensure the factory implementation is loaded from the application * classpath (which contains the implementation classes), rather than the * system classpath (which doesn't). */ Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); ... } } Success
At this point, we have repackaged the javax.* package in order to include it in our application. The package will continue to work, even if future versions of Android include a conflicting version of the API.
|
This weblog only liked a combination of technical articles and writings have been using to keep. Many of the articles published in this blog do not belong to me, articles resources, am showing you care to publish the latter part of the articles.
Tuesday, October 11, 2011
Including additional javax.* packages in your Android App
Subscribe to:
Post Comments (Atom)
When I run this example I see this output in my logcat: VFY: unable to find class referenced in signature (Lcom/mycompany/transform/Source;
However it still manages to run and I see the xml output in my log cat.
If I try to let eclipse compile the example, I get a compile error: javax.xml.transform.Source cannot be resolved. It is indirectly referenced from required .class files
I was kind of ok with moving to an ant build for my application, but Eclipse really needs to be able to compile things in order for this to be a feasible strategy.