Android-Besonderheiten für Entwicklungen mit Xamarin - Teil 2

Android mit Xamarin: Die Hostplattform und ihre Gefahren kennenlernen
Kommentare

Einer der gefährlichsten Irrglauben im Bereich Cross-Plattform-Entwicklung ist, dass man auch ohne Verständnis der Hostplattform erfolgreiche Applikationen erzeugen kann. Da Android spätestens dank der Übernahme von Xamarin durch Microsoft auch in den Fokus der .NET-Entwickler rückt, wollen wir nun einen Blick auf das Betriebssystem als Ganzes werfen. Schon aus Gründen der besseren Weiterverwendbarkeit unserer Ergebnisse wollen wir als Arbeits- und Forschungsumgebung Xamarin verwenden. In diesem Teil der Artikelserie lernen wir mehr über die Sicherheitsvorkehrungen und Gefahren in Xamarin.

Diese Artikelserie wird Schritt für Schritt erklären, wie man GUIs für Android mit Xamarin Studio erstellt. Im ersten Teil widmen wir uns zunächst der Aktualisierung von Visual Studio, dem Start der Erstellung von GUIs mit Xamarin Studio und der Fehlersuche. Im zweiten Teil geht es um Fragmentierung, Lokalisierung und Testing in Xamarin Studio. Danach beschäftigten wir uns mit der gesamten Hostplattform und lernten in Teil 3 die Android-Besonderheiten bei der Entwicklung mit Xamarin näher kennen, bevor wir uns jetzt in Teil 4 mit den Sicherheitsvorkehrungen und Gefahren befassen.

Achtung, Falle!

Wer die beiden Beispiele in der vorliegenden Form ausführt, macht mit einer Exception Bekanntschaft – das Betriebssystem kann die betreffende Klasse nicht finden. Die weiter oben erwähnte Empfehlung, den Inhalt des Manifests zu prüfen, bringt Folgendes zu Tage:

<activity android:exported="true" android:label="ZweiteActivity" android:name="md54ad7a0700598417e8970a2871168311f.ZweiteActivity" />

Laut der unter einsehbaren Dokumentation generiert Xamarin für Android seit Version 5.1 den Namen der Assembly über einen vom Inhalt abhängigen Hashwert, um Paketkonflikte zu verhindern – für unsere Aufrufidee alles andere als sinnvoll.

Erfreulicherweise gibt es auch hierfür eine Lösung – der Wert des Name-Attributs wandert 1:1 in die Manifestdatei:

namespace SUSAndroidTest1
{
  [Activity(Label = "ZweiteActivity", Exported = true, Name = "susandroidtest1.ZweiteActivity")]
  public class ZweiteActivity : Activity

An dieser Stelle findet sich Potenzial für einen zünftigen Editor-War: Wer einen „beliebigen“ String angibt, wird von Visual Studio mit einer Compilerwarnung ausgebremst. Leider ist auch nicht alles, was Visual Studio akzeptiert, unter Android legal: Lässt sich das Programm plötzlich nicht mehr debuggen, so steht ein aus Sicht von Android ungültiger Wert im Manifest. In Tests des Autors erwies sich die Nutzung von lower(name).Klassenname als für beide Seiten akzeptabel – im Runner ist nun noch eine kleine Codeänderung notwendig:

button.Click += delegate {
        Intent intent = new Intent();
        intent.SetComponent(new ComponentName("SUSAndroidTest1.SUSAndroidTest1", "susandroidtest1.ZweiteActivity"));

Der momentane Aufbau ist insofern attraktiv, als er das Öffnen der betreffenden Activity mit vergleichsweise geringem Aufwand erlaubt. Wer sein Programm in die Share- oder sonstigen Subsysteme von Android einbinden möchte, nutzt stattdessen die IntentFilter-Klasse. Wir wollen auch dies kurz vorführen, in dem wir unsere Applikation zum Entgegennehmen von Text per SendKommando befähigen:

namespace SUSAndroidTest1
{
  [Activity(Label = "ZweiteActivity", Exported = true, Name = "susandroidtest1.ZweiteActivity")]
  [IntentFilter(new[] { Intent.ActionSend }, Categories = new[] {
  Intent.CategoryDefault,
  Intent.CategoryBrowsable
}, DataMimeType = "text/plain")]
public class ZweiteActivity : Activity

Wie bei der Erzeugung eines absendbaren Intents sind auch hier mehrere Parameter notwendig: Neben der Art der zu erledigenden Aufgabe (hier das Versenden) muss auch die Kategorie und der Mime-Typ der entgegenzunehmenden Daten angegeben werden. Zur Laufzeit führt dies zur in Abbildung 3 gezeigten Integration mit Chrome.

Abb. 3: ZweiteActivity lässt sich von Chrome mit Text versorgen

Abb. 3: ZweiteActivity lässt sich von Chrome mit Text versorgen

Aus Platzgründen – es gibt über Android zu viel zu erzählen – wollen wir auf die Auswertung der angelieferten Informationen hier nicht weiter eingehen: Das Intent-API erlaubt das Entgegennehmen von im für den Aufruf der Activity zuständigen Intent befindlichen Informationen.

Warnung an alle

Unter Android gibt es mit dem BroadcastReceiver ein ausgefeiltes Notification-System: Das Betriebssystem emittiert während der Programmausführung eine Vielzahl von Ereignissen, die über Instanzen der gleichnamigen Klasse entgegengenommen werden können.

Wir wollen dies zur Realisierung eines kleinen Counters nutzen, der jeden Systemneustart mitprotokolliert. Im ersten Schritt ist dazu eine Klasse erforderlich, die den BroadcastReceiver realisiert. Der Inhalt der Datei MyReceiver.cs sieht so aus:

[BroadcastReceiver(Enabled = true)]
[IntentFilter(new[] { Android.Content.Intent.ActionBootCompleted })]
public class MyReceiver : BroadcastReceiver
{
  public override void OnReceive(Context context, Intent intent)
  {
    throw new NotImplementedException();
  }
}

Das Attribut BroadcastReceiver weist den Manifestgenerator dazu an, die Klasse beim Betriebssystem anzumelden. Der darunter befindliche IntentFilter legt fest, welche Ereignisse an die Klasse weiterzureichen sind. Zum Entgegennehmen des BootCompleted-Ereignisses ist eine Permission erforderlich. Sie lässt sich durch Doppeltanklicken von Properties einpflegen: Wechseln Sie in die Rubrik Android Manifest, und setzen Sie ein Häkchen neben: RECEIVE_BOOT_COMPLETED.

Als eigentliche Logik bietet sich das Erzeugen einer Präferenzdatei an, die im privaten Speicherbereich der Applikation – in der Handcomputerindustrie hat sich dafür der Begriff Jail eingebürgert – zu liegen kommt:

public override void OnReceive(Context context, Intent intent)
{
  var prefs = context.GetSharedPreferences("MySettings.txt", FileCreationMode.Private);
  int bootcount = 0;
  bootcount=prefs.GetInt("reboots",0);
  var editor = prefs.Edit();
  bootcount++;
  editor.PutInt("reboots", bootcount);
  editor.Commit();
}

SharedPreferences implementieren eine Art KV-Speicher, dessen Verwaltung komplett in der Hand des Betriebssystems liegt. Aus diesem Grund ist die Routine vergleichsweise einfach – zum Schreiben der Daten ist ein Editorobjekt erforderlich, das wir on the fly erzeugen. Jede Lesemethode nimmt einen Default-Wert entgegen, der beim Nichtvorhandensein des Eintrags zurückgegeben wird. Zur Überprüfung des Erfolgs erweitern wir den Konstruktor der MainActivity um eine Routine, die die im Bootfile befindlichen Informationen ausliest und auf den Bildschirm ausgibt:

Button button = FindViewById<button>(Resource.Id.MyButton);

. . .

var prefs = this.GetSharedPreferences("MySettings.txt", FileCreationMode.Private);
int bootcount = 0;
bootcount = prefs.GetInt("reboots", 0);
button.Text = Convert.ToString(bootcount);

An dieser Stelle ist eigentlich nur die Beschaffung des Verweises auf den Button interessant: Zeiger auf Steuerelementinstanzen erzeugt man unter Android prinzipiell unter Nutzung des ID-Attributs.
Damit ist auch diese Version des Programms einsatzbereit.

Xamarin im Videotutorial

entwickler.tutorialsIn unserem entwickler.tutorial Einstieg in Cross-Plattform-App-Development mit Xamarin zeigt Jörg Neumann, wie Xamarin funktioniert und wie Sie das Maximum aus der Plattform herausholen können. Wer sich jetzt anmeldet, sichert sich das 3-in-1 Gratistutorial – und kann das erste Kapitel des Tutorials kostenlos sehen!

Services sind immer da

Das Android-Activity-System ist an einem der häufigsten „Ärgernisse“ des Android-Betriebssystems schuld. Wer eine App in den Hintergrund verschiebt, muss damit rechnen, dass ihr Inhalt nach dem nächsten Neustart nicht mehr zur Verfügung steht. Das liegt daran, dass Android in Zeiten von Speichermangel im Hintergrund befindliche Activities nach Belieben löscht.

Ein Service unterscheidet sich von einer Activity insofern, als er „immer da ist“. Der einfachste Service wird im Rahmen eines Ereignisses gestartet und läuft dann vor sich hin. Als Beispiel dafür wollen wir einen kleinen Dienst realisieren, der eine eventuell angeschlossene Workstation regelmäßig per Logcat mit Daten befüllt (Listing 2).

[Service]
public class CatService : Service
{
  [return: GeneratedEnum]
  public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
  {
    Thread t = new Thread(() => {
        while (1 == 1)
        {
          Thread.Sleep(5000);
          Log.Debug("SuS", "Ich lebe noch!");
        }
    });
    t.Start();
    return StartCommandResult.Sticky;
  }

Services laufen von Haus aus im Hauptthread: Dies ist unter Android nicht empfehlenswert, weil er auch für die Abarbeitung des GUI zuständig ist. Wir lösen dieses Problem durch das Abfeuern eines weiteren Threads, der sodann im Hintergrund weiterläuft. Die Aktivierung lässt sich dann per StartService bewerkstelligen:

button.Click += delegate {
StartService(new Intent(this, typeof(CatService)));

Kleine Werkzeugkunde

An dieser Stelle ist es an der Zeit, sich ein wenig mit dem zur Android-Entwicklung verwendeten Werkzeug zu beschäftigen. Am mit Abstand am Wichtigsten ist ein als ADB bezeichnetes Produkt – hinter der Abkürzung verbirgt sich die Bezeichnung Android Debug Bridge.

Ein Beispiel für die Vielzahl der Möglichkeiten von ADB zeigt folgender Befehl, der alle mit der Workstation verbundenen Telefone und Emulatoren auflisten kann. Wer seine ersten Erfahrungen mit Android unter Unix gemacht hat, muss an dieser Stelle umdenken – während Ubuntu und Co. im Entwicklermodus befindliche Smartphones automatisch erkennen, verweigert Windows mitunter die Zusammenarbeit nach folgendem Schema:

C:\Program Files (x86)\Android\android-sdk\platform-tools>adb devices
List of devices attached
C:\Program Files (x86)\Android\android-sdk\platform-tools>>

Jeder Hersteller spielt im Bereich Gerätetreiber seine eigene Rolle: Als Erstes sollten Sie den USB-Treiber von Google gemäß den hier bereitstehenden Anleitungen installieren. Bringt das kein Ergebnis, probieren Sie die unter bereitstehende Liste. Falls auch dies nicht funktioniert, finden Sie in der hier bereitstehenden Diskussion weitere Informationen.

Funktionierende Konfigurationen nennen ein oder mehrere Geräte – auf der mit einem Xiaomi Redmi 3 verbundenen Workstation des Autors sieht das so aus:

C:\Program Files (x86)\Android\android-sdk\platform-tools>adb devices
List of devices attached
UCHQXXX411199999 device

Ein weiteres interessantes Werkzeug ist das im vorigen Abschnitt erwähnte Logcat. Laufende Applikationen, Treiber und andere Nettigkeiten feuern während ihres gesamten Lebenszyklus Meldungen ab, die in einem an das Systemlog von Windows NT erinnernden Zentralspeicher zusammenlaufen.

Sein Inhalt lässt sich über das Programm Logcat entgegennehmen, das von Xamarin in Visual Studio integriert wird und über View | Other Windows | Android Device Logging auf den Bildschirm kommt. Nach der Auswahl des Wunschgeräts mit dem kleinen Smartphonesymbol erscheinen mehr oder weniger zahlreiche Meldungen, deren Anzeige sich schon mal auf die Systemperformance auswirken kann. Die Nutzung von „Filter by“ hilft dabei, nur gewünschte Tags auf den Bildschirm zu holen.

Signierungen

Solange Applikationen nur per Deployment ausgeliefert werden, ist das Dateisignierungssystem von Android nicht relevant. Wichtig wird es in dem Moment, wo eine Applikation Upgrades erfahren soll: Ein Angreifer könnte seine Ausleseapplikation als Update tarnen, um so an die im Jail liegenden Daten zu kommen.

Das Jailing von Android ist kein Ersatz für solide Verschlüsselung. Auf SD-Karten befindliche Jails lassen sich an dem Cardreader auslesen, auf nicht verschlüsselten Telefonen greift man schlimmstenfalls zum JTAG-Flasher.

Google löst das Problem der Sicherung auf eine innovative Art und Weise: Nur jene APKs sind berechtigt, die Daten des schon am Telefon befindlichen Programms zu kapern, die mit demselben Entwicklerzertifikat signiert wurden. Das liegt in einem als Keystore bezeichneten File, das neben einem Passwort auch zufällig generierte Daten enthält: Ein Backup an einem sicheren Platz (Stichwort Banksafe) ist empfehlenswert, um nicht im Zweifelsfall ohne Updatemöglichkeit dazustehen. Die genaue, zum Signieren notwendige, Befehlsfolge unterscheidet sich von Version zu Version und ist hier im Detail beschrieben. Für uns ist an dieser Stelle nur wichtig, dass eine auszuliefernde Applikation signiert sein muss.

Fazit

Android bringt einige für mit Windows aufgewachsene Entwickler ungewöhnliche Designpatterns mit. Wer sich jedoch einige Zeit mit dem System auseinandersetzt, stellt fest, dass der Gutteil des Systems gut „gemeint“ war. Praktische Probleme sind im Großen und Ganzen eine Konsequenz des Aufbaus im Zusammentreffen mit der noch nicht unbegrenzt leistungsfähigen Hardware.

Windows Developer

Windows DeveloperDieser Artikel ist im Windows Developer erschienen. Windows Developer informiert umfassend und herstellerneutral über neue Trends und Möglichkeiten der Software- und Systementwicklung rund um Microsoft-Technologien.

Natürlich können Sie den Windows Developer über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist der Windows Developer ferner im Abonnement oder als Einzelheft erhältlich.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -