Version | Anpassungen |
---|---|
1.0 (2025-10-09) | Initiale Version |
Bei App-Inventor-Projekten liefern die Sensoren keine Werte, wenn sich die App nicht mehr im Vordergrund befindet. Diese Extension stellt die Werkzeuge bereit, um Sensordaten auch dann auswerten zu können, wenn sich die App im Hintergrund befindet oder der Bildschirm ausgeschaltet wird.
Die meisten Sensordaten werden in hoher Frequenz angeliefert und sind mehr oder weniger verrauscht.
Inhaltsverzeichnis
Verwendung Komponente UrsAI2SensorUtil
ForegroundService, WakeLock, BatteryOptimization
Berechtigung für Benachrichtigungen
Ereignisse zum Aktivitätszyklus
Verwendung Komponente UrsSensorAverager
Hinweis: Die Extension ist wegen des notwendigen ForegroundService ggf. nicht für eine Mehrfachinstanziierung geeignet. Deshalb sollte man nur eine Instanz der Extension in der gesamten App verwenden.
Das ZIP-Archiv UrsAI2SensorUtil zum Download. Das Archiv enthält den Quellcode, das kompilierte Binary zum Upload in den App Inventor und eine Beispiel-Anwendung.
Die Extension enthält drei Komponenten:
Die Komponente
UrsAI2SensorUtil enthält Methoden, die es ermöglichen, Sensordaten auch dann auswerten
zu können, wenn sich die App im Hintergrund befindet oder sogar, wenn der Bildschirm ausgeschaltet ist (Standby-Modus,
Doze-Mode).
Die Komponente
UrsSensorAverager liefert Mittelwerte von Daten der Sensorkomponenten, um verrausche
Daten zu glätten.
Die Komponente
UrsAverager liefert Mittelwerte von allgemeinen Daten, um verrausche Daten zu glätten.
Damit Sensordaten auch dann zur Verfügung stehen, wenn sich die App im Hintergrund befindet oder der Bildschirm ausgeschaltet ist, muss zunächst verhindert werden, dass das Gerät in den Doze-Mode wechselt, bei dem die CPU abgeschaltet wird. Bei älteren Android-Versionen reichte es, einen WakeLock anzufordern. In späteren Versionen reichte das nicht aus. Es musste zusätzlich ein ForegroundService angelegt werden. Dieser ForegroundService ist zwangsläufig mit einer Benachrichtigung (Notification) verbunden, die dem Anwender anzeigen soll, dass der Stromsparmodus ausgesetzt ist. In noch späteren Android-Versionen kann der Anwender diese Benachrichtigung löschen, ohne das der ForegroundService gestoppt wird. Er kann auch verbieten, dass die App Benachrichtigungen anzeigt. In den neuesten Android-Versionen sind Benachrichtigungen nach der Installation der App standardmäßig abgeschaltet. Das Ganze wird noch unübersichtlicher, weil die konkrete Implementierung dieser Mechanismen von Gerätehersteller zu Gerätehersteller unterschiedlich erfolgt.
Aus Gründen der Anwenderfreundlichkeit sollte man sicher stellen, dass der Anwender weiß, warum eine Benachrichtigung in Zusammenhang mit einen WakeLock und einem ForegroundService durchaus sinnvoll ist und ihm die Möglichkeit geben, Benachrichtigungen zu erlauben.
In den neuesten Android-Versionen ist dies nicht mehr ausreichend. Die App braucht die zusätzliche Erlaubnis, im Hintergrund aktiv zu bleiben. Man muss also den Anwender auffordern eine entsprechende Berechtigung zu erteilen. Er kann die Berechtigung später auch wieder entziehen.
In der ein oder anderen Anwendung sollen die lfd. Sensordaten versendet werden. Damit Android die WiFi-Funktionen im Hintergrund-Modus nicht abschaltet, muss ein WiFiLock angefordert werden.
Damit wäre für Android alles bereit, um weiterhin Sensordaten zu erhalten. Leider macht da App-Inventor einen Strich durch die Rechnung. Die Sensor-Komponenten sind so programmiert, dass sie sich automatisch abschalten, wenn die App aus dem Vordergrund verdrängt wird :-(
Die Android-Ereignisse onPause und onResume (siehe Aktivitätslebenszyklus) werden ausgenutzt, um die Sensoren ab- und anzuschalten:
Um Sensordaten zu erhalten, wenn die App im Hintergrund ausgeführt wird oder wenn der Bildschirm ausgeschaltet wird, ist es notwendig einen WakeLock anzufordern, in Kombination mit der Einrichtung eines ForegroundService.
Der App-Inventor-Companion besitzt leider nicht die Berechtigungen, einen WakeLock anzufordern oder einen ForegroundService einzurichten. Dazu muss die App kompiliert werden. Das ist sehr umständlich, wenn man i.W. die Sensorfunktionen testen will. Der ForegroundService wird deshalb nur auf Anforderung durch die Funktion StartForegroundService angelegt. Mit dem ForegroundService wird auch gleichzeitig ein PARTIAL_WAKE_LOCK angefordert. Der ForegroundService kann über die Funktion StopForegroundService wieder gestoppt werden.
Ob zusätzlich ein WiFiLock angefordert werden soll, kann über die Eigenschaft AquireWiFiLock eingestellt werden.
Wenn der ForegroundService gestartet wurde, wird dies über das Ereignis AfterServiceStarted angezeigt.
Bei neueren Geräten reicht es nicht aus, einen ForegroundService in Kombination mit einem WakeLock anzulegen. Es muss zusätzlich vom Anwender die Berechtigung zur Ausführung im Hintergrund erteilt werden. Über die Eigenschaft IsIgnoringBatteryOptimizations kann abgefragt werden, ob diese Berechtigung bereits erteilt wurde. Sie liefert true, wenn die Ausführung im Hintergrund möglich ist.
Der Anwender kann über die Methode RequestIgnoreBatteryOptimizations aufgefordert werden, die Berechtigung zur Hintergrundaktivität zu erteilen. Das Ergebnis der Aufforderung wird im Ereignis OnBackgroundPermissionResponse zurück geliefert. Der Companion besitzt nicht die Berechtigung die Zulassung der Hintergrundaktivität zu erfragen. Dies funktioniert nur in der kompilierten App.
Ob die App im Companion läuft, also kein ForegroundService, kein WakeLock und keine Abfrage zur Hintergrundaktivität möglich ist, kann über die Eigenschaft IsRunningInCompanion abgefragt werden.
Sollen Daten über das Netz versendet werden, ist es ggf. notwendig einen WiFiLock anzufordern, damit sichergestellt wird, dass der WiFi-Komponente nicht abgeschaltet wird. Wenn die Eigenschaft AquireWifiLock beim Start des ForegroundService gesetzt, wird zusätzlich ein WiFiLock angefordert.
Hinweis: Im Zuge der Android-Weiterentwicklung hat sich die Implementierung von Benachrichtigungen mehrfach erheblich geändert. Außerdem gibt es Abhängigkeiten vom Gerätemodell.
Der ForegroundService muss zwingend (und auch sinnvollerweise) mit einer Benachrichtigung (Notification) versehen sein. Diese wird bei der Einrichtung des ForegroundService angelegt.
In neueren Android-Versionen (ab Android 14?) sind Benachrichtigungen nicht automatisch aktiviert, bzw. können vom Benutzer deaktiviert werden. Dies gilt auch für die Benachrichtigungen, die an einen ForegroundService gebunden sind. Der ForegroundService zur Sensordatenermittlung ist i.d.R. mit einem WakeLock verbunden, der verhindert, dass die CPU ausgeschaltet wird. Somit werden auch dann Sensordaten geliefert, wenn der Bildschirm des Smartphones ausgeschaltet wird. Das ist m.E. ein unglückliche Entwicklung, da fortlaufend der Akku belastet wird, ohne dass der Anwender davon etwas erfährt.
Ob der Anwender die Berechtigung zur Anzeige von Benachrichtigung für die App erteilt hat, kann über die Eigenschaft HasPostPermission abgefragt werden. Diese fragt aber nur die generelle Berechtigung ab, ob die App Benachrichtigungen anzeigen darf. Der Anwender kann immer noch die Berechtigung für einen speziellen Kanal entziehen. Hier habe ich leider nicht herausgefunden, wie man das Anfragen kann.
Die Extension kann die Berechtigung, Benachrichtigungen zu senden, über die Funktion RequestNotificationPermission anfordern. Das Ergebnis wird über das Ereignis OnPermissionResponse zurück geliefert. In der Anleitung zu der App sollte unbedingt auf den Grund der Abfrage hingewiesen werden. Das Beispielprojekt zeigt, wie so etwas funktionieren könnte.
Kanalspezifische Berechtigungsvergabe für die Beispiel-App bei einem Motorola g15, deutsche Version. Obwohl die allgemeine Berechtigung zur Anzeige von Benachrichtigungen erteilt wurde, die Berechtigung für den Kanal mit dem Namen URS Sensor Test verweigert worden. In der Test-App wird dieser Kanal für die Benachrichtigung des ForegroundService benutzt.
Ab Android 15 (?) kann der Anwender auch dann Benachrichtigungen löschen, wenn sie mit einem ForegroundService verbunden sind. Dies hat keinen Einfluss auf die Funktion der Services. Die App läuft auch im Hintergrund weiter. Wenn die Eigenschaft NotificationDeletable auf false gesetzt ist, generiert die Extension eine neue Benachrichtigung, wenn sie gelöscht wurde.
Eine Benachrichtigung muss einem Benachrichtigungskanal (NotificationChannel) zugeordnet werden. Dieser wird bereits bei der ersten Instanziierung der Extension angelegt, d.h. beim ersten Start der kompilierten App mit dieser Extension. Beim Betrieb im Companion werden kein Benachrichtigungskanal und demzufolge auch keine Benachrichtigung angelegt.
Jeder Benachrichtigungskanal besitzt eine eindeutige Kennung, die zur Android-internen Identifizierung des Kanals dient. Diese Kennung (Eigenschaft ChannelID) wird durch die Extension festgelegt und kann nicht nachträglich geändert werden.
Ebenfalls wird die Bedeutsamkeit der Kanals (Eigenschaft ChannelImportance) bei der ersten Instanziierung der Extension festgelegt und kann nicht nachträglich geändert werden.
Die Bezeichnung des Kanals (Eigenschaft ChannelName) und die Kanalbeschreibung (Eigenschaft ChannelDescription) können nachträglich geändert werden.
Der Benachrichtigungston wird explizit abgeschaltet.
Ob die App im Companion läuft, also keine Benachrichtigung vorgesehen ist, kann über die Eigenschaft IsRunningInCompanion abgefragt werden.
Die Benachrichtigung (Notification) wird angezeigt, wenn der ForegroundService gestartet wird und wieder gelöscht, wenn der Service gestoppt wird. Ob im konkreten Fall eine Benachrichtigung angezeigt wird, hängt davon ab, ob die App die dazu notwendigen Berechtigungen besitzt (siehe auch Abschnitt Berechtigung für Benachrichtigungen).
Jede Benachrichtigung besitzt eine eindeutige ID-Nummer. Diese wird von der Extension festgelegt und kann über die Eigenschaft NotificationID abgefragt werden.
Die Gestaltung der Benachrichtigung geschieht über die Eigenschaften NotificationTitle, NotificationText, NotificationIcon bzw. NotificationIconAsset, NotificationLargeIcon. Hinzu kommen evtl. bis zu drei Aktionsschaltflächen, die über die Eigenschaften Action1Title, Action2Title und Action3Title definiert werden. Es werden nur die Schaltflächen angezeigt, zu denen ein Wert angegeben wurde.
Das Icon für die Benachrichtigung wird über die Eigenschaft NotificationIconAsset festgelegt. Dieses Icon wird auch in der Statusleiste angezeigt. Es sollte zweifarbig sein (Hintergrund transparent und Grafik in weiß) in der Größe 96x96 Pixel, z.B.:
Falls kein eigenes Icon entworfen werden soll, kann über die Eigenschaft NotificationIcon eines der System-Icons angezeigt werden. NotificationIconAsset hat Vorrang vor NotificationIcon.
Mit NotificationDeletable kann man steuern, ob der Anwender die Benachrichtigung löschen darf. Das Verhalten der Benachrichtigungen hat sich im Laufe der verschiedenen Android-Versionen deutlich verändert. Außerdem besteht eine Abhängigkeit von der konkreten Implementierung von Android durch die Hersteller (siehe auch Abschnitt Berechtigung für Benachrichtigungen). Je nach Android-Version ist das Löschen einer Benachrichtigung, die mit einem ForegroundService verbunden ist, nicht möglich, kann über NotificationDeletable verboten werden oder bewirkt, dass die Benachrichtigung nach dem Löschen neu erzeugt wird. Voraussetzung ist, dass der Anwender in den neueren Android-Versionen die Berechtigung zum Senden von Benachrichtigungen erteilt hat. In den neueren Versionen wird je nach Stellung von NotificationDeletable die Benachrichtigung neu erzeugt oder das Ereignis OnDeleteNotification ausgelöst.
Mit der Eigenschaft ScreenToOpen wird festgelegt, was beim Antippen der Benachrichtigung geschehen soll. Wenn der Wert leer bleibt, passiert nichts, wenn die Benachrichtigung angetippt wird. Ansonsten wird der angegebene Screen geöffnet. Wenn der Screen neu geöffnet wird, kann über die Eigenschaft StartValue ein Startwert mitgegeben wird, der über die Methode Control.get start value abgefragt werden kann.
Jede Benachrichtigung kann mit bis zu drei Aktionsschaltflächen versehen werden. Diese werden durch die Eigenschaften Action1Title, Action2Title und Action3Title angefordert und definiert. Wird eine dieser Schaltflächen angetippt, wird das Ereignis OnActionClick ausgelöst. Die dabei übergebene Aktionsnummer bezieht sich auf die Nummer in Action?Title.
Wie oben beschrieben schalten die Sensorkomponenten den Empfang von Sensorereignissen ab, wenn die App in den Hintergrund verdrängt wird. Die Komponenten registrieren sich während der Initialisierung beim übergeordneten Screen (Klasse Form) für den Empfang der Aktivitätszyklusereignisse onPause und onResume. Diese Ereignisse werden dann in der Sensorkomponente dazu verwandt, den Empfang von Sensorereignissen an- und abzuschalten.
Die Form-Klasse führt eine Liste aller für die Aktivitätszyklusereignisse registrierten Komponenten. Empfängt sie eines der genannten Ereignisse leitet sie diese an alle Elemente der Liste weiter. Die Methode RemoveListeners entfernt die angegebene Sensorkomponente wieder aus der Liste. Damit werden die relevanten Ereignisse nicht mehr weiter geleitet und der Empfang der Sensorereignisse bleibt aktiviert.
Unter Umständen ist es wichtig zu wissen, in welchem (Lebenszyklus-) Zustand sich die App befindet. Deshalb sind einige Ereignisse herausgeführt, die Änderungen des Zustands anzeigen. Diese sind:
Sensordaten werden mit einer hohen Frequenz (i.d.R. 50 Hz) angeliefert und sind häufig verrauscht. Die Komponente UrsSensorAverager mittelt die Werte die von Android gelieferten Werte.
Im Designer wird mit der Eigenschaft Source festgelegt, von welchem Sensor die Daten gemittelt werden sollen. Die Eigenschaft DataSourceKey legt fest, welcher der Werte gemittelt werden soll. Die Eigenschaft CacheSize gibt an, über wie viele Werte gemittelt werden soll. Übersteigt die Anzahl der gespeicherten Werte den Wert von CacheSize wird der älteste Wert verworfen.
Mit der im Folgenden gezeigten Einstellung werden die letzten fünf Werte X-Werte des AccelerometerSensor gemittelt.
Es stehen zwei verschiedene Mittelwerte zur Verfügung. Die Eigenschaft Average liefert das arithmetische Mittel. WeightedAverage liefert ein gewichtetes Mittel, bei dem die jüngsten Werte am stärksten und die ältesten nur schwach gewichtet werden. Sind fünf Werte im Cache (V1 (älteste Wert), V2 .. V5 (jüngster Wert) wird wie folgt berechnet:
Result = (V1 * 1 + V2 * 2 + .. + V5 * 5) / (1 + 2 + .. + 5)
Die Eigenschaft Count gibt an, in wie weit der Cache gefüllt. Nach einer Anlaufzeit hat Count den Wert von CacheSize.
An example is the tag of the TinyDB component, which identifies the value.
The property is a Designer-only property, and should be changed after setting the Source component of the Chart Data component.
A complete list of applicable values for each compatible source is as follows:
Nicht jede Sensorkomponente kann in die oben beschriebenen eingebunden werden. Die möglichen Sensortypen werden (vermutlich) hart gefiltert. Die Komponente UrsAverager bietet die gleichen Mittelwertfunktionen wie UrsSensorAverager, kann aber mit beliebigen Werten bestückt werden. Zur Mittelung von Sensorwerten sollte unbedingt eine Instanz von UrsSensorAverager benutzt werden. Die Übernahme wird intern geregelt. Bei der Verwendung von UrsAverager müssen die Daten über den aufwändigen App-Inventor-Ereignismechanismus geschleust werden.
Die Eigenschaft CacheSize gibt an, über wie viele Werte gemittelt werden soll. Mit der Methode Add werden neue Werte dem Cache hinzugefügt. Übersteigt die Anzahl der gespeicherten Werte den Wert von CacheSize wird der älteste Wert verworfen.
Es stehen zwei verschiedene Mittelwerte zur Verfügung. Die Eigenschaft Average liefert das arithmetische Mittel. WeightedAverage liefert ein gewichtetes Mittel, bei dem die jüngsten Werte am stärksten und die ältesten nur schwach gewichtet werden. Sind fünf Werte im Cache (V1 (älteste Wert), V2 .. V5 (jüngster Wert) wird wie folgt berechnet:
Result = (V1 * 1 + V2 * 2 + .. + V5 * 5) / (1 + 2 + .. + 5)
Die Eigenschaft Count gibt an, in wie weit der Cache gefüllt. Nach einer Anlaufzeit hat Count den Wert von CacheSize.
![]() |
Beispielprojekt |
Das Beispielprojekt versucht alle wesentlichen Features der Extension darzustellen. Es sind drei Screens implementiert:
Screen1 besteht aus mehreren Bereichen. Zum besseren Überblick sind diese farblich hinterlegt.
Angezeigt werden: ein evtl. Startwert, der SDK-Level der laufenden Android-Version, die Version der Extension und der Werte der Eigenschaften AreNotificationsEnabled und IsIgnoringBatteryOptimizations Die Werte werden im Ereignis Screen1.Initialize ermittelt. AreNotificationsEnabled und IsIgnoringBatteryOptimizations werden im Ereignis OnNotificationPermissionResponse bzw. OnBackgroundPermissionResponse angepasst.
Damit die App wie vorgesehen funktioniert, sind zwei Berechtigungen erforderlich: die Berechtigung, Benachrichtigungen zu erstellen, und die Berechtigung, im Hintergrund aktiv zu sein.
Diese Bereiche werden im nicht im Companion angezeigt, da dort kein ForegroundService angelegt werden kann und auch die Berechtigung zur Hintergrundaktivität nicht erteilt werden kann. In der kompilierten App werden sie angezeigt, wenn die entsprechende Berechtigung nicht erteilt sind.
Der angezeigten Texte lauten vollständig "You must grant permission to post notifications for the app to work correctly." und "You must grant permission to operate in the background for the app to work correctly.".
Über die Schaltfläche cmdRequestNotificationPermission (Text: Request Notification Permission) wird über die Funktion RequestNotificationPermission ein Dialog zur Erteilung der Berechtigung zum Senden von Benachrichtigungen geöffnet. Die Schaltfläche cmdRequestBackgroundPermission (Text: Request Background Permission) ruft die Funktion RequestIgnoreBatteryOptimizations auf. Hierdurch wird ein Dialog geöffnet, der die Berechtigung für Hintergrundaktivitäten erfragt:
![]() |
![]() |
Beide Dialoge stammen von einem Motorola g15 mit Android 15.
Wenn eine Berechtigung erteilt wurde, wird der entsprechende Bereich unsichtbar geschaltet. Sind beide Berechtigungen erteilt, erscheint der Bereich zum Starten des ForegroundService.
Dieser Bereich steht im Companion nicht zur Verfügung, da dort kein ForegroundService angefordert werden kann. In der kompilierten App erscheint er nur, wenn die Berechtigung zum Senden von Benachrichtigungen erteilt wurde.
Man kann den ForegroundService starten, inkl. der oben gezeigten Benachrichtigung und WakeLock, und auch wieder stoppen. Bei laufendem ForegroundService wird die CPU nicht abgeschaltet und die Sensoren liefern auch dann Ergebnisse, wenn die App in den Hintergrund gelangt oder der Bildschirm ausgeschaltet wird. Um das zu verifizieren, werden über den Timer tmReadAcc bei gestartetem Beschleunigungssensor dessen Daten per UDP an die Broadcast-Adresse 255.255.255.255, Port 2222 gesendet.
Das Verhalten der Benachrichtigung kann angepasst werden. Wenn die Benachrichtigung angezeigt wird, kann über die Schaltfläche cmdAlterNotification (Alter Notification Title) die Überschrift der Benachrichtigung in "Altered Title" abgeändert. Über die Schaltfläche cmdAlterChannel kann gezeigt werden, dass man den Namen des Benachrichtigungskanals nachträglich ändern kann. Der neue Name ist dann "New Channel".
Wird eine der Aktionsschaltflächen in der Benachrichtigung angetippt, wird dessen Nummer kurz im Label lblActionButton angezeigt. Über den Timer tmClearActionMsg wird die Anzeige nach drei Sekunden wieder gelöscht.
In diesem Bereich werden die Daten des Beschleunigungssensors angezeigt. Wenn der Sensor gestartet wurde, die Messdaten des Ereignisses SensorChanged in der eingestellten Rate im Label lblFromEventAcc angezeigt. Außerdem wird, gesteuert über den Timer tmReadAcc, die aktuellen Messwerte im Sekundentakt über die Funktion GetValues abgerufen und im Label lblFromReadingAcc angezeigt. Zusätzlich werden die Daten per UDP an die Broadcast-Adresse 255.255.255.255, Port 2222 gesendet.
Zusätzlich werden im Sekundentakt gemittelte X-Werte des Beschleunigungssensors über eine Instanz von UrsSensorAverager abgerufen und angezeigt.
Der Magnetfeldsensor funktioniert ähnlich wie der Beschleunigungssensor. Leider ist die Benutzung der UrsSensorAverager-Komponente nicht möglich (durch den App Inventor nicht vorgesehen. Hier muss zur Glättung auf die UrsAverager-Komponente zurück gegriffen werden. Im Ereignis MagneticChanged werden die gemeldeten xStrength-Werte an eine UrsSensorAverager-Komponente übergeben.
Zur besseren Übersicht sind die Funktionsblöcke nach Aufgaben sortiert:
Für die Erstellung einer eigenen Extension habe ich einige Tipps zusammengestellt: AI2 FAQ: Extensions entwickeln.