2015-04-14

Seifenkisten auf Abwegen

Der Zugriff auf (üblicherweise) leichtgewichtige REST-Webservices ist unter Android kein Problem – sein Java-Erbe verhilft dem kleinen grünen Roboter  zu einem ansehnlichen (Netz)Werk(zeug)-Koffer. Nur mit den (meist) etwas schwergewichtigeren SOAP-Webservices will es “out of the box” nicht so recht klappen. Zwar hat Java mit JAX-WS ab Java SE 6 entsprechende Klassenbibliotheken an Board, aber Androids Harmony-Spin-off ist irgendwie bei Java 1.5 stehen geblieben (naja, skurrile Erweiterungen und Portierungen mal außen vor gelassen). Glücklicherweise gibt es zu jedem Java Specification Request eine Referenzimplementierung. Bis einschließlich JAX-WS RI 2.2.6 ist “nur” Java SE 5 erforderlich. Warum also nicht einfach die Runtime-Bibliotheken mit einer Android-App ausliefern, und auf diese Weise auf SOAP-Services zugreifen können? Der Abschnitt Jar dependency der Release Notes listet auf, welche Bibliotheken der App hinzugefügt werden müssen. Eigentlich könnte man das Unterfangen an dieser Stelle abbrechen, denn die 18 Jars bringen üppige 4,8 MB auf die Waage. Wer möchte das schon als zusätzlichen Ballast mitgeben, “nur” um einen Webservice aufzurufen? Aber falls der Forscherdrang siegt, und man die Jars in das libs-Verzeichnis des Android-Projekts kopiert, wartet die nächste unangenehme Überraschung.

Screenshot: Fehlermeldung beim Hinzufügen eines Jars
Screenshot: Fehlermeldung beim Hinzufügen eines Jars

Google nennt den Versuch, Jars hinzuzufügen, die bestimmte Paket-Namensräume abdecken, “Ill-advised or mistaken usage of a core class” und unterbindet die Integration. Fairerweise muss man sagen, dass die Motivation durchaus legitim ist. Bestimmte Paketnamen sind eben der Standardklassenbibliothek vorbehalten. Schade nur, wenn sie dann nicht vorhanden sind. Glücklicherweise lässt Google dem Entwickler ein Hintertürchen offen, in Gestalt der Option --core-library, die man dem monierenden Tool (dex) übergeben kann. Wie, ist freilich kaum herauszubekommen. Ganz so einfach geht das nämlich nicht, wenn man mit Hilfe von Gradle baut. Hier ein Rezept, das zumindest bei mir funktioniert. In der Modul-spezifischen build.gradle-Datei ist folgendes Quelltextfragment einzufügen:

   dexOptions {  
     preDexLibraries = false  
   }  
   project.tasks.withType(com.android.build.gradle.tasks.Dex) {  
     additionalParameters=['--core-library']  
   }  

Jetzt klappt der Build zwar immer noch nicht, aber zumindest erhält man eine andere Fehlermeldung.

Screenshot: Fehlermeldung beim Build
Screenshot: Fehlermeldung beim Build

Auch dies bekommt man in den Griff, indem man build.gradle abermals erweitert:

   packagingOptions {  
     exclude "META-INF/LICENSE"  
   }  
   

Nun lässt sich das Projekt bauen und starten. Leider klappt der Zugriff auf den Webservice aber trotzdem nicht. In der Konsole findet man die Meldung, dass die Klasse java.awt.Image nicht gefunden wird.

Screenshot: Klasse java.awt.Image nicht gefunden
Screenshot: Klasse java.awt.Image nicht gefunden

Tja, schade. Wieder ein guter Zeitpunkt, das ganze sein zu lassen.

Zwinkerndes Smiley

Aber hey, warum nicht weiter forschen…? Dass es eigentlich nicht funktionieren kann, “einfach so” eine entsprechende Klasse zu bauen, ist logisch. Aber vielleicht lässt sich auf diese Weise ja herausfinden, was von java.awt.Image benötigt wird. Eine leere Implementierung lässt den Start-Vorgang dann auch etwas später abbrechen. Diesmal fehlt javax.activation.DataHandler. Diese Klasse gehört zum JavaBeans Activation Framework. Hierzu könnten Sie dessen activation.jar in das libs-Verzeichnis kopieren. Ich spare mir das und lege stattdessen wieder eine Dummy-Klasse an. Warum, sehen Sie gleich…

Screenshot: Fehler beim Initialisieren der Klasse DatatypeFactory
Screenshot: Fehler beim Initialisieren der Klasse DatatypeFactory

Diesmal schlägt das Initialisieren der Klasse DatatypeFactory fehl, weil eine andere (die Implementierungsklasse) nicht vorhanden ist:

Screenshot: Die Klasse org.apache.xerces.jaxp.datatype.DatatypeFactoryImpl ist nicht vorhanden
Screenshot: Die Klasse org.apache.xerces.jaxp.datatype.DatatypeFactoryImpl ist nicht vorhanden

Das ist sogar vergleichsweise einfach erklärbar, weil es so spezifiziert wurde. Die Doku der Klasse DatatypeFactory beschreibt den Initialisierungsvorgang, beim Aufruf der Methode newInstance(). Dort ist auch Folgendes zu lesen: “Note that you must supply your own implementation (such as Xerces); Android does not ship with a default implementation.” Die Idee ist nun, den Teil von Xerces hinzuzufügen, der die fehlende Klasse enthält. Es gibt sogar ein passendes Jar, xercesImpl.jar. Wiederstehen Sie aber der Versuchung, die Datei in das libs-Verzeichnis zu kopieren. Dann nämlich gibt es eine Fehlermeldung, die meiner Meinung nach mit dem zu alten Klassenformat der Binärdistribution zu tun hat. Der Bau von Xerces aus den Sourcen klappt auch nicht so ohne Weiteres, weil weitere Libs aus dem Apache-Fundus vonnöten sind.

Irgendwie frustrierend, oder? Es scheint einen vermeintlichen Rettungsanker zu geben, in Gestalt einer Android-Portierung von Xerces. Aber auch die hilft nicht, weil alle Paketnamen mit dem Präfix mf. versehen wurden. Deshalb findet DatatypeFactory die benötigte Implementierung DatatypeFactoryImpl nicht (sie befindet sich ja im Paket mf.org.apache.xerces.jaxp.datatype). Wenn Sie jetzt sagen “Dann nutze ich eben die Übergabe zum Beispiel mit System.setProperty()”, muss ich Sie wieder enttäuschen. Dann gibt es zur Laufzeit eine Exception, weil die Implementierung nicht von der originalen DatatypeFactory ableitet, sondern der aus einem Paket, das mit mf. beginnt.

Wie geht es jetzt weiter? Nun, ich habe als letzten Versuch die wenigen benötigten Klassen aus der Xerces-Source-Distribution in das Projekt kopiert. Damit wird tatsächlich alles gefunden. Glauben Sie, es funktioniert?

Screenshot: Noch eine Exception
Screenshot: Noch eine Exception

Man könnte natürlich noch analysieren, was es mit den IllegalAnnotationExceptions auf sich hat, aber darauf hatte ich dann doch keine Lust mehr. JAX-WS und Android mögen sich also offensichtlich nicht besonders gerne. Wie man aber doch noch auf SOAP-Services zugreifen kann, lesen Sie in einer der kommenden KaffeeKlatsch-Ausgaben.

No comments:

Post a Comment