20. Oktober 2021
StartMagazinVon Android 2 nach Android 4 in sieben Schritten ? Teil 1

Von Android 2 nach Android 4 in sieben Schritten ? Teil 1

Im Android Play Store bewegen sich die Anwendungen, die sich der neuen exklusiv für Tablets vorbereiteten Funktionen bedienen, in homöopathischer Dosierung. Diese Artikelserie hinterfragt die Gründe und bietet Anreize zu einer Portierung von Gingerbread auf Ice Cream Sandwich.

Android 3 sollte den Umschwung einleiten: Mit seiner Veröffentlichung sollte das Android-SDK eigentlich für Tablet-Computer gerüstet sein. Was hatte sich geändert? Neue Klassen und Methoden im Verbund mit neuen Layoutvorgaben sollten die Darstellung umfangreicher Daten auf den deutlich größeren Displays verbessern. Android-3-Apps hatten einen auf Activities ausgerichteten Ansatz: Hinter jeder Anzeige auf den kleinen Displays stand eine Activity. Eine typische Anwendung benötigte zur Anzeige einer Auswahlliste (Abbildung 1), der Detailseite eines Elements (Abbildung 2) und der passenden Änderungsseite hierzu (Abbildung 3) genau eine ListActivity und zwei Activities. Jede einzelne dieser Activities enthielt dazu immer die ganze Logik für diese Seite.

Abbildung 1: Eine Auswahlliste aller Datenbankeinträge unter Android 2.x.
Abbildung 1: Eine Auswahlliste aller Datenbankeinträge unter Android 2.x.

Abbildung 2: Detailseite eines Elements für Android-2-Apps.
Abbildung 2: Detailseite eines Elements für Android-2-Apps.

Abbildung 3: Ändern bzw. Neuerfassung eines Elementes unter Gingerbread.
Abbildung 3: Ändern bzw. Neuerfassung eines Elementes unter Gingerbread.

Während Android 3 den Tablet Geräten vorbehalten war sollten die Smartphones nach wie vor durch Android 2 bedient werden. Erst mit Android 4 [1] verschmolzen die beiden Stränge. Tablets und Phones unterstützt Ice Cream Sandwich über ein einziges System gleichermaßen. Durch ein statisches Compatibility Pack [2] kommen auch ältere und kleine Devices in den Genuss der neuen Features.

Das Activity-Prinzip war für die mehr oder weniger kleinen Displaygrößen von Smartphones perfekt. Jede Activity war gleichbedeutend mit einer Displayseite für die wiederum jeweils ein Layout sowie ein Menü benötigt wurde (bei Listen zusätzlich noch das Kontextmenü). Klare Verhältnisse, durch den Entwickler klar zu strukturieren, und vom Android SDK perfekt unterstützt.

Nun kannte aber bereits Android 2 durchaus unterschiedliche Displaygrößen. Durch entsprechend benannte Layout Ordner (zum Beispiel res/layout und res/layout-large-land) in den Ressourcen ließen sich diese recht gut abdecken. So war es möglich, zum Beispiel ListActivities auf drei unterschiedlich großen Geräten in drei unterschiedlichen Darstellungen zu bieten – und das ohne dass der Entwickler irgendetwas an seinen Sourcen ändern musste. Er erstellte für jede von ihm unterstützte Display-Größe passende XML -Layout-Dateien und legte diese in nach Android-Regeln benannten Ressource-Ordnern ab. Um den Rest kümmerte sich das Android System.

Diese Herangehensweise funktioniert nach wie vor unter Android 3 und 4. Es gibt sogar noch mehr Layout-Ordner für noch größere Displays (zum Beispiel res/layout-xlarge-land) was auch von einigen Entwicklern dankbar aufgenommen wurde.

Aber ein 10.1 Zoll Display hat einfach viel mehr Raum zu bieten als eine typische Anzeigeseite an Platz benötigt. Ein anderer Ansatz musste deshalb her. Als Beispielanwendung für die großen Geräteklassen nutzt Google gerne die typische Mail-App. Die Liste der Mails gleichzeitig mit den Details einer ausgewählten Mail auf dem Bildschirm – wie bei den Desktop Anwendungen – das war das Ziel für Android 4.0.

Auf der Heft-CD

Den Quellcode und die APK-Dateien der Beispielanwendungen finden Sie auf der Heft-CD im Verzeichnis DevCorner.

Fragmente als Lösung?

Wer nun aber denkt, dass es ab Android 4 einen Ansatz gibt, um bereits existierende Anwendungen mit einigen kleinen Anpassungen Tablet ready zu machen, hat die Rechnung leider ohne Google gemacht. Strikt nach Lehrbuch hat man mit Ice Cream Sandwich einfach den Klassenbaum des Android SDK aufgebohrt und unterhalb der Activities mit den Fragmenten quasi wiederverwendbare Container eingeführt.

Fragmente wirken optisch wie die bekannten ViewGroups – inhaltlich ähneln Sie aber eher Activities. Fragmente sollen re-usable und kombinierbar sein. Sie können in Activities eingebunden und von diesen komplett verwaltet werden.

Einfach dem Lehrbuch folgend würden wir in unserer Beispiel-App bisherige ListActivity und die Detail-Activity zu zwei Fragmenten in einer einzelnen Activity umwandelnt und wir müssten zudem in der App die Weiche zwischen kombinierter Darstellung der Fragmente auf dem Tablet sowie activity-basierender Darstellung auf dem Smartphone steuern – was letztendlich eine weitere Activity erfordern würde da auf Telefonen nach wie vor (auch mit Compatibility Pack) eine activity-zentrierte Entwicklung notwendig ist (Abbildung 4). Die Seite für das Ändern eines Elements ist an dieser Stelle noch nicht berücksichtigt. Es kommt also noch eine weitere Activity hinzu.

© developer.android.comAbbildung 4: Activities und Fragmente auf Phones und Tablets.
© developer.android.comAbbildung 4: Activities und Fragmente auf Phones und Tablets.

Kurzsichtig

Das neue Design ist sauber und das Paradebeispiel einer gelungenen Klassenbaum-Erweiterung. Die Google Entwickler schlugen sich auf die Schenkel ob der gelungenen Integration. Bloß an die Entwickler der bereits fertigen 500.000 Anwendungen hat man nicht gedacht. So gibt es keinen offiziellen Migrationspfad für den fertigen Sourcecode. Alte Apps skaliert ICS zwar auf Benutzerwunsch auf großen Displays, mehr hat Android 4.0 aber nicht zu bieten nicht. Gelungen ist etwas anderes.

So wirkt zwar eine alte App mit drei kleineren Anpassungen optisch wie eine "neue" (Stichworte: targetSdkVersion="11", hardwareAccelerated="true" im Manifest und showAsAction="..." in den Deklarationen des Optionsmenü), aber um die gleichzeitige Darstellung von Liste und Detail vorzunehmen, ist ein nicht unerheblicher Umbau einer bestehenden App notwendig. Wer eine Gratisapp bei Google Play eingestellt hat, wird es sich zwei Mal überlegen, ob sich der Aufwand lohnt. Anders sieht es aus, wenn Sie sich aktiv mit der Android-4.0-App-Entwicklung beschäftigen und die Portierung auf Android 4.0 quasi als Lehrstück betrachen.

An diesem Punkt setzt dieser Workshop an und konzentriert sich auch auf zukünftige Android-Anwendungen. Deshalb belassen wir es nicht bei den üblichen minimalen Anpassungen wie targetSdkVersion="11" sondern stellen die Migration von Android 2.x auf 4.x für zwei Apps im Detail vor. Den Quellcode und die APK-Dateien der Beispielanwendungen finden Sie auf der Heft-CD im Verzeichnis DevCorner.

Der Workshop kümmert sich dabei nicht um die kombinierte App – das Web ist voll von solchen Beispielen, sondern zeigt, wie Sie jeweils eine App für Tablets und für Smartphones bereitstellen. Eine einzelne App für Phone- und Tablet-Devices ist mit steigenden Anforderungen an die App einfach nicht mehr in vernünftigem Umfang zu realisieren. Aus diesem Grund hat Google wohl auch vor einiger Zeit die Möglichkeit geschaffen mehrere APK-Dateien für die selbe Anwendung anzubieten. Kombinierte Anwendungen sind bei einfachen übersichtlichen Apps kein Problem. Von komplexeren Anwendungen erstellen Sie aber besser mehrere unterschiedliche Programmpakete.

Der Workshop transformiert eine komplette Phone-App in eine Tablet-App. Dabei geht er auf die neuen Features wie Fragment, ActionBar, CursorLoader, Preference-Header und weitere ein. Verabschieden Sie sich schon einmal von vielen liebgewonnenen Funktionen von Android 2. Die Wahrscheinlichkeit, dass diese spätestens mit Android "Jelly Bean" als "deprecated" erklärt werden, ist extrem hoch.

Schritt 1: Ist vs. Soll

An erster Stelle folgt eine Bestandsaufnahme. Wie ist die alte Anwendung strukturiert und wie soll die neue Anwendung aussehen? Unsere ursprüngliche Phone-App enthält eine Datenbank-Tabelle mit einer vollständigen Bearbeitungskette (Liste, Anzeigen, Löschen, Neu, Editieren). Die App startet mit ActivityList. Diese zeigt eine Liste aller Einträge einer Tabelle in der Datenbank. Nach längerem Druck auf einen Listeneintrag erscheint ein Kontextmenü mit Optionen genau für diesen ausgewählten Eintrag. Der kurze Klick auf ein Element der Liste hingegen verzweigt auf dessen Detailseite. Zum bessern Verständnis finden Sie die APK-Datei der App auf der Heft-CD.

Über das Optionenmenü der Detailseite erreicht der Benutzer unter anderem die Bearbeitungsseite. Beide Menüarten (Kontext- sowie Optionenmenü) sind somit vertreten. Die Liste wird mittels eines AsyncTask im Hintergrund geladen. Während des Ladens erscheint ein Fortschrittsdialog, der auch das Ändern der Ausrichtung (vertikal/horizontal) überlebt. Ein AlertDialog als Sicherheitsabfrage vor dem Löschen eines Eintrags, der ebenfalls nach dem Orientation Change weiter existiert, ist gleichermaßen an Bord. Eine nicht sinnvoll genutzte Seite mit Anwendungs-Einstellungen rundet die Sache ab.

Die neue ICS konforme App soll dem Benutzer die Liste und die Detailseite (Abbildung 5) auf einen Blick anbieten – beides realisiert als Fragmente und verwaltet durch eine zentrale Activity. Die Änderungsseite (Abbildung 6) bleibt eine Activity die über die beiden Fragmente gelegt wird. Der AsyncTask wird durch einen neuen CursorLoader ersetzt. Die Optionenmenüs mutieren zu einem ActionBar. Im Gegensatz zu den Google-Beispielen, bei denen alle Klassen in einer Datei abgelegt werden (Activities sowie Fragmente) werden diese im Workshop größtenteils getrennt. Die Kommunikation der Objekte untereinander erfolgt mit Hilfe diverser Listener. Nicht mehr erwünschte (deprecated) Klassen und Methoden werden – sofern möglich – durch neue ersetzt.

Abbildung 5: Listen und Detailfragment nebeneinander in einer Activity.
Abbildung 5: Listen und Detailfragment nebeneinander in einer Activity.

Abbildung 6: Die Erfassungs- bzw. Änderungsseite liegt immer obenauf.
Abbildung 6: Die Erfassungs- bzw. Änderungsseite liegt immer obenauf.

Schritt 2: Manifest

Wie bereits oben aufgeführt nehmen wir in die Manifest Datei bei der Property application das Attribut hardwareAccelerated="true" auf. Mit dieser Einstellung benutzt die App den ab Android 3 vorhandene OpenGL-Renderer. Die Attribute minSdkVersion und targetSdkVersion beim Tag uses-sdk erhöhen wir von 10 (Android 2.3.3) auf 14 (Android 4.0) erhöht. Bereits diese minimalen Änderungen sorgen dafür, dass die neue ActionBar zum Einsatz kommt. Das neue und attraktive Holotheme ist dann ebenfalls sichtbar.

Beide Schritte nehmen also bereits Einfluss auf die Darstellung der App auf einem Android-4-Gerät. Fügt der Entwickler dann noch showAsAction-Attribute den Optionenmenü-Einträgen hinzu, dann sieht die App auf neueren Geräten schon toll aus. Android 4 greift einer auf diese Art angepassten App durch entsprechende Skalierung unter die Arme, und schwupps sieht die App wie eine moderne Android-4-Tablet-App aus – ist sie aber nicht. Dennoch stoppt der Entwickler meist an dieser Stelle.

Uns reicht das nicht. Wir nehmen noch beim Tag supports-screens das neue Attribut android:xlargeScreens="true" auf und setzen alle anderen Screens auf false. Damit wird diese App nur auf entsprechend dimensionierten Geräten zum Einsatz kommen.

Schritt 3: Layout

Die neue App soll ausschließlich im Tablet Modus betrieben werden. Listendarstellung und Details eines Listeneintrags befinden sich somit fast immer auf dem Schirm. Nur zur Neuerfassung bzw. Ändern eines Eintrags, sowie durch die Einstellungsseite, wird dieses Layout von einer Activity überdeckt. Auch diese Seiten bestehen jeweils aus einem Pärchen aus Activity und Fragment. Theoretisch wäre das an dieser Stelle nicht nötig aber es soll halt alles nach den neuen Regeln ablaufen.

Listing 1

Das kombinierte Layout auf dem Tablet

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >
    <fragment
        class="de.asltd.androiduser.a4.FragmentList"
        android:id="@+id/fragmentlist"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:layout_width="0dip"
        android:name="de.asltd.androiduser.a4.FragmentList" />
    <FrameLayout
        android:id="@+id/right"
        android:layout_height="match_parent"
        android:layout_weight="2"
        android:layout_width="0dip" />
</LinearLayout>

Listing 1 zeigt das Layout für den Hauptschirm mit den beiden Fragmenten. Diese XML-Ressourcen-Datei wird von der Start-Activity ActivityList geladen. Dabei wird automatisch die mit dem Tag fragment (Achtung Kleinschrift) versehene Klasse herangezogen. Dahinter verbirgt sich in der Beispielanwendung ein ListFragment. Das zweite Element der Layout-Datei ist ein Platzhalter. Dieses FrameLayout wird später, während des Programmlaufs, durch andere Fragmente ersetzt. Für die Gewichtung auf dem Display sorgt das Attribut android:layout_weight. Die Liste nimmt einen Drittel und der Platzhalter zwei Drittel des verfügbaren Platzes ein.

Die Layouts der einzelnen Fragmente übernehmen wir 1:1 aus der ursprünglichen Phone-App.

Schritt 4: Fragmente

Google versteht unter einem Fragment ein wiederverwendbares Stück Software mit einem eigenen Layout. Waren unter älteren Android- Versionen Activities mit ihren Layouts und Views das Maß aller Dinge so stehen nun mit den Fragmenten kombinierbare UI-Elemente zur Verfügung. Im Falle des Hauptschirms handelt es sich sowohl bei der Liste (FragmentList) als auch bei der Detailseite (FragmentDetails) um jeweils ein Fragment. Über diesem Konstrukt liegt eine einzelne Activity (ActivityList).

Dem Entwickler bleibt überlassen, ob er mehrere oder gar alle Fragmente seiner App durch eine Activity verwaltet oder ob er Anwendungsblöcke bildet bei denen mehrere Activities jeweils diverse Fragmente beinhalten. Letztendlich kommt es dabei nicht nur auf die Anforderungen an die App an. Der Grad der Komplexität in den Activities steigt nämlich mit der Anzahl der Fragmente. In der Praxis hat sich bewährt solche Verarbeitungsstränge (Liste, Details, Manipulation) durch jeweils eine Activity zu verwalten. Gibt es mehrere solcher Stränge so existiert dann eine entsprechende Anzahl Activities.

Fragmente besitzen einen ähnlichen Lebenszyklus wie Activities. Da Fragmente üblicherweise in Activities eingebunden sind, wird der Activity-Lebenszyklus (Abbildung 7) um mehrere Stationen erweitert (Abbildung 8). Der onAttach eines Fragments wird zum Beispiel gefeuert, wenn eine Activity das erste Mal mit einem Fragment verbunden wird. In der Beispielanwendung nutzen wir diesen Callback, um die gerade angebundenen Activities zu prüfen. Wichtig ist hier, ob seitens der Activities erforderliche Listener implementiert wurden. Mit Hilfe dieser Listener lässt sich ein Teil der Logik in die Activities verlagern. Die Fragmente erledigen ihren Teil der wieder verwendbar sein soll. Den "Rest" erledigen die Activities, wie Listing 2 zeigt.

© developer.android.comAbbildung 7: Der Activity Lebenszyklus einer klassischen Android-2.x-App.
© developer.android.comAbbildung 7: Der Activity Lebenszyklus einer klassischen Android-2.x-App.

© developer.android.comAbbildung 8: Der Lebenszyklus der Fragmente ist gegenüber den Activities erweitert.
© developer.android.comAbbildung 8: Der Lebenszyklus der Fragmente ist gegenüber den Activities erweitert.

Listing 2

Listener beim Anbinden der Activities

private MyListItemClickListener listItemClickListener;
public interface MyListItemClickListener {
        public void myListItemClick(long id);
}
@Override
public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
                listItemClickListener = (MyListItemClickListener) activity;
        } catch (ClassCastException classCastException) {
                throw new ClassCastException(activity.toString() + " must implement MyListItemClickListener");
        }
}

Google hat es sich sehr einfach gemacht: In fast allen Android-Beispielen sind Activities und Fragmente in einer Quelldatei verpackt. Die Kommunikation mit privaten Inner Classes ist so denkbar bequem. Man spart getter/setter, wechselt auf package-Variablen, und schon hat man ein wenig an der Performance- und Memory-Schraube gedreht [3]. Die Welt sieht aber anders aus. Hier ist es erforderlich richtige wiederverendbare Objekte zu verwenden. Das übernehmen die Listener.

Der in Activities so eminent wichtige onCreate()-Aufruf nutzt man in Fragmenten eher selten. Dafür kommt der neue Call onCreateView() zum Einsatz. In diesem Callback zieht das Fragment sein Layout heran – sofern es in einer Ressourcen-Datei vorhanden ist und nicht erst im Code manuell erzeugt wird. Am Namen der Methode und an ihrem Return-Wert erkennt man übrigens sehr schön, dass Fragmente nichts anderes als eine Art ViewGroups sind die wiederum in andere ViewGroups eingebettet werden, wie Listing 3 zeigt.

Listing 3

Fragmente laden ihre Layouts selbst

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
        return inflater.inflate(R.layout.fragmentdetails, null);
}

Klickt der Benutzer auf einen Eintrag in der Liste, so wird vom ListFragment ein Listener befeuert. Die angebundene ActivityList wird über diesen Event informiert und startet das zweite Fragment, das FragmentDetails mit den Daten zu dem ausgewählten Listeneintrag. Man erkennt hier, dass Fragmente nur ein User Interface mit minimaler eigener Logik bereitstellen. Die komplette Verwaltung hingegen wird den Anwendungsteilen überlassen, die diese Fragmente eingebunden haben. Ein Beispiel zeigt Listing 4.

Listing 4

Kommunikation zwischen Fragmenten und Activities

// In FragmentList
@Override
public void onListItemClick(ListView listView, View view, int position, long id) {
        super.onListItemClick(listView, view, position, id);
        listView.setItemChecked(position, true);
        listItemClickListener.myListItemClick(id);
}
// In ActivityList
public class ActivityList extends Activity
        implements FragmentList.MyListItemClickListener {
        @Override
        public void myListItemClick(long id) {
                processDetails(id);
        }
        private void processDetails(long id) {
                Fragment fragment = getFragmentManager().findFragmentById(R.id.right);
                if (fragment == null ||
                                (fragment instanceof FragmentDetails && ((FragmentDetails) fragment).getCurrentId() != id)) {
                        fragment = new FragmentDetails(id);
                        FragmentTransaction transaction = getFragmentManager().beginTransaction();
                        transaction.replace(R.id.right, fragment);
                        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                        transaction.commit();
                }
        }
}

Der Code aus Listing 4 ersetzt den eingangs genannten Platzhalter, das FrameLayout, durch das Fragment mit den Details. Sollte sich an der Stelle des Platzhalters schon ein altes FragmentDetails für einen anderen Listeneintrag befinden, so wird auch dieses durch ein neues ersetzt. Theoretisch könnte man dem Fragment einfach mittels setCurrentId() einen neuen Listeneintrag übergeben und ein Neuzeichnen veranlassen. Das Beispiel sollte aber den Austausch von Fragmenten zeigen – deshalb der etwas umständliche Weg.

FragmentManager und FragmentTransaction sind neue Klassen, die auch im Compatibility Pack vorliegen. Die komplexe Verwaltung von Fragmenten fassen wir in einer Transaktion zusammen. Der Commit veranlaßt, dass Android diese der Reihe nach abarbeitet. Im Beispiel erfolgt das Ersetzen eines leeren Platzhalters bzw. eines alten FragmentDetails durch ein neues FragmentDetails.

Selbst ganze Kaskaden an Fragmenten lassen sich in einer App vorab laden und mittels hide() sowie show() verstecken bzw. anzeigen. Es gibt hier außer dem verfügbaren Speicher so gut wie keine Grenzen.

Fazit und Ausblick

Wer nur das Minimalprogramm fahren will, kommt mit drei Änderungen in der Manifest-Datei zum Android-4.0-Ziel. Wir haben gezeigt, welche Änderungen sich noch durchführen lassen, um eine App optimal auf Android 4.0 zu portieren. Im zweiten Teil dieses Workshops zeigen wir, was sich an der Datenbankkommunikation änder und wir sich die neue ActionBar sowie das Preference-System in neuem Android-4-Gewand zeigen.

HINTERLASSEN SIE EINE ANTWORT

Please enter your comment!
Please enter your name here

EMPFEHLUNGEN DER REDAKTION

MAGAZIN

APPS & SPIELE