Kontynuacja poprzedniego odcinka.
Serwisy - co jeszcze o nich należy wiedzieć? Możemy wyróżnić wśród nich serwisy na żądanie, które działają tylko wtedy, gdy ktoś zleca im coś do przetwarzania. Ostatnio narzekałem na skomplikowaną dość implementację serwisu, teraz mogę powiedzieć że dzięki klasie IntentService przynajmniej pisanie serwisów na żądanie może być proste. Serwisy długo wykonujące mogą z kolei działać praktycznie przez cały czas i służyć np. do monitorowania. Jedynie ten przykład, gdzie dla zamknięcia serwisu wywoływana jest metoda onStartCommand z akcją zamknięcia wydaje się przekombinowany, ale chyba przy takim API nie dało się inaczej. Korzystanie z jednego serwisu przez różne aplikacje może być sporym udogodnieniem dla twórców aplikacji. Świadomie musimy określić to w manifeście, więc może nie oznacza to zmniejszenia bezpieczeństwa. W Windows/Windows Phone nie ma takiej możliwości, serwisy trzecie są związane tylko z daną aplikacją (każda aplikacja trzymana jest w izolowanym kontenerze) i nie wykonują się ciągle przez praktycznie nieokreślony czas (oszczędność zasobów).
Broadcast receivery - wspominałem o tym już wcześniej, to jest częściowo odpowiednik triggerów i krótko wykonujących się tasków Windows/Windows Phone, przy czym mogą być używane w aktywnościach powiązanych z UI, nie tylko z serwisami. Mogą jawnie odwoływać się do metod aktywności wpływających na interfejs użytkownika. W Windows/Windows do obsługi części zdarzeń systemowych w aplikacji mamy po prostu API z odpowiednimi zwykłymi zdarzeniami. W Androidzie mogą podobać się spore możliwości emulacji obejmujące zmianę sposobu zasilania czy stanu naładowania baterii. Istnieje możliwość automatycznego uruchamiania broadcast receiver-ów na podstawie manifestu. Często są używane do sterowania serwisami, które wykonują dłużej trwające prace. Sterowanie taskami w zależności od zdarzeń systemowych jest także bardzo popularne w Windows/Windows Phone.
Notyfikacje - notyfikacje w Android, przynajmniej na ile je poznałem, to ikona na pasku systemowym, która po dotknięciu może wykonać jakąś akcję, z reguły coś otworzyć (w Windows/Windows Phone notyfikacja toast/tile pochodząca od danej aplikacji zawsze w niej cos otwiera, notyfikacje raw mogą wykonać dowolnego taska w tle). Pasek z notyfikacjami możemy rozwinąć w dół, otrzymując bardziej szczegółowy widok listy notyfikacji otrzymanych przez system z różnych aplikacji. To jakby centrum akcji z Windows Phone 8.1. Przy czym w Android toast messages nie są uważane za rodzaj notyfikacji. To osobny niezależny byt, jakby coś pośredniego między notyfikacją toast a tile z Windows/Windows Phone, a przez dowolność akcji mogący także przypominać notyfikację raw.
Serwisy cz.2
Serwisy na żądanie
Prostsza implementacja
- Android uruchamia serwis przy pierwszym żądaniu
- Serwis pozostaje uruchomiony tak długo jak przetwarzane są zgłoszenia
- Serwis jest zamykany jak tylko zakończy się przetwarzanie
Android dostarcza identyfikator dla każdego żądania
- onStartCommand otrzymuje unikalne startId z każdym requestem
- o zakończeniu requestu informujemy wołając stopSelfResult
- przekazujemy startId żądania, które chcemy zakończyć
- jeśli pojawią się dodatkowe żądania, serwis pozostanie uruchomiony
- jeśli startId pochodzi z ostatniego żądania, serwis zostanie zamknięty
Android może zabić przedwcześnie serwis, jeśli będzie potrzebował zasobów
- serwis może później zostać ponownie uruchomiony, jeśli zasoby będą dostępne
- onStartCommand zwraca wartości wskazujące na potrzeby restartu
- START_REDELIVER_INTENT - restartuj serwis i dostarcz ponownie ostatni Intent
- START_STICKY - restartuj serwis bez Intentu
- START_NOT_STICKY – nie restartuj serwisu
Serwis na żądanie - implementacja
public class MyService extends Service {
…
@Override
public void onCreate() {
mHandlerThread = new HandlerThread(“MyService”);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
Intent intent = (Intent) msg.obj;
int startId = msg.arg1;
doWork(intent);
stopSelfResult(startId);
}
};
}
…
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Message msg = mHandler.obtainMessage();
msg.obj = intent;
msg.arg1 = startId;
msg.sendToTarget();
return 0;
}
}
IntentService enkapsuluje serwisy na żądanie
- ustawia HandlerThread i przekazywanie pracy do tego wątku
- obsługuje cykl życiowy
- dostarcza implementację onCreate, onDestroy i onStartCommand
- do kontroli zachowania podczas restartu używa metody setIntentRedelivery
- true - powoduje, że onStartCommand zwraca START_REDELIVER_INTENT
- false - powoduje, że onStartCommand zwraca START_NOT_STICKY
- sygnalizuje zakończenie każdego żądania
- aby wykonywać pracę nadpisujemy onHandleIntent
- otrzymuje instancję Intent, która została przekazana do onStartCommand
- wykonuje się w wątku w tle
- dostarczamy domyślny konstruktor, który wywołuje konstruktor klasy bazowej i przekazuje napis do nadania nazwy wątku
public class MyIntentService extends IntentService {
public IBinder onBind(Intent intent) {
return null;
}
@Override
protected void onHandleIntent(Intent intent) {
String messageText = intent.getStringExtra(“MessageText”);
FileOutputStream outStream = FileHelper.openOutStream(this, “xxx.dat”);
for (int i = 0; i < 10, i++) {
FileHelper.slowWrite(outStream, messageText);
}
FileHelper.closeOutStream(outStream);
}
public MyIntentService() {
super(“MyIntentService”);
}
}
Serwisy długo wykonujące
Czas życia serwisu nie musi być powiązany z zadaniami
- raz uruchomiony może wykonywać się przez czas nieokreślony (wykonuje się dopóki pozwolą na to zasoby lub przyjdzie żądanie zatrzymania - na skutek zaistniałych potrzeb lub jawne)
- użyteczne dla monitoringu, wstępnych pobierań itp.
- zatrzymywanie długo wykonującego się serwisu
- sam może natychmiast się zatrzymać przez wywołanie metody stopSelf bez parametrów
- może być zatrzymany z zewnątrz przez wywołanie Context.stopService z odpowiednim Intent-em
public class MonitoringService extends Service {
public static String START_ACTION = “com.xx.yyy.START_MONITORING”;
public static String STOP_ACTION = “com.xx.yyy.STOP_MONITORING”;
HandlerThread mHandlerThread;
LocationListener mLocationListener = null;
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
mHandlerThread = new HandlerThread(“MyService”);
mHandlerThread.start();
}
@Override
public void onDestroy() {
mHandlerThread.quit();
mHandlerThread = null;
mHandler = null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String action = intent.getAction();
if (START_ACTION.equalsIgnoreCase(action)) {
startMonitoring();
} else if (STOP_ACTION.equalsIgnoreCase(action)) {
stopMonitoring();
stopSelf();
}
return START_REDELIVER_INTENT;
}
private void startMonitoring() {
if (mLocationListener == null) {
LocationManager lm = (LocationManager) getSystemService(LOCATION_SERVICE);
mLocationListener = new MyLocationListener();
lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 10000, 0,
mLocationListener, mHandlerThread.getLooper());
}
}
private void stopMonitoring() {
if (mLocationListener != null) {
LocationManager lm = (LocationManager) getSystemService(LOCATION_SERVICE);
lm.removeUpdates(mLocationListener);
mLocationListener = null;
}
}
}
//start
Intent intent = new Intent(this, MonitoringService.class);
intent.setAction(MonitoringService.START_ACTION);
startService(intent);
//stop
Intent intent = new Intent(this, MonitoringService.class);
intent.setAction(MonitoringService.STOP_ACTION);
startService(intent);
Serwisy dostępne dla innych procesów
Serwisy mogą być dostępne dla innych procesów na urządzeniu
- w tym celu używamy niejawny Intent (Action, Data, Category)
- Service zawiera w manifeście Intent Filter (takie same zasady jak przy dostępie do aktywności)
- Service wykonuje się w procesie aplikacji, w której został zdefiniowany
<service android:name=”.MonitoringService”>
<intent-filter>
<action android:name=”com.xx.yyy.START_MONITORING”/>
<action android:name=”com.xx.yyy.STOP_MONITORING”/>
</intent-filter>
</service>
//inna aplikacja, w innym procesie
Intent intent = new Intent();
intent.setAction(”com.xx.yyy.START_MONITORING”);
startService(intent);
Przechwytywanie systemowych notyfikacji za pomocą broadcast receiver-ów
Android dostarcza systemowe notyfikacje z rozgłaszaniem
- Użyteczne do monitorowania zmian w stanie systemu
- przełączanie z baterii na zewnętrzne zasilanie
- wyłączenie / włączenie trybu airplane
- zmiany w statusie sieci
- …
- Notyfikacje są wysyłane jako instancje Intent
- Akcja Intent identyfikuje typ rozgłaszania
- Dodatkowe informacje często zawarte są w extras
- Muszą być przechwytywane przez specjalne komponenty zwane broadcast receiver-ami
Broadcast receivers
- jedyny sposób na odbieranie broadcast-ów
- muszą dziedziczyć po BroadcastReceiver
- nadpisujemy metodę onReceive (otrzymujemy Context i Intent)
- musi nastąpić wyjście z onReceive do 10 sekund
public class MyReceiver extends BroadcastReceiver {
public void onReceive(Context c, Intent i) {
…
}
}
Przechwytywanie broadcastów
Używamy Context.registerReceiver do wiązania receivera z broadcastem
- dostarczamy referencję do instancji receivera
- dostarczamy IntentFilter z odpowiednią akcją
- Intent.ACTION_BATTERY_CHANGED
- Intent.ACTION_AIRPLANE_MODE_CHANGED
- …
- Używamy Context.unregisterReceiver, gdy nie chcemy już dłużej słuchać
public class BatteryStatus {
public final int level;
…
public BatteryStatus(Intent intent) {
level = intent.getIntExtra(“level”, -1);
…
}
}
Android Studio
- New Android Studio –> Broadcast Receiver
public class BatteryLogReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
BatteryStatus batteryStatus = new BatteryStatus(intent);
…
}
}
public class MainActivity extends Activity {
BatteryLogReceiver mBatteryLogReceiver = null;
…
private void handleActionStartBatteryLog(MenuItem item) {
if (mBatteryLogReceiver == null) {
mBatteryLogReceiver = new BatteryLogReceiver();
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
registerReceiver(mBatteryLogReceiver, intentFilter);
}
}
private void handleActionStopBatteryLog(MenuItem item) {
if (mBatteryReceiver != null) {
unregisterReceiver(mBatteryLogReceiver);
mBatteryLogReceiver = null;
}
}
}
Sticky broadcasts – zawsze wysyłają broadcast do nowo zarejestrowanych odbiorców
Standardowe nie wysyłają nic po rejestracji, tylko jeśli nastąpi zmiana stanu.
Konsola Telnet - symulacja odłączenia od sieci, zmiany poziomu naładowania baterii
open localhost port_emulatora
power ac off
power capacity 75
Broadcasty i aktywności
Aktywności często potrzebują odpowiedzieć na informacje w broadcastach
- potrzeba utworzenia asocjacji pomiędzy broadcast receiverem a aktywnością
- można przekazać referencję aktywności do broadcast receivera
- często klasy zagnieżdżone są prostszym rozwiązaniem (trzymają referencję do instancji otaczającej klasy)
- Rejestrujemy odbiorcę w onResume aktywności
- Odpinamy odbiorcę w onPause aktywności
public class MainActivity extends Activity {
…
BatteryDisplayReceiver mDisplayReceiver = null;
…
@Override
protected void onResume() {
super.onResume();
mDisplayReceiver = new BatteryDisplayReceiver();
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
registerReceiver(mDisplayReceiver, intentFilter);
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(mDisplayReceiver);
}
…
private void setPowerDisplay(boolean onAC, …) {
…
}
…
private class BatteryDisplayReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
int level = intent.getIntExtra(“level”, –1);
int plugged = intent.getIntExtra(“plugged”, –1);
boolean onAC = plugged == BatteryManager.BATTERY_PLUGGED_AC;
…
setPowerDisplay(onAC, …);
}
}
}
Automatyczne wykonywanie broadcast receiver-ów
Mogą być statycznie rejestrowane
- Umieszczamy Intent Filter dla broadcast receivera w manifeście
- Android utworzy instancję broadcast receivera
- uruchomi powiązany proces, jeśli aktualnie nie jest wykonywany
- Android wymusza bardzo surowe czasowe restrykcje
- metoda onReceive musi zakończyć się do 10 sekund
- Component jest uważany za niepoprawny, gdy tylko onReceive powraca
- Często powiązane z serwisami
- Informują serwisy o zmianach
- Używają serwisów do wykonywania każdych powiązanych dłużej trwających operacji
<receiver android:name=”.MyReceiver”>
<intent-filter>
<action … />
</intent-filter>
</receiver>
public class MonitoringService extends Service {
…
}
public class ManageLocationListenerReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) {
boolean isOn = intent.getBooleanExtra(“state”, false);
String serviceAction = isOn ? MonitoringService.STOP_ACTION : MonitoringService.START_ACTION;
Intent serviceIntent = new Intent(serviceAction);
context.startService(serviceIntent);
}
}
}
<receiver android:name=”.ManageLocationListenerReceiver”>
<intent-filter>
<action android:name=”android.intent.action.AIRPLANE_MODE” />
</intent-filter>
</receiver>
Wyświetlanie notyfikacji
Wady i zalety dialogów i powiadomień toast
- message dialog: widoczny, ale jest zbyt nachalny
- toast message: nie narzuca się, ale użytkownik może przegapić
Notyfikacje łączą wymagania
- nie są nachalne - na górze ekranu (na pasku systemowym) pokazuje się ikona
- użytkownik zobaczy - możliwość rozwinięcia w dół szczegółów
- mogą być wyświetlane przez aplikacje w tle
Budowanie notyfikacji
Używamy klasy Notification.Builder
- dla API Level < 11 używamy NotificationCompat.Builder
- mała ikona: setSmallIcon
- tytuł: setContentTitle
- tekst: setContentText
- pobieranie zbudowanej instancji Notification
- Notification.Builder < API 16: getNotification
- Notification.Builder >= API 16: build
- NotificationCompat.Builder: build
Wyświetlanie notyfikacji
NotificationManager dostarcza dostęp do systemu notyfikacji
- NotificationManager jest systemowym serwisem
- używamy Context.getSystemService z Context.NOTIFICATION_SERVICE
- do wyświetlenia notyfikacji używamy metody notify
- na instancji Notification
- dostarczamy zdefiniowane przez aplikację całkowite id
- opcjonalnie dołączamy tag dla zwiększenia pewności id
- możliwość aktualizacji notyfikacji z wywołaniem notify z istniejącym id/tagiem
- usuwamy notyfikację przez cancel
public class MyLocationListener implements LocationListener {
…
@Override
public void onLocationChanged(Location location) {
…
MyNotificationHelper.displayNotification(mContext, location);
}
}
public class MyNotificationHelper {
public static final int LOCATION_NOTIFICATION_ID = 1;
public static void displayNotification(Context context, Location location) {
…
Notification.Builder builder = new Notification.Builder(context);
builder.setContentTitle(“XXX”)
.setContentText(“message”)
.setSmallIcon(R.drawable.icon24x24);
Notification notification = builder.build();
NotificationManager mgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
//String tag = this.getClass().getName();
mgr.notify(LOCATION_NOTIFICATION_ID, notification);
}
public static void removeNotification(Context context) {
NotificationManager mgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
mgr.cancel(LOCATION_NOTIFICATION_ID);
}
}
Akcja dla notyfikacji
Akcja dla notyfikacji po naciśnięciu
- używamy setContentIntent
- wszystko, co powiązane z Intent
- najczęściej otwiera Activity
- używamy specjalnej klasy PendingIntent
- opakowuje Intent w przyszłą akcję
- używamy PendingIntent.getActivity do opakowania Activity Intent w PendingIntent
Wyświetlenie zawartości adresu po naciśnięciu
Uri uri = Uri.parse(mapUri);
Intent mapIntent = new Intent(Intent.ACTION_VIEW, uri);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, mapIntent, 0);
…
builder. …
.setContentIntent(pendingIntent);
Brak komentarzy:
Prześlij komentarz