Android besitzt ein sehr grosses Eco-System an Third-Party Bibliotheken. Es kann öfters durchaus Sinn machen eine bestehende Java Bibliothek in ein Xamarin.Android Projekt einzubinden anstatt selber eine entsprechende Implementation in C# zu schreiben.
Dabei gibt es zwei mögliche Optionen:
- Erstellen einer Binding Bibliothek. Dies ist im Wesentlichen eine Wrapper um die Java Bibliothek herum. Die Bindings werden mit Hilfe von so genannter Managed Callable Wrappers (MCW) implementiert.
- Verwendung des Java Native Interface (JNI) um direkt Java Code aufzurufen.
In folgendem Blog Beitrag wird auf die erste Option eingegangen und anhand eines Beispielprojektes gezeigt, auf was für Probleme man dabei stossen kann und wie diese gelöst werden können.
Das komplette Beispielprojekt kann hier gefunden werden und beinhaltet die Xamarin Solution sowie den Java Source Code der Beispielbibliothek.
Vorgehen
- Erstellen der Binding Library
Xamarin bieten eine Projektvorlage für das Erstellen einer Binding Bibliothek. So kann einfach zu einer bestehenden Xamarin.Android Solution ein neues Binding Projekt hinzugefügt werden.Im Template enthalten ist unter anderem die Datei Metadata.xml. Mit Hilfe von dieser kann in den Generierungsprozess der Bindings eingegriffen werden. Ausführliche Informationen dazu sind auf der Xamarin Website zu finden. - Java Bibliothek zum Binding Projekt hinzufügen
Um die Java Bibliothek zum Binding Projekt hinzuzufügen, zieht man die Jar Datei in den Jars Ordner des Projektes. Anschliessend muss noch die richtige Build Action für die Jar Datei ausgewählt werden. In Unserem Fall ist dies EmbeddedJar. Eine genaue beschreibung der verschiendenen Build Actions kann hier gefunden werden. - Binding Projekt kompilieren
Nun kann das Projekt kompiliert werden. Doch das ist leichter gesagt als getan. Versucht man dies nämlich mit der oben verwiesenen Jar Datei, so stellt man fest, dass mehrere Fehler auftreten:
CS0234 The type of namespace name ‘TipCalculationServiceBase’ does not exit
Wenn man sich diesen Fehler genauer anschaut, so sieht man, dass eine Klasse TipCalculationServiceImpl generiert worden ist, welche von TipCalculationServiceBase ableitet. Allerdings wurde gar keine TipCalculationServiceBase Klasse generiert. Um die Ursache des Problems zu finden, muss man sich den Java Code genauer ansehen. Dort ist zu erkennen, dass beide Klassen ebenfalls vorhanden sind. Die TipCalculationServiceBase Klasse ist allerdings nicht public. Während es in Java erlaubt ist, dass eine public Klasse von einer anderen ableitet, die nicht public ist, so ist das in C# nicht möglich.
Um das Problem zu lösen hat man nun zwei Möglichkeiten:
- Man entfernt die TipCalculationServiceImpl Klasse mit folgendem Eintrag in der metadata.xml Datei:
<remove-node path="/api/package[@name='crehmann.samples.monodroidjarexample']/class[@name='TipCalculationServiceImpl']"/>
Dies ist in unserem Fall jedoch keine Option, weil wir die TipCalculationServiceImpl verwenden wollen und somit unsere Binding Library einen solchen Wrapper besitzen muss.
- Man macht die TipCalculationServiceBase mit Hilfe der metdata.xml Datei public:
<attr path="/api/package[@name='crehmann.samples.monodroidjarexample']/class[@name='TipCalculationServiceBase']" name="visibility">public</attr>
Nun wird zwar ebenfalls ein Binding für die TipCalculationServiceBase Klasse erstellt, allerdings sollte man im C# Code diese nicht direkt verwenden.
Versucht man das Projekt erneut zu kompilieren, so stellt man fest, dass nun zwar alle vorgängigen Fehler behoben wurden. Allerdings ist ein neuer erschienen:
Dieser Fehler tritt auf, wenn z.B. ein Binding für eine Java Methode generiert wird, dass einen kovarianten Rückgabe Wert hat. So definiert das Java Interface TipCalculationService für die Methode CalculateTip einen Rückgabewert vom Typ Object, während bei TipCalculationServiceBase der Rückgabewert vom Typ TipCalculationResult ist.
Nun hat man auch wieder zwei Möglichkeiten um das Problem zu lösen:
- Man ändert den Typ des Rückgabewertes für den generierten C# Code indem folgenden Eintrag in der Metadata.xml definiert_
<attr path="/api/package[@name='crehmann.samples.monodroidjarexample']/class[@name='TipCalculationServiceBase']/method[@name='calculateTip' and count(parameter)=1 and parameter[1][@type='crehmann.samples.monodroidjarexample.TipRequest']]" name="managedReturn">Java.Lang.Object</attr>
- Man fügt zum Projekt eine Partial Class Deklaration hinzu, welche das Interface entsprechend implementiert:
using Java.Lang; namespace Crehmann.Samples.Monodroidjarexample { public abstract partial class TipCalculationServiceBase { Object ITipCalculationService.CalculateTip(TipRequest p0) { return CalculateTip(p0); } } }
Nun lässt sich das Projekt ohne Fehler kompilieren und kann innerhalb des Xamarin.Android Projektes referenziert und verwendet werden.
Fazit
Mit Xamarin ist es relativ leicht möglich bestehende Android Java Bibliotheken zu verwenden. Mit dem Binding Projekt Template kann dabei auf einfache Weise eine Wrapper Bibliothek generiert werden. Dabei hat man viele Möglichkeiten um in die Generierung der Wrapper Klassen einzugreifen und diese zu modifizieren um dadurch allfällige Probleme zu beheben.
Pingback: Noser Blog Cross Plattform Native Apps mit Xamarin - Noser Blog