Het ontwikkelen van mobile apps is helemaal in. Via de appstores van Apple en Google kun je de wereld aan apps vinden voor zo ongeveer alles wat je maar wilt hebben. Ik kan mij voorstellen dat je zelf een app wilt ontwikkelen om deze vervolgens in de appstore te publiceren. Je kan dat doen met de standaard ontwikkeltools van Google of Apple, maar als je .NET programmeert in het dagelijks leven dan is er ook een andere manier om apps te ontwikkelen.
In dit artikel laat ik je zien hoe je met Mono for Android een Android app kan ontwikkelen waarmee je een foto kan maken en weergeven op het scherm.
Wat is het Mono for Android framework?
Het Mono for Android framework is een implementatie van Mono op het Android platform. Novell heeft ervoor gezorgd dat je als .NET ontwikkelaar met je bestaande .NET kennis een app kan ontwikkelen op het Android platform. Daarbij kun je gebruik maken van alle bekende programmeerinterfaces zoals LINQ, WCF, Threading, System.Xml en andere veelgebruikte onderdelen van het .NET framework.
Figuur 1: Architectuur Mono for Android
In figuur 1 is te zien hoe de architectuur van het Mono for Android framework er uit ziet.
Om het Mono framework te laten communiceren met het Android platform heeft Novell een aantal specifieke architectuur keuzes gemaakt. Zo is het Mono framework niet boven op de Dalvik runtime gebouwd. De Dalvik runtime is de java virtuele machine waarop alle apps binnen Android worden uitgevoerd. In plaats van Mono bovenop de Dalvik engine te draaien is er een apart systeem ontwikkeld dat rechtstreeks op de Linux kernel van het Android OS draait. Dit maakt het mogelijk om de reguliere Mono JIT compiler te gebruiken samen met de eigen garbage collector.
Apps op Android kunnen worden gestart vanuit de launcher of door op de widget te drukken op het bureaublad van je telefoon. Zowel de launcher als de widgets kunnen alleen worden geimplementeerd door het schrijven van Java code. Om toch Mono for Android apps te kunnen starten wordt er voor elke .NET class, die aangeroepen moet kunnen worden vanuit Android, een Android Callable Wrapper (ACW) gegenereerd door de Mono for Android compiler. Een ACW kan het beste worden gezien als de stunt-double van een .NET class.
Er is door Novell gekozen om met Mono for Android zo dicht mogelijk de native omgeving te benaderen op het Android platform. Net als reguliere Android apps bestaat daarom een Mono for Android app uit een serie activities waarvan er een is geregistreerd in de eerder genoemde launcher. Een Activity is echter een Java class. Om deze class te kunnen gebruiken is het noodzakelijk om hiervoor een zogenaamde Mono Callable Wrapper (MCW) te gebruiken. Dit is net als de ACW een stunt-double, maar dan voor een Java class.
ACW componenten en MCW componenten zijn componenten die intern communiceren via de JNI (Java Native Interface). Java en Mono praten nooit rechtstreeks tegen elkaar, maar altijd via de Mono runtime die op de Linux kernel draait. Deze vertaalt daarbij allerhande zaken op een zodanige manier dat als er wordt verwezen naar een Java object er aan de Mono kant een .NET object bestaat en andersom.
Het Mono for Android framework draait voor een groot deel om vertaalslagen tussen Android en Mono. Novell heeft er voor gezorgd dat ontwikkelaars hier geen grote hinder van ondervinden. De gehele Android API is voorzien van een .NET saus zodat je geen getProperty() en setProperty() methodes meer ziet, al dit soort constructies zijn omgezet in .NET native constructies.
Je eerste app ontwikkelen
Voordat je kan beginnen met het ontwikkelen Mono for Android moet je de nodige voorbereidingen treffen. Voor het kunnen compileren voor Android heb je de Android SDK nodig. Deze kan je downloaden vanaf de Android developer website. Nadat je hem hebt gedownload moet je de SDK manager van de Android SDK eenmalig starten om de rest van de SDK te installeren op je computer.
Naast de Android SDK heb je ook de Mono for Android componenten nodig. Deze kun je vinden op de website van Mono for Android (http://mono-android.net/).
Als je na de installatie Visual Studio 2010 opstart en een nieuw project gaat aanmaken zal je een nieuwe categorie project templates aantreffen in het dialoogvenster.
Figuur 2: Mono for Android projecttypes
Mono for Android ondersteunt een drie tal verschillende soorten projecten. Je kan een Mono for Android Application maken, dit is een reguliere Android app. Daarnaast kan je een OpenGL app maken, hiermee kun je een game ontwikkelen voor Android. Tot slot kun je een Mono for Android library ontwikkelen, je kan dit vergelijken met een JAR library voor Android.
Voor het voorbeeld heb ik gebruik gemaakt van de Mono for Android application template. Als je op basis hiervan een project aanmaakt krijg je een structuur waarbij een Activity class is aangemaakt in het project en er een setje mappen is neergezet met wat standaard resource files. In figuur 3 is de structuur weergegeven zoals je hem in Visual Studio 2010 ziet.
Figuur 3: Projectstructuur
Het Android besturingssysteem werkt met zogenaamde activities om delen van apps aan te duiden. Je kan een activity vergelijken met een enkel scherm in een Windows Forms applicatie. Activities zijn echter wel meer geïsoleerd van elkaar dan dat je gewend zult zijn vanuit Windows Forms. Je kan activities namelijk alleen opstarten doormiddel van intents. Intents zijn berichten die je kan versturen richting het Android OS. Deze berichten bevatten een soort adres, namelijk de actie. Daarnaast kun je zogenaamde extra’s meesturen, dit is een dictionary met key-value pairs waarin je extra gegevens kan doorgeven aan de ontvanger van de intent. Intents worden gebroadcast en de eerste app die reageert wint en mag de intent afhandelen. Alles en iedereen mag zich inschrijven voor intents. Je kan bijvoorbeeld je inschrijven op een intent voor het adresboek. Je vervangt dan het normale adresboek van Android met je eigen implementatie.
Het voorbeeld dat ik ga gebruiken is een app waarmee je een foto kan nemen en deze vervolgens kan e-mailen naar iemand in je adresboek. De eerste stap om richting het kunnen nemen van een foto is het implementeren van de root activity van de app. Er is minimaal één activity in je app die gekoppeld dient te zijn aan de Android launcher om hem te kunnen starten.
[Activity(Label = "PhotoMailer", MainLauncher = true, Icon = "@drawable/icon")]
public class MainActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
Button buttonTakePicture =
FindViewById<Button>(Resource.Id.select_picture);
buttonTakePicture.Click += OnTakePictureClicked;
}
private void OnTakePictureClicked(object sender, EventArgs e)
{
// Take the picture
}
}
|
Codevoorbeeld 1: FotoMailer activity
In codevoorbeeld 1 is te zien hoe de MainActivity van de app eruit ziet. Boven de class staat een attribuut om aan te geven dat het een activity is en dat hij in de launcher moet worden weergegeven. Dit attribuut wordt door de Mono for Android compiler gebruikt om de juiste informatie in het app manifest te zetten. Dit manifest beschrijft hoe je app eruit ziet en welke rechten hij nodig heeft op het Android OS.
In de OnCreate methode kun je de benodigde configuratie doen van je activity, zoals het inladen van een layout file voor de user interface. Dit doe je door de methode SetContentView(…) aan te roepen. Aan deze methode geef je het ID van de layout resource mee die je wilt inladen. Deze ID’s worden automatisch voor je gegenereerd op het moment dat je het project een keer compileert. De layout resource ID’s vind je na het compileren terug in Resource.Layout.XX properties.
Je kan in Android op twee manieren met user interface omgaan. Je kan in code de user interface opbouwen of een layout file inladen. Het laatste is een stuk praktischer dan de user interface in code opbouwen. Je kan namelijk voor allerlei uitgangssituaties layout files produceren. Zo kun je bijvoorbeeld een layout file maken voor portrait en landscape. Je plaatst dan de ene file in Resources\layout-portrait en de andere in Resources\layout-landscape. Zolang je ze dezelfde naam geeft zal Android zelf kunnen uitzoeken welke hij moet weergeven.
De layout van de voorbeeld app bestaat uit twee knoppen en een imageview. De imageview kan je gebruiken om een afbeelding weer te geven op het scherm. De layout is in codevoorbeeld 2 weergegeven.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button
android:id="@+id/select_picture"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/select_picture_text"
/>
<ImageView android:id="@+id/selected_picture"
android:layout_height="480dip"
android:layout_width="640dip"/>
</LinearLayout>
|
Codevoorbeeld 2: Layout file
Zowel de button als de imageview zijn voorzien van een android:id attribuut, met een @+id constructie. Door deze constructie te gebruiken maak je duidelijk aan de resource generator dat je graag een nieuw ID wilt hebben. Je kan ook gebruik maken van bestaande ID’s uit de Android omgeving. Dit gebruik je voor constructies waarbij je een bestaande user interface wilt aanpassen specifiek voor jouw app. In het codevoorbeeld zie je op een aantal punten een vermelden met @string staan. In Android kun je gebruik maken van zogenaamde string files waarin je de teksten die je wilt gebruiken in de user interface kan bewaren. Op deze manier kun je de user interface vertalen in meerdere talen. Om gebruik te maken van deze teksten geef je ze in de string file een naam en refereer je naar deze naam doormiddel van de @string/naam constructie in je layout files.
Een foto nemen
Nu de basis van de applicatie staat, is de volgende stap het toevoegen van functionaliteit om een foto te nemen. Het nemen van een foto op Android doe je door een intent op te starten en het resultaat ervan weer op te vangen in je activity. Zoals je eerder hebt kunnen lezen worden intents gebruikt om te communiceren met het OS. Je hebt dit in het geval van een foto maken nodig, omdat je niet weet welke app de gebruiker van de telefoon heeft geïnstalleerd voor het maken van foto’s.
Voor het nemen van een foto heb je de intent “android.media.action.IMAGE_CAPTURE” nodig. Aan deze intent kun je een output file mee geven, maar dat hoeft niet. Geef je het niet mee dan is het de bedoeling dat de activity die de intent implementeert, zelf een naam verzint.
private const int TakePictureRequestCode = 1001;
private void OnTakePictureClicked(object sender, EventArgs e)
{
// Maak een intent aan voor de camera
Intent intent = new Intent(MediaStore.ActionImageCapture);
StartActivityForResult(intent, TakePictureRequestCode);
}
|
Codevoorbeeld 3: Een foto nemen
In codevoorbeeld 3 is te zien hoe je de camera kan starten. Je ziet de naam van de intent niet meer terug in de code, omdat Novell hiervoor een constante heeft gegenereerd in de Mono for Android library. Je vind de intent voor het nemen van een foto en andere media gerelateerde intents terug op de MediaStore class.
Om het resultaat van de camera op te kunnen vangen start je de intent op met de methode StartActivityForResult. Bij het starten kun je een requestcode opgeven. In het volgende codevoorbeeld zal je zien hoe je hier handig gebruik van kunt maken.
De app kan met de code in codevoorbeeld 3 prima foto´s maken alleen krijg je ze nooit meer terug. Om de foto namelijk terug te halen moet je de methode OnActivityResult overriden in je activity.
protected override void OnActivityResult(
int requestCode,
Result resultCode,
Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (requestCode == TakePictureRequestCode &&
resultCode == Result.Ok)
{
var dataUri = data.ToURI();
var imageView =
FindViewById<ImageView>(
Resource.Id.selected_picture);
imageView.SetImageURI(Android.Net.Uri.Parse(dataUri));
}
}
|
Codevoorbeeld 4: Resultaat van de camera opvangen
In de OnActivityResult methode krijg je een intent terug met daaraan de data van de activiteit. Daarnaast krijg je een requestcode en een resultaatcode terug. De requestcode die je hier terugkrijgt komt overeen met de requestcode die je bij het starten van de intent hebt opgegeven. Als je in deze methode terechtkomt is het belangrijk om te controleren of de activiteit gelukt is. Dit doe je door te controleren de resultCode overeenkomt met de waarde Result.OK. Daarnaast moet je controleren voor welke intent je data terug hebt gekregen. Als je namelijk meerdere intents tegelijk opstart in je app kan het zijn dat het resultaat van de ene intent eerder terug is dan van de andere.
Als je een foto hebt genomen met de camera intent krijg je een URI terug van de afbeelding die is genomen door de camera. Deze URI kun je vervolgens koppelen aan een imageview om de foto weer te geven.
Hinken op twee gedachten
In de voorbeeld app merk je niet veel van het feit dat Android op Java is gebaseerd. Maar hoewel je .NET programmeert met Mono for Android zul je het Java aspect toch nog overal terug vinden in de apps die je ontwikkelt.
Onderdelen van de app die gebruik maken van de Android voorzieningen hebben dikwijls classes nodig die zijn afgeleid van Java.Lang.Object. Je zult merken dat het gehele Mono for Android framework eigenlijk een beetje op twee gedachten hinkt. De ene keer heb je te maken met System.Object en de andere keer heb je te maken met Java.Lang.Object. In de praktijk levert dit niet heel vaak problemen op. Het punt waarop je er wel mee te maken krijgt is bijvoorbeeeld op het moment dat je een web service reference aan je app hebt toegevoegd en je objecten daaruit wilt weergeven op de user interface. Om objecten in een lijst op het scherm te tonen maak je gebruik van een zogenaamde ListAdapter. Een ListAdapter vertaalt elk object in zijn data source naar een view op het scherm. De ListAdapter gaat uit van Java.Lang.Object gebaseerde objecten in zijn data source. Deze heb je echter niet als je objecten ophaalt van een webservice die je via een service reference hebt gekoppeld aan je app.
Een oplossing voor dit scenario en vergelijkbare scenario’s is gelukkig eenvoudig te implementeren. In codevoorbeeld 5 is een wrapper geschreven die het mogelijk maakt om objecten die niet zijn afgeleid van Java.Lang.Object toch als zodanig te behandelen.
public class JavaObjectWrapper<T> : Java.Lang.Object
{
private T _value;
public JavaObjectWrapper(T value)
{
_value = value;
}
public T Value
{
get { return _value; }
}
public static implicit operator T(
JavaObjectWrapper<T> input)
{
return input.Value;
}
}
|
Codevoorbeeld 5: Java object wrapper
Wanneer je zelf een ListAdapter gaat implementeren kun je handig gebruik maken van de wrapper uit codevoorbeeld 5 op het moment dat je een bepaald item uit de gekoppelde lijst moet teruggeven aan het Android OS. Een voorbeeld hiervan zie je in code voorbeeld 6.
public class MyListAdapter<T>: BaseAdapter
{
private List<T> _items;
public override Object GetItem(int position)
{
return new JavaObjectWrapper<T>(
_items.ElementAt(position));
}
// ...
}
|
Codevoorbeeld 6: Java object wrapper in combinatie met een list adapter
De toegevoegde waarde van Mono for Android
De standaard omgeving voor Android is eclipse met de Android SDK. Het Java platform is op zichzelf een prima keuze om Android mee te ontwikkelen, dus waarom zou je wat anders willen? De reden om wat anders te willen doen zit hem niet zozeer in de extra mogelijkheden die Mono for Android biedt. Dit zijn er namelijk maar een beperkt aantal. De echte kracht van Mono for Android zit hem in de mogelijkheid om de C# code, die je hebt geschreven voor je Android app, te kunnen hergebruiken in bijvoorbeeld een Windows Phone 7 app of een MonoTouch app.
Om de hoeveelheid code die je kan hergebruiken zo groot mogelijk te maken is het wel zaak om de logica van je app zo in te delen dat je de business logica gescheiden houdt van de logica om de user interface aan te sturen. Op deze manier heb je een groot geheel dat je kan hergebruiken. Het user interface gedeelte kun je vervolgens helemaal toespitsen op Android en er het maximale uit halen qua mogelijkheden die Android biedt.
Als je alleen .NET programmeert is Mono for Android ook een prima keuze. Je hoeft namelijk op dat moment alleen de Android specifieke zaken te leren. Je hoeft geen nieuwe programmeertaal te leren en je hoeft ook niet te leren omgaan met de standaard Java classes. Je kan in plaats daarvan gebruik blijven maken van de bestaande kennis die je in de loop van de tijd hebt opgedaan op het .NET platform.
Conclusie
Zoals je hebt kunnen zien voelt het ontwikkelen met C# op Android heel natuurlijk aan als je .NET gewend bent. Er zijn en blijven altijd kleine details, die net even anders werken dan je gewend bent, maar over het algemeen is het een solide combinatie. Het wordt zelfs nog krachtiger als je slim nadenkt over de opzet van je app en delen ervan apart zet, zodat je deze code ook kan hergebruiken voor Windows Phone 7 en MonoTouch.
De smartphone markt is dusdanig gefragmenteerd dat als je apps wilt ontwikkelen, voor de diverse populaire platforms, je enorme investeringen moet doen in kennis en tools. Door te investeren in het cross-platform ontwikkelen van apps doormiddel van C# zul je merken dat je beter in staat bent om apps te produceren voor zowel Android, iPhone als Windows Phone 7. Daarnaast zal je merken dat je apps vooral goedkoper kunt produceren.
Links