Teil 2: Project Rome auf Abwegen

Project Rome: Android Italia!
Keine Kommentare

Microsoft erlaubt seinen Entwicklern mit dem Remote-Systems-API, auf das „Unruhiger-werden“ der Nutzer zu reagieren. Am Ende des ersten Artikels dieser Serie hatten wir unter Nutzung des Remote-Systems-API eine Interaktion zwischen mehreren Geräten durchgeführt, die alle auf der Universal Windows Platform basieren.

Hätte Steve Ballmer mit Windows Phone 7 Erfolg gehabt, so müssten wir uns an dieser Stelle maximal mit Applications Services herumärgern. Leider ist der Marktanteil mobiler UWP-Geräte verschwindend gering; ein Faktum, das Microsoft zur Ankündigung von SDKs für diverse andere Plattformen animiert.

Zum Zeitpunkt der Drucklegung gibt es vier verschiedene SDKs, die nicht für die UWP vorgesehen sind. Erstens: Native Entwicklungssysteme für Android und iOS, die Entwicklern das Einbinden von Remote-Systems-API-Funktionalitäten in Android- und Apple-Apps erlauben. Zweitens: Ein SDK für Xamarin, das sich – logischerweise – an all jene richtet, die .NET-Technologien im Mobilbereich einsetzen. Zu guter Letzt gibt es noch ein API auf Basis von Microsoft Graph: Das ist für all jene vorgesehen, die mit den angebotenen SDKs nicht auskommen, aber trotzdem nicht auf die Remote-Systems-Plattform verzichten können.

Artikelserie

Teil 1: Project Rome: Apps auf Wanderschaft

Teil 2: Project Rome auf Abwegen

Teil 3: Komplexe Remote-Interaktionen mit App Services

Zu Beginn muss angemerkt werden, dass Geräte bzw. Applikationen, die auf einem der vier SDKs basieren, „Teilnehmer zweiter Klasse“ in Remote-Systems-Ökosystemen darstellen. Das äußert sich unter anderem dadurch, dass die Geräte ausschließlich aktiv agieren können: Ein Android-Smartphone kann also einen UWP-Diener nach Belieben mit Befehlen traktieren, von ihm selbst aber nicht mit Aufgaben geärgert werden. Warum sich Microsoft an dieser Stelle nicht kooperativer zeigt, ist fraglich; das „Parsen“ von URLs wäre beispielsweise auch unter Android problemlos möglich.

Vorbereitungshandlungen

Wer eine AMD-Workstation hat, soll den Gedanken an die Entwicklung von Android-Programmen unter Windows loslassen: Der durchaus brauchbare Softwareemulator funktioniert nämlich nur unter Linux mit Hardwarebeschleunigung. Aus diesem Grund wird der Autor auf Android-Seite in den folgenden Schritten eine AMD-Achtkern-Workstation verwenden, die unter Ubuntu 14.04 betrieben wird. Als Entwicklungsumgebung kommt eine aktuelle Version von Android Studio zum Einsatz; falls Ihnen diese fehlt, finden Sie auf der Homepage von Android Studio Informationen zum Download. Android Studio verhält sich übrigens – Google sei Dank – unter Windows, MacOS und Linux im Großen und Ganzen identisch; wer unter Intel arbeitet, kann die folgenden Schritte natürlich auch unter Windows nachvollziehen.

Aufgrund der nicht unerheblichen Komplexität des Projektskeletts – unter anderem liefert Microsoft die Bibliotheken nur für bestimmte Prozessorarchitekturen aus – ist es in höchstem Maße empfehlenswert, die Initialerstellung des Android-Projektskeletts unter Nutzung des in GitHub bereitstehenden Projektbeispiels durchzuführen. Auf der Website klicken Sie auf den Button Clone or Download und dann Download ZIP, um ein rund 20 MB großes Abbild des gesamten Repositorys herunterzuladen.

Öffnen Sie im nächsten Schritt Android Studio, und klicken Sie auf den Button Open an existing Android Studio project. Im daraufhin erscheinenden Common-Dialog navigieren Sie zur Datei project-rome-master/Android/sample/build.gradle, die als Quellfile für das gesamte Projekt dient.

Mit Eclipse oder Visual Studio aufgewachsene Entwickler müssen an dieser Stelle umdenken: Android Studio basiert auf dem Gradle-Build-System, das Entwicklern an manchen Stellen eine komplett andere Vorgehensweise aufzwingt. Der wichtigste Unterschied ist die Art, wie die Steuerung des Projekts erfolgt. In Visual Studio werden die diversen Parameter in der Projektdatei festgelegt, die normalerweise ausschließlich mit WYSIWYG-Methoden bearbeitet wird.

Im Fall von Gradle sieht die Situation komplett anders aus: Android Studio enthält einen Parser, der die Dateien analysiert und daraus Makefiles und andere für die Projektverwaltung erforderliche Artefakte generiert. Das manuelle Bearbeiten von .gradle-Dateien ist unter Android-Entwicklern übrigens Gang und Gäbe. Das zeigt sich in der in Abbildung 1 dargestellten Projektstruktur; verwirrend ist, dass unser Projekt zwei build.gradle-Dateien enthält.

Abb. 1: Microsofts Beispielprojekt ist durchaus verwirrend aufgebaut

Für uns ist hierbei das zum Modul app gehörende File relevant: Android Studio blendet neben dem Dateinamen in grau einen Beschreibungstext ein, der erklärt, zu welchem Teil des Android-Projekts das jeweilige Gradle-File gehört. Da der Umgang mit Gradle einen interessanten „Brainteaser“ darstellt, wollen wir die interessantesten Aspekte des Produkts kurz durchgehen.

Das Buildsystem enthält ein an NuGet erinnerndes Paketverwaltungssystem, mit dem Entwickler Module aus dem Internet herunterladen können. Microsoft meldet mit diesem Block ein Repository auf Basis von Maven an, das die diversen für die Einbindung des API notwendigen Module bereitstellt.

repositories {
jcenter()
maven {
url "http://projectrome.bintray.com/maven"
}
}

Auf der Unterseite des Files findet sich der dependencies-Block, in denen die diversen erforderlichen Module aufscheinen. Gradle analysiert sie im Rahmen der Kompilation und beschafft fehlende Module automatisch. Project Rome wird wie in Listing 2 dargestellt eingebunden.

dependencies {
  . . .
  compile(group: 'com.microsoft.connecteddevices', name: 'connecteddevices-sdk-armv7', version: '0.4.0', ext: 'aar', classifier: 'release')
 . . .
}

Zudem finden sich in der .gradle-Datei einige Blöcke, die das IDE zum selektiven Deployment des Kompilats anweisen. Das ist beim Übernehmen des vorgegebenen Projektskeletts nicht weiter relevant – wichtig wird es, falls Sie das Project-Rome-API in ein vorhandenes Projekt einbinden wollen.

Android Studio reagiert auf Änderungen in .gradle-Dateien mit der Einblendung eines gelben Banners, das den Entwickler darüber informiert, dass die Dateien nicht mehr „synchron“ sind. Wenn Sie dieses Fenster aus irgendeinem Grund am Bildschirm sehen, so klicken Sie auf Sync Now – das IDE aktualisiert Makefiles und Co. dann automatisch.

Ein Konto muss her

Da die Arbeit mit dem Project-Rome-SDK die Nutzung eines Microsoft-Accounts voraussetzt, müssen Sie im nächsten Schritt das im Microsoft Application Registration Portal bereitstehende Entwickler-Backend öffnen. Wechseln Sie dort in die Rubrik „Live SDK applications“, und klicken Sie auf den blauen Knopf Add an app. Der Assistent fragt im ersten Schritt nach dem Namen: Wir nutzen in den folgenden Schritten SUSRome. Nach dem Anklicken von Create Application ist etwas Zeit erforderlich, bevor eine Website mit Detailinformationen erscheint. Von besonderem Interesse ist der im Feld Client ID angezeigte String, der Ihr Programm gegenüber dem Microsoft-Server authentifiziert. Er muss in die MainActivity wandern:

public class MainActivity . . .{
private static String CLIENT_ID = "000000004C1E181E";

Das Projektskelett ist von Haus aus nur für Webapplikationen geeignet; wer es mit einer mobilen Applikation verbindet, wird während der Programmausführung mit einem Fehler belohnt. Zur Behebung dieses Problems klicken Sie auf den Button Add Platform und wählen im daraufhin erscheinenden Feld die Option „Native Application“ aus. Microsoft erweitert Ihr Applikationsprofil sodann um eine weitere Rubrik, in der Sie hauseigene Redirect-URLs eingeben dürfen. Das ist für uns allerdings nicht notwendig, da ein Anklicken des Pfeils die in Abbildung 2 gezeigten und von Microsoft vorangemeldeten URLs auf den Bildschirm holt.

Abb. 2: Das Backend zeigt sich an dieser Stelle von seiner komfortablen Seite

Damit ist unser Programmskelett adaptiert. Senden Sie es auf ein Smartphone Ihrer Wahl, wo es sich wie in Abbildung 3 gezeigt präsentiert. Klicken Sie auf den Button Sign in with Microsoft MSA, um das Programm zum Anzeigen des Anmeldedialogs zu animieren.

Abb. 3: Die Benutzerschnittstelle des von Microsoft angebotenen Android-Beispiels ist eher spartanisch

Nach dem Anklicken des Buttons erscheint der vom Desktop bekannte Anmeldedialog, mit dem sich der Benutzer bei einem Microsoft-Konto anmelden kann. Interessant ist hierbei, dass man im Hause Microsoft für die Anzeige der diversen vom Webserver angelieferten Informationen auf ein Dialogfenster setzt, in dem eine WebView mit Inhalten angezeigt wird. Nach dem erfolgreichen Anmeldeprozess erscheint ein Bildschirm, der eine Übersicht aller im Konto enthaltenen Geräte anbietet.

Was passiert hier?

Beginnen wir unsere Betrachtungen des API mit dem Log-in-Knopf, der den Start der Anmeldeoperation auslöst. Er führt zur Anzeige einer WebView, die im Rahmen der Erstellung in ein URL-Wechsel-Listener eingeschrieben wird. Dessen Aufgabe ist das Erkennen des von Microsoft angelieferten „Erfolgs-URLs“, der sodann um den Authentifizierungscode erleichtert wird (Listing 3).

public void onLoginClick(View view) {. . .
  _web.loadUrl(_oauthUrl);
  WebViewClient webViewClient = new WebViewClient() {
    . . .
    @Override
    public void onPageFinished(WebView view, String url) {
      super.onPageFinished(view, url);
      if (url.startsWith(REDIRECT_URI)) {
        Uri uri = Uri.parse(url);
        String code = uri.getQueryParameter("code");
        . . .

In MainActivity müssen Entwickler zudem eine Instanz der Platform-Klasse beleben, die im Namespace com.microsoft.connecteddevices liegt. Sie exponiert eine Gruppe von Funktionen, die unter Anderem zur Anmeldung der Client-IDs dienen. Zudem liefert die Platform-Klasse den URL zurück, der für den Start der Anmeldung verwendet wird (Listing 4).

private void InitializePlatform() {
  this.runOnUiThread(new Runnable() {
    @Override
    public void run() {
      . . .
      Platform.initialize(getApplicationContext(), new IAuthCodeProvider() {
        @Override
        . . .
        public void fetchAuthCodeAsync(String oauthUrl, Platform.IAuthCodeHandler handler) {
          _oauthUrl = oauthUrl;
          _authCodeHandler = handler;
          . . .

Rein theoretisch wären wir an dieser Stelle fertig; in der Praxis macht uns Google durch die in neuen Versionen von Android eingeführte Rechteverwaltung einen Strich durch die Rechnung. Auf modernen Android-Smartphones reicht es nicht aus, eine benötigte Permission in der Manifestdatei zu deklarieren. Der Nutzer muss ihrer Verwendung vielmehr vor dem ersten Start des Programms zustimmen, was im Fall der Plattformklasse wie in Listing 5 aussieht.

Random rng = new Random();
_permissionRequestCode = rng.nextInt(128);
int permissionCheck = ContextCompat.checkSelfPermission(this.getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION);
if (permissionCheck == PackageManager.PERMISSION_DENIED) {
  ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.ACCESS_COARSE_LOCATION}, _permissionRequestCode);
} else {
  InitializePlatform();
}

Hierzu eine kleine Anmerkung: Die Platform-Klasse nutzt auch Bluetooth, um nach in der Umgebung befindlicher Hardware zu suchen. Da man über diesen Funkstandard theoretisch auch Informationen über den Aufenthaltsort des Telefons erhalten kann, verlangt Android die Permission Manifest.permission.ACCESS_COARSE_LOCATION. Fehlt die Permission, so folgt eine Anforderung – der an requestPermissions übergebene Event Handler aktiviert im Fall der erfolgreichen Beschaffung die Suchroutine.

Die eigentliche Suche nach Geräten erfolgt in der Klasse DeviceRecyclerActivity. Microsoft implementiert das Remote-Services-API für Android unter Nutzung des Factory-Patterns, weshalb vor dem Aufrufen von „Start“ das Aktivieren der getResult-Funktion des RemoteSystemDiscoveryBuilders erforderlich ist (Listing 6).

private RemoteSystemDiscovery createDiscovery() {
  return discoveryBuilder.getResult();
}
private void startDiscovery() {
. . .
  discovery = createDiscovery();
  try {
    discovery.start();
  }. . .
}

Für die Erstellung des DiscoveryBuilders ist die Methode initializeData verantwortlich, deren in Listing 7 stark gekürzt abgedruckter Code mit dem Erzeugen einer neuen Klasseninstanz beginnt.

private void initializeData(){
  initializeAdapter();
  discoveryBuilder = new RemoteSystemDiscovery.Builder().setListener(new IRemoteSystemDiscoveryListener() {
    @Override
    public void onRemoteSystemAdded(RemoteSystem remoteSystem) {
      _devices.add(new Device(remoteSystem));
      . . .
    }
  });
}

Die unter der Universal Windows Platform von Hand einzuschreibenden Event Handler werden im Android-API in Form einer Instanz von IRemoteSystemDiscoveryListener angeliefert. Diese in Java-Entwicklerkreisen nicht unbedingt populäre Vorgehensweise hat sich unter Android als Standard etabliert – für uns ist die genaue Implementierung der Methoden im Moment nicht relevant.

Interessanter ist die am Ende stehende Anmeldung der Filterattribute, die hilft, die Art der anzuzeigenden Geräte einzuschränken:

..}).filter(generateDiscoveryTypeFilter()).filter(generateSystemKindFilter());
..startDiscovery();
}

Microsoft realisiert die eigentliche Anzeige der Formularinformationen komplett unter Nutzung von Data Binding – ein Thema, dem wir an dieser Stelle allerdings keinen weiteren Platz einräumen können.

Koroutinen in Kotlin

mit René Preißel (eToSquare)

Get native – iOS- und Android-Apps in Java entwickeln

mit Manfred Steyer (SOFTWAREarchitekt.at)

Analogieschlüsse sind hilfreich

Microsofts SDK richtet sich ausschließlich an Entwickler, die bereits Erfahrung im Umgang mit dem Remote-Systems-API mitbringen. Daraus folgt, dass eine Vielzahl von Klassen mehr oder weniger analog übernommen wurde – das gilt auch für RemoteSystem, das eine Verbindung zu einem Gerät beschreibt. Interessanterweise setzt man im in MSDN enthaltenen Beispiel auf einen Wrapper namens Device, der die diversen Attribute des RemoteSystem-Objekts „spiegelt“ (Listing 8).

public class Device implements Parcelable {. . .
  private String id;
  private boolean isAvailableByProximity;
  private RemoteSystem system = null;
  Device(RemoteSystem system) {
    this.system = system;
    id = system.getId();
    . . .
    isAvailableByProximity = system.isAvailableByProximity();
  }
}

In DeviceActivity.java findet sich sodann eine Gruppe von Handlern, die für das eigentliche Interagieren mit den gefundenen Zielgeräten zuständig sind. Als Kern des Beispiels dient wie im vorigen Teil der Serie die Methode launchUri, die einen URL in Richtung des Remotegeräts schiebt und ihn danach unter Nutzung der diversen am Ziel befindlichen Applikationen zu bearbeiten sucht. LaunchUri beginnt mit dem Aufnehmen der in der Textbox eingegebenen Adresse, die sodann an die Funktion LaunchUriAsync weitergereicht wird:

private void launchUri(RemoteSystemConnectionRequest connectionRequest) {
try {
   String url = _launchUriEditText.getText().toString();
   new RemoteLauncher().LaunchUriAsync(connectionRequest, url,

Auch hier erfolgt die Verarbeitung der im Rahmen der Codeausführung aufgetretenen Ereignisse über ein Listener-Objekt. Beim Abfeuern eines URL hört das Objekt auf den Namen IremoteLauncherListener und bringt den in Listing 9 dargestellten Methodenkorpus mit.

 
new IRemoteLauncherListener() {
  @Override
  public void onCompleted(RemoteLaunchUriStatus status) {
    String message;
    if (status == SUCCESS){
      message = "Launch succeeded";
      Log.i(TAG, message);
    }
    else  {
      message = "Launch failed with status " + status.toString();
      Log.e(TAG, message);
    }
  logMessage(message);
  DeviceActivity.this.runOnUiThread(new PrintToast(message));
  }

Hier findet sich keine Raketenwissenschaft: Microsoft nutzt die in Android enthaltenen Logfunktionen, um Informationen über den Ausführungszustand des Programms in Richtung des LogCat-Fensters zu schieben (Abb. 4). Es handelt sich dabei um ein in Android Studio anzeigbares Element, das die diversen Ereignisse aller Programme für den Entwickler sichtbar macht.

Abb. 4: Wundern Sie sich nicht: Im LogCat-Fenster geht es auf echten Geräten „kuschelig“ zu

Zu guter Letzt folgt noch das Abfangen von Exceptions, die im Rahmen des Übertragens der Information geworfen werden könnten:

} catch (ConnectedDevicesException e) {
 e.printStackTrace();
 }
 }

Im Benutzerinterface des Programms findet sich auch die Möglichkeit, einen Ping-Befehl in Richtung des Servers zu senden. Hierbei handelt es sich allerdings um kein im Remote-Systems-API von Haus aus enthaltenes Feature; vielmehr muss hierzu ein dediziertes Programm aus dem Marketplace auf die Maschine geladen werden, das danach einen der App Services bereitstellt, die im folgenden dritten Teil dieser Serie besprochen werden (Abb. 5).

Abb. 5: Das Übertragen eines Store-Links öffnet die Website der Companion-Applikation am Tablet

Anpassendes

Auch wenn die tiefergehende Analyse des Projektskeletts mit Sicherheit nicht uninteressant ist: Wir wollen unser Beispielprojekt nun zum Aktivieren des im vorigen Teil der Serie erzeugten Programms anpassen. Zur Erinnerung: SUSClient hört auf URLs, die nach dem Schema susrometest2342:?hallowelt=freundlich aufgebaut sind.

Die in Abbildung 5 gezeigte Ansicht entsteht unter Nutzung eines CardView-Steuerelements, dessen Korpus in der Datei device_view.xml angelegt ist. Als Masterelement dient ein LinearLayout, dem mehrere CardViews eingeschrieben werden (Listing 10).
Das jeweilige CardView-Widget bringt selbst ein oder mehrere Kindersteuerelemente mit, die wir an dieser Stelle aus Platzgründen nicht abdrucken können.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" . . .

<android.support.v7.widget.CardView

..android:layout_width="match_parent"

android:layout_height="wrap_content"

android:id="@+id/device_card"

>

Für Entwickler, die mit der Windows-Steuerelementebibliothek aufgewachsen sind, ist an dieser Stelle etwas Umdenken erforderlich, weshalb wir die Struktur nochmals durchdenken wollen. CardViews entstehen in Android durch die Errichtung eines Layoutelements, das sodann spezielle „Kartencontainer“ eingeschrieben bekommt.

Da Microsoft unser Programmbeispiel mit einem schlüsselfertigen Layout ausliefert, beginnen wir mit der Einschreibung einer weiteren Karte. Da die Logkarte zur Laufzeit nach unten expandiert, platzieren wir das Teil stattdessen unter der Ping-Karte. Der eigentliche Korpus ist dann primitiv – eine CardView und ein Button sind alles, was es zur Realisierung benötigt (Listing 11).

 
<android.support.v7.widget.CardView

android:layout_marginTop="10dp"

android:layout_width="match_parent"

android:layout_height="match_parent">

<Button

android:layout_width="wrap_content"

android:layout_height="60dp"

android:padding="10dp"

android:layout_margin="10dp"

android:id="@+id/cmdSusOpen"

android:text="SUS-App aktivieren"/>

</android.support.v7.widget.CardView>

Als Nächstes müssen wir einen Event Handler für den Knopf anlegen. Öffnen Sie hierzu DeviceActivity.java und adaptieren Sie den Code von onCreate wie in Listing 12.

Button susButton = (Button) findViewById(R.id.cmdSusOpen);
susButton.setOnClickListener(new View.OnClickListener() {
  public void onClick(View v) {
    if (device.getSystem() != null) {
      launchSUSUri(new RemoteSystemConnectionRequest(device.getSystem()));
    }
  }
});

Interessant ist in diesem Zusammenhang die „Codekollabierung“ von Android Studio: Als „inline“ deklarierte Einzel-Event-Handler werden wie in Abbildung 6 dargestellt.

Abb. 6: Der hinter dem grünen Feld stehende Code wird erst nach Anklicken sichtbar

Microsofts Version von launchUri holt sich den auszuführenden URL aus einem im Formular befindlichen Textfeld; wir begegnen diesem Problem durch die Einführung einer neuen Methode, die sich nur minimal vom Vorbild unterscheidet (Listing 13).

private void launchSUSUri(RemoteSystemConnectionRequest connectionRequest) {
  try {
    String url = "susrometest2342:?hallowelt=freundlich";
    new RemoteLauncher().LaunchUriAsync(connectionRequest, url,
    . . .

Damit sind wir an dieser Stelle fertig; das Android-Smartphone kann nun – ganz analog zu unserer Desktop-App – Kommandos in das Remote-Device-Subsystem absetzen.

Fazit

Ein alter Spruch besagt, dass die im MSDN veröffentlichten Codebeispiele stets extrem komplex ausfallen. Im Fall des Project-Rome-APIs für Android trifft das definitiv zu – allein über die Initialisierung könnte man einen ganzen Fachartikel schreiben.

Da der Windows Developer keine Fokusänderung in Richtung Android plant, wollen wir Android an dieser Stelle allerdings Android sein lassen. Der dritte und letzte Teil dieser Artikelserie zu Project Rome wird sich mit Application Services befassen. Der Autor wünscht bis dahin viel Spaß!

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

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -