Die Implementierung des kontextuellen Aktionsmodus (Contextual Action Mode, kurz CAM) in Android ermöglicht dem Benutzer, mehrere Elemente in einer Liste gleichzeitig zu markieren und auf diese eine oder mehrere Operationen auszuführen. Der entscheidende Aspekt dieser Funktionalität besteht darin, dass die Interaktion mit den Objekten in einem temporären, zustandsbasierten UI-Modus stattfindet – unabhängig von der normalen Aktionsleiste oder Menüstruktur.

Zunächst wird ein MultiChoiceModeListener definiert, der auf Auswahländerungen innerhalb einer ListView reagiert. Dieser Listener implementiert das Interface ActionMode.Callback, wodurch Callback-Methoden wie onActionItemClicked() bereitgestellt werden, um auf Benutzeraktionen zu reagieren. Die Selektion erfolgt durch das Aufrufen der Methode setItemChecked() innerhalb des OnItemClickListener, was das gewählte Element markiert und gleichzeitig den Kontextmodus initiiert, ohne auf ein langes Drücken angewiesen zu sein. Dies ist entscheidend für die Benutzerfreundlichkeit, da das lange Drücken als Einstiegspunkt oft übersehen wird.

Innerhalb des ListAdapters wird ein einfaches Array von Ländern verwendet, welches über ein Standardlayout (android.R.layout.simple_list_item_checked) visualisiert wird. Das ListView-Element wird in den Mehrfachauswahlmodus CHOICE_MODE_MULTIPLE_MODAL versetzt und der definierte Listener wird über setMultiChoiceModeListener() eingebunden.

Die Menüaktionen wie „Move“ oder „Delete“ werden über die IDs der Menüeinträge identifiziert. Nach Ausführen der entsprechenden Aktion wird der Modus durch mode.finish() beendet. Visuelles Feedback wird über Toast-Nachrichten bereitgestellt, um dem Nutzer eine Rückmeldung über seine Aktion zu geben.

Ein bemerkenswerter Aspekt ist die Möglichkeit, den kontextuellen Aktionsmodus auch ohne sichtbare Action Bar zu verwenden. In solchen Fällen wird die Contextual Action Bar (CAB) dynamisch in das UI eingefügt und bei Beendigung wieder entfernt. Dies erhöht die Flexibilität bei der Gestaltung von Aktivitäten ohne permanente Aktionsleiste.

Zusätzlich zum kontextuellen Modus gibt es die Möglichkeit, ein Pop-up-Menü bereitzustellen. Im Gegensatz zur CAB beeinflusst das Pop-up-Menü das UI nicht dauerhaft, sondern bietet temporäre Auswahloptionen – typischerweise als Reaktion auf das Drücken eines Buttons. Ein typisches Szenario ist ein ImageButton, der beim Anklicken ein Menü mit Optionen wie „Reply“, „Reply All“ oder „Forward“ anzeigt.

Die XML-Datei für das Menü wird im Verzeichnis res/menu erstellt und durch popupMenu.inflate() eingebunden. Der Listener OnMenuItemClickListener übernimmt die Selektion und verarbeitet die Auswahl analog zur CAB. Auch hier geben Toast-Meldungen dem Nutzer eine sofortige Rückmeldung.

Die Entscheidung, ob ein CAB oder ein Pop-up-Menü verwendet wird, hängt vom Anwendungsfall ab. Der CAB eignet sich für zustandsbasierte Aktionen an einer Sammlung von Objekten, während das Pop-up-Menü eher für kontextbezogene, punktuelle Interaktionen gedacht ist. Die klare Trennung dieser beiden Mechanismen gewährleistet eine intuitive und konsistente Benutzererfahrung, was die Lernkurve für neue Benutzer senkt.

Es ist essentiell, den Kontext der Interaktion in der UI-Architektur korrekt zu definieren. Der kontextuelle Modus signalisiert dem Benutzer explizit einen temporären Statuswechsel und sollte visuell klar vom normalen Modus getrennt sein. Zudem sollte die Einstiegsgeste klar kommuniziert oder intuitiv auffindbar sein, um unbeabsichtigte Intransparenz im Verhalten der Anwendung zu vermeiden.

Wichtig ist außerdem die korrekte Trennung der Menütypen innerhalb der Architektur. Während kontextuelle Menüs (CAM) den Fokus auf Objektselektion und -manipulation legen, stellen Pop-up-Menüs eher eine Erweiterung der unmittelbaren Kontextfunktion dar. Ihre Einbindung sollte nicht zu Seiteneffekten im UI führen, da sie, im Gegensatz zu Kontextmenüs, keine dauerhafte Zustandsänderung hervorrufen. Die Wahl des Menütyps ist daher keine gestalterische, sondern eine strukturelle Entscheidung mit direkten Implikationen für das Verhalten der Applikation.

Wie liest man Ressourcentextdateien in Android und was ist dabei zu beachten?

In Android-Projekten gibt es verschiedene Möglichkeiten, externe Textdateien einzubinden und auszulesen. Zwei der gebräuchlichsten Methoden sind das Laden von Dateien aus dem res/raw-Verzeichnis und aus dem assets-Ordner. Trotz ähnlicher Funktionalität unterscheiden sich diese beiden Ansätze vor allem in der Art, wie die Dateien im Code referenziert und gehandhabt werden.

Zunächst wird in einem Beispielprojekt ein Layout mit zwei TextViews erstellt, um den Inhalt von zwei unterschiedlichen Textdateien gleichzeitig anzuzeigen. Eine Datei befindet sich im Verzeichnis res/raw, die andere im assets-Ordner. Wichtig ist, dass der Ordner res/raw explizit im Projekt angelegt wird, da er nicht automatisch in neuen Android-Projekten enthalten ist. Im Gegensatz dazu ist der assets-Ordner etwas schwieriger zu erstellen, jedoch stellt Android Studio eine eigene Option im Menü zur Verfügung, um diesen Ordner bequem hinzuzufügen.

Der Zugriff auf die Ressourcen erfolgt über unterschiedliche Methoden. Für eine Datei im raw-Verzeichnis wird die Methode getResources().openRawResource(R.raw.raw_text) verwendet. Diese liefert ein InputStream-Objekt zurück, welches direkt zum Lesen der Datei genutzt werden kann. Dateien im assets-Ordner werden über getAssets().open("asset_text.txt") geladen, was ebenfalls einen InputStream liefert, allerdings in einem try/catch-Block ausgeführt werden muss, da hier keine Kompilierzeit-Prüfung erfolgt und Ausnahmen behandelt werden müssen.

Das Einlesen der Datei geschieht in beiden Fällen über eine Methode, die den InputStream zeilenweise mit einem BufferedReader ausliest und den gesamten Text in einem StringBuilder sammelt. Diese Methode gibt den kompletten Text als String zurück und kann anschließend in der Benutzeroberfläche angezeigt werden.

Ein wesentlicher Unterschied zwischen beiden Ressourcenarten liegt in der Art der Verwaltung und Verfügbarkeit der Dateien. Dateien im res/raw-Ordner sind über eine generierte Ressourcen-ID (z.B. R.raw.raw_text) fest im Projekt verankert. Dadurch erhält man zur Kompilierzeit eine sichere Referenz, die ein versehentliches Vergessen oder Umbenennen der Datei verhindert. Im Gegensatz dazu sind Dateien im assets-Ordner nicht über eine solche ID verfügbar, sondern werden als einfache Dateinamen behandelt, was bedeutet, dass Fehler in der Benennung oder das Fehlen der Datei erst zur Laufzeit auffallen.

Aus praktischer Sicht bietet die Kombination beider Methoden eine Flexibilität, die es ermöglicht, statische Ressourcen in res/raw zu halten, während zusätzliche Dateien zur Laufzeit etwa aus dem Internet heruntergeladen und im assets-Verzeichnis ergänzt oder ersetzt werden können. So kann die Anwendung auf neue Inhalte reagieren, ohne dass das gesamte APK neu ausgeliefert werden muss.

Es ist außerdem wichtig zu verstehen, dass assets-Dateien nicht automatisch vom System verwaltet werden, sodass bei Zugriffen stets auf mögliche Fehler geprüft werden muss. Ebenso erfordert das Lesen der Dateien mittels InputStream immer eine ordnungsgemäße Behandlung von Ein-/Ausgabe-Ausnahmen.

Die im Beispiel genutzte Methode, Textdateien zeilenweise zu lesen, stellt eine universelle Herangehensweise dar, die leicht an andere Dateiformate oder größere Ressourcen angepasst werden kann. Dabei sollte jedoch immer der Ressourcenzugriff und das Ressourcenmanagement bedacht werden, da fehlerhafte oder ineffiziente Handhabung zu Speicherproblemen oder Abstürzen führen kann.

Neben dem reinen Lesen von Dateien kann das Konzept auch auf komplexere Datenquellen erweitert werden, beispielsweise durch dynamische Ressourcenaktualisierung via Netzwerk oder das Speichern und Abrufen von Daten in Datenbanken wie SQLite. Die saubere Trennung von Ressourcen und Datenmanagement ist dabei ein grundlegender Aspekt für stabile und wartbare Android-Anwendungen.

Endlich ist das Verständnis der unterschiedlichen Ressourcentypen und deren Zugriffsmethoden zentral für die effiziente Gestaltung von Android-Apps, da es die Grundlage für den Umgang mit allen externen Dateien bildet — von einfachen Texten bis hin zu komplexen Konfigurationsdateien oder multimedialen Inhalten.

Wie man mit Daten arbeitet: Grundlagen und Optimierung der Datenbankabfragen in Android

Die Verwendung von Datenbanken in Android-Anwendungen gehört zu den grundlegenden Aspekten der App-Entwicklung. SQLite bietet eine einfache Möglichkeit, lokal Daten zu speichern. Allerdings geht die Arbeit mit Daten weit über das Erstellen einer funktionierenden Datenbank hinaus. Es erfordert auch ein Verständnis für die Verwaltung von Datenversionen, den Umgang mit langen Datenbankabfragen und die Optimierung der Anwendungsleistung. In diesem Kapitel werden wir uns mit der grundlegenden Funktionsweise von SQLite und der Verwendung von Loadern zur Hintergrundverarbeitung von Daten beschäftigen.

Die erste Herausforderung bei der Arbeit mit einer SQLite-Datenbank ist die Verwaltung der Datenbankversionen. Wenn die Version der Datenbank erhöht wird, beispielsweise von Version 1 auf Version 2, ruft Android automatisch die onUpgrade()-Methode auf. Dies ist ein kritischer Moment, um die bestehenden Daten in das neue Format zu migrieren. Wichtig ist, dass nicht garantiert werden kann, dass der Benutzer die App in aufeinanderfolgender Reihenfolge aktualisiert. Daher könnte ein Benutzer beispielsweise von Version 1 direkt auf Version 4 springen. Diese Tatsache verlangt, dass wir beim Upgrade der Datenbank so flexibel wie möglich bleiben, um die Integrität der Daten zu gewährleisten und gleichzeitig die Nutzererfahrung zu optimieren.

Ein weiterer wesentlicher Aspekt bei der Arbeit mit Daten in Android ist die Vermeidung von blockierenden Operationen im UI-Thread. Längere Datenbankabfragen auf dem UI-Thread können dazu führen, dass die Anwendung nicht mehr reagiert, was die Benutzererfahrung erheblich beeinträchtigt. Im schlimmsten Fall zeigt das System einen "Application Not Responding" (ANR)-Fehler an, was zu einem Absturz der App führen kann. Um dies zu vermeiden, bietet Android die Loader-API, die speziell dafür entwickelt wurde, um Datenbankabfragen im Hintergrund durchzuführen und das UI zu benachrichtigen, sobald die Daten verfügbar sind. Ab Android 3.0 (API Level 11) ist der Loader die bevorzugte Methode zur Durchführung von Hintergrundabfragen.

Die Verwendung von Loaders hat zwei Hauptvorteile: Erstens wird die Datenbankabfrage automatisch in einem Hintergrundthread ausgeführt, was verhindert, dass der UI-Thread blockiert wird. Zweitens sorgt der Loader dafür, dass die angezeigten Daten automatisch aktualisiert werden, wenn die zugrunde liegende Datenquelle, wie z. B. ein Content Provider, Änderungen erfährt.

Um diese Technik in einer Anwendung zu integrieren, benötigen wir einen benutzerdefinierten Adapter, der von CursorAdapter erbt und die Daten aus der SQLite-Datenbank an eine Ansicht wie ein ListView bindet. Der CursorAdapter ist verantwortlich für die Bindung der Daten aus einem Cursor an die Benutzeroberfläche. Der DictionaryAdapter ist in diesem Fall eine konkrete Implementierung des Adapters, die die Wörter aus einer Wortliste darstellt.

Neben dem Adapter benötigen wir einen Loader, der die Daten im Hintergrund lädt. Ein CursorLoader ist in diesem Fall eine Erweiterung des Loader-Objekts, das die SQLite-Datenbank abfragt und das Ergebnis an den Adapter übergibt. Der DictionaryLoader ist dafür verantwortlich, die Wortliste aus der Datenbank zu laden, ohne den UI-Thread zu blockieren.

Im Code verwenden wir den DictionaryAdapter zusammen mit einem DictionaryLoader, um die Daten im Hintergrund zu laden und sie auf der Benutzeroberfläche darzustellen. Dies gewährleistet eine reibungslose Benutzererfahrung, da die UI nicht einfriert, während Daten aus der Datenbank geladen werden.

Der grundlegende Ablauf in der MainActivity besteht darin, die notwendigen UI-Elemente wie EditText, Button und ListView zu initialisieren und die Daten mit einem Loader zu laden. Sobald der Benutzer ein neues Wort hinzufügt oder ein bestehendes Wort löscht, wird der Loader neu gestartet, um die Daten zu aktualisieren. Das ist besonders wichtig, um sicherzustellen, dass die angezeigte Liste immer die neuesten Daten enthält.

Besonders hervorzuheben ist, dass in diesem Beispiel die Daten direkt aus der SQLite-Datenbank geladen werden, ohne den Umweg über einen Content Provider zu gehen. Das bedeutet, dass der CursorAdapter nicht mit einem URI arbeiten muss, sondern direkt mit einem Cursor aus der Datenbank interagiert.

Ein weiterer wichtiger Punkt ist, dass der Loader nur dann wiederhergestellt oder neu gestartet wird, wenn sich die zugrunde liegenden Daten ändern. Dies ist effizienter als eine ständige Aktualisierung der Daten, da es unnötige Datenbankabfragen vermeidet. Dies verbessert nicht nur die Performance, sondern reduziert auch die Systemressourcen, die durch wiederholte Abfragen verbraucht werden.

Neben der grundlegenden Implementierung der Datenbankabfragen ist es von Bedeutung, dass der Entwickler den Umgang mit Daten auf lange Sicht berücksichtigt. Besonders bei größeren Datenmengen kann es sinnvoll sein, Strategien zur Optimierung der Datenbankabfragen zu entwickeln. Beispielsweise können Indexe für häufig abgefragte Felder erstellt oder Transaktionen verwendet werden, um mehrere Datenbankoperationen zu gruppieren und so die Performance zu steigern.

Zudem sollte man sich bewusst sein, dass Datenbankoperationen auf mobilen Geräten durch verschiedene Faktoren, wie z. B. die Hardwareleistung und die Netzwerkanbindung, langsamer sein können als auf Desktop-Systemen. Es ist daher von größter Bedeutung, die Benutzererfahrung stets im Auge zu behalten und sicherzustellen, dass die App selbst unter ungünstigen Bedingungen reibungslos läuft.

Abschließend lässt sich sagen, dass die korrekte Handhabung von Datenbankabfragen und -migrationen sowie die Nutzung von Hintergrundoperationen wie dem Loader zu einer wesentlich besseren Leistung und Benutzererfahrung führen kann. Auch wenn das hier beschriebene Beispiel relativ einfach ist, sollten Entwickler stets darauf achten, ihre Anwendungen auch bei komplexeren Szenarien effizient zu gestalten.

Wie man eine MediaPlayer-Benachrichtigung in Android erstellt (API 21)

In der neuesten Version von Android können Entwickler benutzerdefinierte Benachrichtigungen für Medienplayer erstellen, die besonders auf der Sperrbildschirm-Oberfläche hervorgehoben werden. Diese Art von Benachrichtigungen bietet eine komfortable Möglichkeit, die Benutzererfahrung zu verbessern, indem sie interaktive Steuerelemente direkt auf dem Sperrbildschirm bereitstellt, ohne dass der Nutzer die Anwendung öffnen muss.

Das grundlegende Konzept hierbei ist, eine Benachrichtigung zu erzeugen, die Mediensteuerungen wie „Play“, „Pause“, „Vorheriges Lied“ und „Nächstes Lied“ ermöglicht. Im Gegensatz zur früheren Methode, bei der NotificationCompat verwendet wurde, nutzt diese Technik die nativen Funktionen von Android ab API Level 21 (Lollipop), um Medieninhalte nahtlos darzustellen.

Der Aufbau der Benachrichtigung erfolgt mit der Notification.Builder-Klasse, die bei Versionen ab Android 6.0 (API 23) die Verwendung von Icons ermöglicht, während für frühere Versionen auf die alten Methoden zurückgegriffen wird, um eine breite Kompatibilität sicherzustellen. Die Implementierung beginnt mit der Erstellung einer einfachen Benutzeroberfläche, die einen Button enthält, welcher die Benachrichtigung auslöst.

Der Code für die Benachrichtigung

Zunächst erstellen Sie in Android Studio ein neues Projekt mit dem Namen „MediaPlayerNotification“. Wählen Sie als Aktivitätstyp „Empty Activity“ und stellen Sie sicher, dass das Ziel-API-Level mindestens API 21 (Android 5.0) ist. Danach fügen Sie einen Button in die activity_main.xml-Datei ein, um den Benutzer mit der Funktion zur Anzeige der Benachrichtigung zu interagieren.

xml
<Button
android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Show Notification" android:onClick="showNotification"/>

Die Logik zur Erstellung der Benachrichtigung wird in der MainActivity.java implementiert. Die Methode showNotification() erzeugt eine Benachrichtigung mit den entsprechenden Steuerungen für den Medienplayer. Sie verwendet PendingIntent, um eine Interaktion mit der Aktivität zu ermöglichen, wenn der Benutzer auf eine der Schaltflächen in der Benachrichtigung klickt. Zudem wird die Sichtbarkeit der Benachrichtigung auf VISIBILITY_PUBLIC gesetzt, damit diese auch auf dem Sperrbildschirm angezeigt wird.

java
@Deprecated public void showNotification(View view) {
Intent activityIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, activityIntent, 0); Notification notification; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { notification = new Notification.Builder(this) .setVisibility(Notification.VISIBILITY_PUBLIC) .setSmallIcon(Icon.createWithResource(this, R.mipmap.ic_launcher)) .addAction(new Notification.Action.Builder( Icon.createWithResource(this, android.R.drawable.ic_media_previous), "Previous", pendingIntent).build()) .addAction(new Notification.Action.Builder( Icon.createWithResource(this, android.R.drawable.ic_media_pause), "Pause", pendingIntent).build()) .addAction(new Notification.Action.Builder( Icon.createWithResource(this, android.R.drawable.ic_media_next), "Next", pendingIntent).build()) .setContentTitle("Music") .setContentText("Now playing...") .setLargeIcon(Icon.createWithResource(this, R.mipmap.ic_launcher)) .setStyle(new Notification.MediaStyle().setShowActionsInCompactView(1)) .build(); } else { notification = new Notification.Builder(this) .setVisibility(Notification.VISIBILITY_PUBLIC) .setSmallIcon(R.mipmap.ic_launcher) .addAction(new Notification.Action.Builder( android.R.drawable.ic_media_previous, "Previous", pendingIntent).build()) .addAction(new Notification.Action.Builder( android.R.drawable.ic_media_pause, "Pause", pendingIntent).build()) .addAction(new Notification.Action.Builder( android.R.drawable.ic_media_next, "Next", pendingIntent).build()) .setContentTitle("Music") .setContentText("Now playing...") .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)) .setStyle(new Notification.MediaStyle().setShowActionsInCompactView(1)) .build(); } NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.notify(0, notification); }

Funktionsweise der Benachrichtigung

Es ist wichtig, darauf hinzuweisen, dass der @Deprecated-Annotation darauf hinweist, dass einige der verwendeten Methoden veraltet sind, aber dennoch kompatibel sind. Der Code prüft die Android-Version zur Laufzeit und stellt sicher, dass für Geräte mit Android 6.0 oder höher die neuesten Methoden verwendet werden, während ältere Geräte mit API 21 und 22 weiterhin die alten Methoden verwenden. Diese Technik gewährleistet die Rückwärtskompatibilität der App.

Ein besonders bemerkenswerter Punkt ist die Verwendung von setShowActionsInCompactView(1), das sicherstellt, dass die Steuerungsaktionen (wie Play, Pause und Vorwärts) auch im kompakten Layout auf dem Sperrbildschirm sichtbar sind.

Für eine vollständigere Implementierung könnte eine MediaSession hinzugefügt werden, um die aktuelle Musikwiedergabe zu verwalten und das Albumcover auf dem Sperrbildschirm anzuzeigen. Dies könnte wie folgt erfolgen:

java
.setMediaSession(mMediaSession.getSessionToken())

Die MediaSession sorgt dafür, dass die Wiedergabe von Mediendateien systemweit erkannt wird und die Benachrichtigung entsprechend angepasst wird, z. B. durch Anzeigen des Albumcovers und anderer Metadaten.

Weitere Überlegungen

Neben der einfachen Implementierung einer Benachrichtigung für die Mediensteuerung gibt es noch andere Aspekte, die Entwickler berücksichtigen sollten. Die Verwendung von PendingIntent für die Interaktionen innerhalb der Benachrichtigung ist ein Standardverfahren, um mit den Benutzern zu interagieren. Bei komplexeren Anwendungen könnte jedoch die Notwendigkeit bestehen, mehrere Intents für unterschiedliche Aktionen zu erstellen.

Darüber hinaus ist es wichtig, die visuelle Darstellung von Benachrichtigungen auf verschiedenen Android-Versionen zu berücksichtigen. Auf Geräten mit älteren Betriebssystemversionen könnte die Darstellung weniger ausgefeilt oder schlichtweg inkompatibel sein. Die Wahl der richtigen API für Ihre Zielgeräte ist daher von zentraler Bedeutung, um eine konsistente Benutzererfahrung zu gewährleisten.

Auch die Interaktion der Benachrichtigungen mit dem Sperrbildschirm kann zu einem kritischen Punkt werden, insbesondere bei Geräten, die eine umfangreiche Anpassung der Benachrichtigungen unterstützen. Bei der Gestaltung von Benachrichtigungen sollte immer berücksichtigt werden, wie diese die Benutzererfahrung verbessern können, ohne den Nutzer zu stören oder die Übersichtlichkeit zu beeinträchtigen.

Ein weiterer wichtiger Punkt ist der Umgang mit der Medienwiedergabe im Hintergrund. Zwar ist die Benachrichtigung ein nützliches Mittel zur Steuerung der Wiedergabe, jedoch muss auch eine effiziente Verwaltung der Ressourcen und der Stromversorgung gewährleistet werden, um eine störungsfreie Benutzererfahrung zu ermöglichen.