czwartek, 23 października 2014

Pojedynek z Androidem - odc. 15 asynchroniczne programowanie i serwisy (mała retrospekcja)

Po wchłonięciu gorących nowości z ostatniego buildu Windows 10 przyszła pora na wydanie kolejnego odcinka poświęconemu systemowi Android. Dziś zrobię trochę rozważań opartych… na tym, co już było. Taka mała retrospekcja.

Jeśli chodzi o responsywność to filozofia przypomina w dużym stopniu tę znaną z Windows, przy czym Microsoft ma tylko asynchroniczne WinRT API i programista - choćby nawet chciał - to nie przyblokuje interfejsu np. operacją na pliku. W Androidzie trzeba być świadomym AsyncTask.  Co do sposobu jego wykonywania, to mamy historyczne zróżnicowanie (sekwencyjnie, równolegle, potem znów sekwencyjnie z możliwością jawnego wybrania). W Windows TPL / WinJS pozwalają wygodnie sterować ciągami operacji.

Serwis widziany z poziomu różnych aplikacji, procesów może być udogodnieniem dla twórców aplikacji, ale - IMHO - też i jakąś furtką dla osób o niezbyt dobrych zamiarach.

Wątki w serwisie… cóż jak dla mnie trochę przesyt różnych tworów takich jak zagnieżdżone klasy, implementacje interfejsu Runnable.  A wszystko to okraszone różnymi zmiennymi.  Zarządzanie cyklem życia serwisu obejmuje potrzebę pamiętania wyrafinowanych opcji przy wznawianiu jego pracy.

Wyświetlanie wiadomości toast z poziomu serwisu wymaga stworzenia wątku z pomocą klasy Handler i loopera. Uff… W Windows nie widzę, żeby były jakieś różnice między korzystaniem z notyfikacji toast w aplikacji i serwisie. W Android przy wysyłania notyfikacji z serwisu (serwisy foreground) też musimy postąpić nieco inaczej niż w UI.  W Windows znów nie ma różnic dla notyfikacji tile.  Android nie jest tutaj mistrzem prostoty.

Wyzwalanie serwisów na zdarzenia systemowe…  to jedna z głównych koncepcji tasków w tle na platformie Windows.  W Android implementujemy odpowiednią klasę do przechwycenia systemowego zdarzenia, a potem w jej metodzie onReceive jawnie wywołujemy serwis przez startService.  Microsoft zaenkapsulował nam taki przypadek w postaci predefiniowanych triggerów, w Androidzie możemy sobie sami to oprogramować.

 

 

Wyzwania dla responsywnego “user experience”

Responsywność

  • Musimy utrzymywać dostępność głównego wątku by przechwytywać zdarzenia
  • Przewodniki dla Android zalecają by handlery zajmowały mniej niż 200ms (preferowane jest mniej niż 100ms)
  • Komunikaty ANR pojawiają się, jeśli wątek główny zajęty będzie dłużej niż 5 sekund

Trzy strategie na podtrzymywanie responsywności

  • upewnienie się, że operacje potencjalnie blokujące nie występują w głównym wątku
    • klasa Strictmode
      • od Android 2.3 / API Level 9
      • rodzaje przechwytywanych operacji - setThreadPolicy(…)
      • sposób przechwytywania - setVmPolicy(…)
      • najczęściej używane - enableDefaults
      • tylko na czas developmentu
  • używanie dodatkowych wątków
    • użyteczne dla nie za długich, długo trwających operacji
      • od 100 ms do kilku sekund
      • wątki trwające więcej niż kilka sekund mogą być zabite przez system operacyjny
    • międzywątkowy dostęp do UI
      • Activity.runOnUiThread
      • View.post
    • stosowanie dedykowanych wątków zwykle nie jest najlepszym pomysłem na poprawę UI
      • tworzenie/niszczenie wątku jest kosztowne
      • dość złożony kod
    • lepiej stosować asynchroniczne taski specjalne dedykowane do tego rodzaju zadań – AsyncTask
      • fazy pracy
        • onPreExecute - odczytanie informacji z UI w głównym wątku
        • doBackground
        • publishProgress / onProgressUpdate
        • onPostExecute
      • lepsza wydajność niż dedykowane wątki (wykorzystanie puli wątków)
      • taski wykonujące się dłużej niż kilka sekund mogą być zabite przez OS
      • duże różnice w wykonywaniu - domyślne zachowanie puli wątków zależy od wersji systemu
        • taski wykonywane są zawsze sekwencyjnie ( Android 1.0 / API 1  -  Android 1.5 / API 3)
        • taski mogą wykonywać się równolegle (Android 1.6 / API 4  -  Android 2.2 / API 8)
        • taski wykonywane są zawsze sekwencyjnie (Android 2.3  / API 9  i nowszy)
      • od Android 3.0 / API 11 można określić sposób wykonywania taska
        • AsyncTask.executeOnExecutor
        • AsyncTask.SERIAL_EXECUTOR - sekwencyjne wykonywanie tasków
        • AsyncTask.THREAD_POOL_EXECUTOR - równoległe wykonywanie tasków
      • więcej:  odc 4
  • używanie serwisów

 

public class XActivity extends Activity  {          

       …

       private void updateDisplay(String message)  {

               _defaultTextViewTemp = message;

              _defaultTextView.post(new Runnable()  {

                      public void run()  {

                              _defaultTextView.setText(_defaultTextViewTemp);

                      }

              }

       }

}

 

_asyncTaskWorker = new AsyncTaskWorker();

_asyncTaskWorker.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, _param);

 

Implementacja długotrwających operacji jako serwisów

Operacja “długotrwająca” - na tyle długa, że użytkownik może przełączyć się na inną aplikację

W serwisie mogą być określone preferencje co do wykonywania

  • może być długo trwającą operacją w tle
  • może mieć taką samą ważność jak UI aktualnie używanej aplikacji

Kategorie serwisów

  • uruchomione (started services)
    • duża kontrola nad swoim czasem życia
    • ograniczona interakcja z innymi komponentami
    • większość serwisów
  • związane (bound services)
    • czas życia dopasowany do komponentów, które mają do niego dostęp
    • duża interakcja z komponentami, które mają do niego dostęp

IntentService

  • najprostsza implementacja (wystarczy konstruktor i nadpisanie onHandleIntent)
  • uruchamiany na żądanie (na czas przetwarzania przychodzących żądań)
  • obsługa scenariusza kolejkowania długo trwającej operacji w tle
  • tworzy pojedynczy wątek roboczy po wywołaniu startService
    • przy dodatkowych wywołaniach startService podczas wykonywania żądania są serializowane
  • wątek roboczy jest kończony po przetworzeniu wszystkich żądań
  • tworzony jest nowy wątek roboczy, jeśli pojawią się nowe żądania po zamknięciu
  • więcej: odc 5

<manifest … package=”xxx.yyy”>

         <application>

                    <service android:name=”.serviceClass” android:label=”nazwa używana w widokach dla użytkownika” />

         </application>

</manifest>

Używanie serwisów między procesami

Serwis

  • wykonuje się w procesie, w którym jest zdefiniowany
  • może być wywoływany z innych komponentów będących w jego procesie
  • może być wywoływany przez komponenty z innych procesów (z innej aplikacji)
  • może obsługiwać równocześnie żądania z różnych procesów
  • uzupełnienie do odc 5
  • aby być widoczny dla innych procesów musi definiować filtr intencji
    • zwykle test akcji, ale można także umieścić kategorię i/lub dane
    • jeśli zachodzi potrzeba proces serwisu jest uruchamiany
    • jeśli proces serwis już jest uruchomiony wykorzystywana jest jego istniejąca instancja

<service …>

         <intent-filter>

                  …

        </intent-filter>

</service>

 

Zarządzanie cyklem życia serwisu

Serwisy uruchomione (started services) mogą zarządzać swoim cyklem życia

Cykl życia w trzech metodach klasy Service

  • onCreate (każda instancja klasy dokładnie jedno wywołanie, po Context.startService jeśli nie było instancji serwisu)
  • onStartCommand  (przy wywoływaniu Context.startService)
  • onDestroy (po wywołaniu Context.stopService, stopSelf (przez sam serwis) lub przez system operacyjny ze względu na zasoby)
  • więcej:  odc 5

Started services nie używają metody onBind (w nadpisaniu zwracamy null)

Wątki w serwisach

  • wszystkie metody callback serwisu wykonują się w głównym wątku aplikacji (tym samym co UI)
  • jesteśmy odpowiedzialni za przekazanie pracy do innego wątku
    • można użyć jawnie tworzonego wątku
    • można użyć jedną z klas, która implementuje ExecutorService
      • implementacjami ExecutorService są pule wątków
      • używamy klasy Executors to tworzenia instancji ExecutorService
      • przykład: odc 13

Executors

  • newFixedThreadPool(…)
  • newScheduledThreadPool(…)
  • newSingleThreadExecutor()

public class WorkerService extends Service  {

        …

        ExecutorService _executorService;

       

        @Override

        public void onCreate()  {

                 …

                 _executorService = Executors.newSingleThreadExecutor();

        }   

 

        @Override

        public int onStartCommand(Intent intent, int flags, int startId)  {

                  _executorService.execute(new Runnable()  {

                          public void run()  {

                                  …

                          }

                 });

                 return Service.START_NOT_STICKY;

        }  

 

         …

}

Serwis ma dwa mechanizmy na zarządzanie swoim cyklem życia:

  • żądanie zatrzymania siebie
    • jawne
      • serwis wywołuje stopSelf
      • system automatycznie wywołuje onDestroy i zatrzymuje serwis
    • warunkowe
      • serwis może wskazać, że chciałby się zatrzymać jeśli nie ma oczekującej pracy
      • serwis wywołuje stopSelfResult przyjmujące numer żądania z onStartCommand
        • serwis się zatrzyma tylko wtedy, jeśli żadne żądania nie pojawią się po danym żądaniu
  • onStartCommand - zwracana wartość określa, co system powinien zrobić, jeśli proces serwisu zostanie zabity (z powodu zasobów)
    • START_NOT_STICKY - jeśli serwis został zabity, nie jest restartowany (przy większych zasobach) aż do wywołania startService
    • START_STICKY - jeśli serwis został zabity, automatycznie jest restartowany, gdy zasoby są dostępne, intencja przekazywana w onStartCommand ma wartość null
    • START_REDELIVER_INTENT - jeśli serwis został zabity przed wywołaniem stopSelfResult dla ostatniego żądania, automatycznie jest restartowany, jeśli zasoby są dostępne, przekazywana jest intencja ostatnio przekazana do serwisu zanim został zabity
    • START_STICKY_COMPATIBILITY - kompatybilność z zachowaniem serwisu z Android 1.6 / API 4 lub wcześniejszym, automatyczny restart serwisu po zabiciu, jeśli zasoby są dostępne, podczas restartu metoda onStartCommand może zostać wywołana, konieczność nadpisania przestarzałej metody onStart
  • patrz: odc 5 

public class WorkerService extends Service  {

        …   

        ExecutorService _executorService;

        ScheduledExecutorService _scheduledStopService;

       

        @Override

        public void onCreate()  {

                 …

                 _executorService = Executors.newSingleThreadExecutor();

                 _scheduledStopService = Executors.newSingleThreadScheduledExecutor();

        }   

 

        @Override

        public int onStartCommand(Intent intent, int flags, int startId)  {

                 serviceRunnable runnable = new serviceRunnable(this, startId);

                  _executorService.execute(runnable);

                 return Service.START_NOT_STICKY;

        }

 

        …

 

        class serviceRunnable implements Runnable  {

                 WorkerService _theService;

                 int _startId;

                 public serviceRunnable(WorkerService theService, int startId)  {

                          _theService = theService;

                          _startId = startId;

                 }

                 public void run()  {

                           …

                          //_theService.stopSelf();

                         // _theService.stopSelfResult(_startId);

                         // opóźnione zatrzymanie

                         delayedStopRequest stopRequest = new delayedStopRequest(_theService, _startId);

                         _theService._scheduledStopService.schedule(stopRequest, 5, TimeUnit.MINUTES);

                 }

        }

 

        class delayedStopRequest implements Runnable  {

                 WorkerService _theService;

                 int _startId;

                 public delayedStopRequest(WorkerService theService, int startId)  {

                          _theService = theService;

                          _startId = startId;

                 }

                 public void run()  {

                           _theService.stopSelfResult(_startId);

                 }

        }      

}

 

Interakcja z serwisami

Ogólnie, serwisy powinny wchodzić w minimalną interakcję z użytkownikami

  • nie należy próbować oddziaływać bezpośrednio na instancję aktywności
    • przechowywanie referencji nie jest bezpieczne
    • instancje aktywności są często odtwarzane na nowo
  • należy używać technik będących poza interakcją z użytkownikiem
    • wiadomości toast
    • notyfikacje

Wyświetlanie wiadomości toast z poziomu serwisu wymaga specjalnej obsługi

  • musi być wywoływana z wątku, który ma looper (HandlerThread)
  • pomocna jest tu klasa Handler
    • pozwala jawnie tworzyć HandlerThread
    • najłatwiejsze w użyciu Context.getMainLooper by dostać się do głównego HandlerThread z UI

public class WorkerService extends Service  {

        …

        class serviceRunnable implements Runnable  {

                WorkerService _service;                

                …

                Handler _handler;

                String _statusMessage;

                …

                public void run()  {

                      setupHandler();                    

                      …

                      updateStatus(“xxx”);

                      …

                }

                void setupHandler()  {

                       _handler = new Handler(_service.getMainLooper());

                }

                void updateStatus(String message)  {

                        _statusMessage = message;

                        _handler.post(new Runnable() {

                                public void run()  {

                                      Toast toast = Toast.makeText(_service, _statusMessage , Toast.LENGTH_LONG);

                                       toast.show();

                                }

                        });

                }

         }

}

Serwisy, które wpływają bezpośrednio na user experience

  • serwisy foreground 
    • serwis, którego użytkownik jest bezpośrednio świadomy
      • np. odtwarzacz muzyki
    • informacje serwisu pojawiają się w status bar (wyświetlanie notyfikacji przez serwis)
      • wyświetlenie ikony
      • wyświetlenie tekstu (dodatkowa informacja po rozwinięciu; napis podczas pierwszego pojawienia)
      • możliwość wyzwolenia akcji, kiedy użytkownik wchodzi w interakcję z paskiem statusu
    • serwis może się wchodzić i wychodzić z przebywania w stanie foreground
      • sam może wywołać na sobie startForeground / stopForeground
      • wywołanie stopForeground powoduje usunięcie notyfikacji z paska statusu (serwis nadal kontynuuje swoje działanie)
      • jeśli zostanie zamknięty z jakiegokolwiek powodu, system automatycznie wywołuje stopForeground
    • notyfikacja jest głównym sposobem na interakcję z użytkownikiem
      • jak wszystkie serwisy, serwis foreground nie ma interfejsu użytkownika
      • możliwość modyfikowania notyfikacji w dowolnym czasie za pomocą NotificationManager
    • więcej: odc 9

public class WorkerService extends Service  {

         …

         final int _notificationId = 1;

         Notification _foregroundNotification;

         NotificationManager _notificationManager;

         …

         @Override

         public void onCreate() {

                …

               _notificationManager = (NotificationManager)  this.getSystemService(Context.NOTIFICATION_SERVICE);

 

                startInForeground();

         }

         …

         void startInForeground()  {

                 int notificationIcon = R.drawable.icon24x24;

                 String notificationTickerText = “xxx”;

                 long notificationTimestamp = System.currentTimeMillis();

                 _foregroundNotification = new Notification(notificationIcon, notificationTickerText, notificationTimestamp);

 

                 String notificationTitleText = “yyy”;

                 String notificationBodyText = “zzz”;

                 Intent intent = new Intent(this, XActivity.class);

                 PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);

                 _foregroundNotification.setLatestEventInfo(this, notificationTitleText, notificationBodyText, pendingIntent);

 

                 startForeground(_notificationId, _foregroundNotification);

         }

         void setStatusIcon(int iconId)  {

                 _foregroundNotification.icon = iconId;

                 _notificationManager.notify(_notificationId, _foregroundNotification);

          }

         class serviceRunnable implements Runnable  {

                  WorkerService _service;

                  …

                  public void run()  {

                         _service.setStatusIcon(R.drawable.green24x24);

                         …

                         _service.setStatusIcon(R.drawable.icon24x24);

                  }

         }

}

Serwisy i systemowa aktywność

  • zamykanie przy wyłączaniu urządzenia
  • automatyczny start przy bootowaniu urządzenia
  • zatrzymywanie/uruchamianie korzystania z zasobów sieciowych jeśli urządzenie włącza/wyłącza tryb samolotowy
  • niski stan baterii
  • użytkownik podłącza/odłącza urządzenie do/z zasilania sieciowego

Sygnalizowanie zazwyczaj za pomocą broadcast intents.

Implementujemy broadcast receiver. W metodzie onReceive wywołujemy startService.

Przykład: odc 5

środa, 22 października 2014

Windows 10 Build 9860 oficjalnie już jest!

Każdy, kto ma zainstalowany Windows 10 Technical Preview, może sobie w aplikacji Settings w “Preview Builds” zlecić pobranie nowego buildu. Możliwość ta pojawiła się wczoraj w nocy. Update z chmury to jedyna opcja, plik iso nie został udostępniony.

Aktualizacja przebiegła mi dziś sprawnie.  Najpierw pobierał się build z sieci… Następnie otrzymałem modernistyczny komunikat o potrzebie restartu systemu. Potem nastąpiło właściwe wgrywanie w %, przygotowanie urządzeń w %,  konfiguracja & pc settings w % i… już zacząłem działać na 9860.  Mam wszystkie dotychczasowe skróty na biurku, dane, aplikacje, pełna wygoda. Całość zajęła kilkanaście, no może około dwudziestu paru minut łącznie z downloadem. Tego posta piszę już z nowego systemu.

 

W sieci zdążyły się pojawić filmiki o 9860. Bardzo dobry jest ten poniżej:

Informacje zbiorcze o nowym buildzie

 

NOWOŚCI

Wybór częstotliwości pobierania nowych buildów

Albo otrzymujemy częściej buildy mniej stabilne (Fast),  albo rzadziej lepiej dopracowane (Slow). Oczywiście entuzjaści, insiderzy powinni wybrać sobie opcję Fast, co też i ja uczyniłem.

Linki

Centrum notyfikacyjne

Ikonkę centrum (kilka poziomych kresek na białym prostokącie) znajdziemy w taskbar nieopodal miejsca, gdzie w Windows zazwyczaj pojawiały się “stare dymki”. Po jej kliknięciu otwiera się to, co w przeciekach, czyli proste białe okienko w stylu modernistycznym, w którym można przeglądać nieotworzone notyfikacje. W moim przypadku “na dzień dobry” czekało tam powiadomienie od Windows Feedback, które po kliknięciu wyświetliło zapytanie, jak podobają mi się aplikacje Modern na pulpicie.

Animacja podczas otwierania i zamykania okien

Okno przy otwieraniu  lekko “wybucha” swoimi rozmiarami, przy zamykaniu płynnie się natomiast “kurczy”. Tego efektu nie zauważyłem w oknach z aplikacjami Modern (które uzyskały czarne paski tytułowe!). Może lekko nawiązuje do animacji okien z OS X, ale IMHO naprawdę lekko.

Linki

Nowa animacja przy przełączaniu się między wirtualnymi pulpitami

Linki

Nowości w dotyku

Linki

Battery Saver

Kojarzy się z Windows Phone. Funkcjonalność nieukończona.

Linki

Data Sense

Podobnie jak w przypadku Battery Sense, adaptacja kolejnej funkcjonalności z Windows Phone. Również nieukończona.

Linki

Customizacja lock-screen

Linki

Przeniesienie połączeń Wi-Fi z Charms Bar do aplikacji Settings

Teraz po naciśnięciu ikonki sieciowej otwiera się nowe okno z app-ką Settings na zakładce konfiguracji sieci Ethernet / Wi-Fi.

 

Dodatkowe informacje o Windows 10

 

To na razie tyle, jeśli chodzi o nowy Windows.  Na zakończenie bonusowo w tematyce Microsoft dodam, że są coraz bardziej wyraźne pogłoski o zegarku mającym współpracować z wszystkimi mobilnymi platformami.  Komunikacja jakby tylko przez Bluetooth LE …

piątek, 17 października 2014

Pojedynek z Androidem - odc. 14 jeszcze o intencjach

Intencja to Androida esencja. Zachęcony takowym hasłem zdobyłem się na zdobycie kolejnego levelu systemu z zielonym robotem. W sumie jakiegoś dużego zaskoczenia nie było, wiele zostało już powiedziane wcześniej, ale trochę nowych informacji się pojawiło - czy to porządkujących jakieś tam zagadnienie czy odsłaniające więcej szczegółów. A więc, podsumujmy:

Intencja - twór dość ogólny, pełni funkcję zarówno jakby tasków, kontraktów, funkcjonalności (czy czasami zadeklarowanych w manifeście wręcz GUID-ów) oraz zdarzeń i triggerów. Fajnie, że wymyślono coś w miarę uniwersalnego do różnych zastosowań, stosowanego zarówno przez system operacyjny, jak i każdego dostawcę oprogramowania, a każdy może definiować sobie nowe takie byty.  W Windows/Windows Phone mamy wielopostaciowość, narzucone rodzaje komunikacji, które czasami możemy dostosowywać do swoich potrzeb np. przesyłać dane w swoim formacie, obsłużyć swój typ pliku czy protokół.  Czy to oznacza, że to gorzej? Niekoniecznie, możliwości są w miarę podobne, Windows obsługuje wszystko co powinien, a w wielu przypadkach API dzięki choćby async czy zwykłym nawet zdarzeniom wydaje się łatwiejsze i bardziej intuicyjne. 

Aktywacja komponentów - pochwalić można kontrolę nad tworzeniem instancji.  API pokazujące sposób “myślenia systemu” przy wyszukiwaniu komponentów pod dane kryteria może i niezłe, ale czy aż tak potrzebne?  

PendingIntent - jak już wiemy służy do delegowania wykonania czegoś na jakimś komponencie. Jakby taka pewna forma callbacku, w którym wywoływana jest właściwa intencja. Może się sprawdzić w takiej sytuacji jak chociażby oprogramowanie własnych przycisków na notyfikacji czy widgecie, czego jeszcze nie mamy w rodzinie Windows. Natomiast domyślne zwracanie tej samej referencji dla równych intencji (ale np. różniących się danymi) jest pułapką, na którą trudno znaleźć proste wytłumaczenie. Trzeba o tym po prostu wiedzieć, a jeśli nie o to akurat nam chodzi, to używamy jawnie wybranej flagi.

Obsługa funkcji systemowych

  • zmiany w systemie - odpowiednik jakby nawet niektórych triggerów w Windows, zdarzenia śledzące szczegółowo co się dzieje z baterią (np. jej temperaturą) czy podłączenie / odłączenie słuchawek póki co wydają się być domeną Androida
  • przechwytywanie notyfikacji po czasie ich wystąpienia - rzecz dotyczy takich specyficznych przypadków jak dokowanie czy brak miejsca w storage, wydaje się przydatne, na razie w mobilnej platformie Windows czegoś takiego nie mamy (choć są dłużej trwające notyfikacje toast przydatne np. dla aplikacji typu VoIP jak choćby Skype), ale mam tu ciekawe skojarzenia z notyfikacjami Windows 10, w którym postanowiono połączyć w jedną całość notyfikacje z WinRT z notyfikacjami pokazywanymi do tej pory na task barze. W efekcie czego jak np. odłączam sobie USB pokazuje mi się “modernistyczny” prostokąt z informacją, że mogę go już bezpiecznie odłączyć. Może idąc tym tropem doczekamy się podobnych notyfikacji np. o braku miejsca na dysku ?  A wtedy zasadne byłoby odbieranie takich informacji po czasie? Równie dobrze jednak możnaby to załatwić odczytaniem jakiegoś stanu systemowego np. przy starcie aplikacji.
  • kolejkowanie w czasie akcji do wykonania, wyświetlanie strony Web, telefonowanie, robienie zdjęć, wybieranie kontaktu z listy kontaktów - wszystko to mamy także w Windows, przy czym kilka uwag. Kolejkowanie może nie jest aż tak konfigurowalne jak w Android. Chodzi mi o kontrolowanie precyzji co do odstępów czasowych. Jeśli chodzi o budzenie urządzenia, to w Windows również jest to możliwe (Connected Standby), taką ideę spotykamy w niektórych triggerach czy alarmach (w aplikacji alarmowej). Wyświetlanie strony Web jest tak samo proste (Launcher w WinRT API albo z dawniejszych czasów task w WP). Jeśli chodzi o telefonowanie, to w Windows Phone nie ma bezpośredniego dzwonienia. Robienie zdjęć z wykorzystaniem aplikacji systemowej w obu systemach jest dość proste (bardziej nowoczesną formę i prostotę ma WinRT API Windows, w WP możemy odnaleźć jeszcze taska z dawniejszych czasów, który również jest prosty w obsłudze, choć już mniej nowoczesny, bo oparty o zdarzenie, a nie o async).  Jeśli chodzi o wybór kontaktów to mamy analogię w postaci contact pickera w WinRT (możemy jeszcze w starszym API do WP odnaleźć contact choosera).  Co ciekawe Android używa słowa “contract” w klasie zwracającej dane do intencji, a sama akcja zawiera słowo “pick” !  Na tym jednak podobieństwa się kończą, bo wyciąganie danych o kontaktach pokazane przez autora nagrania jest o niebo trudniejsze niż w API Windows, gdzie mamy obiekty klas.  W Androidzie trzeba odpytywać w niskopoziomowym stylu bazodanowym tabelki content providera, bo wyciągnąć e-mail czy numer telefonu wybranego kontaktu!  Trochę słabo.

 

Mała powtórka

Intencja

  • struktura komunikatu specjalnego przeznaczenia
    • aktywacja komponentu
    • opisuje operację lub zdarzenie
  • używana w dwóch celach
    • opis operacji do wykonania
    • opis czegoś co się zdarzyło

Opis operacji bez używania jej typu

  • Action - opis akcji lub operacji do wykonania
  • Extras - dodatkowe informacje (słownik klucz-wartość)
  • Data - URI lub typ mime danych przetwarzanych przez akcję
  • Category - identyfikuje podgrupę typu komponentu, która powinna przechwycić intencję

Android routuje intencje w oparciu o typ docelowy

  • 3 typy docelowe
    • aktywności lub serwisy
      • żądanie wykonania operacji
      • wybierany jest dokładnie jeden serwis lub aktywność
      • użytkownik może być poproszony o dokonanie wyboru
    • broadcast receivery
      • zdarzenie, które zaszło
      • wszystkie pasujące broadcast receivery otrzymują intencję
    • celem każdej intencji jest tylko jeden rodzaj komponentu
  • komponent nie musi się wykonywać by otrzymać intencję
    • package manager sprawdza komponent intencji
    • system automatycznie tworzy nową instancję komponentu, jeśli jest to potrzebne

Komponenty używają filtrów intencji do ogłaszania swoich funkcjonalności i swoich “zainteresowań”

  • IntentFilter
    • wartości składowych opisują testy na podzbiór wartości składowych intencji
    • komponent może zdefiniować wiele instancji filtrów
    • normalnie jest opisywany w manifeście
    • niektóre filtry mogą być dynamicznie rejestrowane w kodzie
  • 2 zasadnicze cele
    • określenie, które komponenty powinny otrzymać daną intencję
    • opisanie możliwości komponentu lub pożądanych przez niego funkcjonalności

Testy filtrów intencji

  • akcja – jedna lub więcej akcji, które komponent może przechwycić
  • dane – jedno lub więcej Uri/rodzaj danych  - // -
  • kategoria – jedna lub więcej podgrup, do których należy komponent

PendingIntent  opakowuje intencję w akcję, która ma być z nią wykonana

  • enkapsuluje tożsamość nadawcy i dane uwierzytelniające
  • używany do delegowania akcji na inny komponent
  • pozwala wykonywać odbiorcy akcję, jakby była pochodziła od nadawcy
  • pozwala przywrócić kontrolę nad danym zadaniem
    • odpala aktywność, kiedy użytkownik zaznacza notyfikację
    • zwraca sterowanie z powrotem do aplikacji, która przekazała sterowanie reużytkowalnemu widgetowi
    • otrzymuje wyniki z serwisu

Linki

 

Aktywacja komponentów przez intencje

Komponenty są aktywowane przez wysyłanie intencji

  • aktywności aktywowane przez startActivity
  • serwisy  - // -  przez startService
  • broadcast receivery  - // -  przez sendBroadcast
  • intencje mogą być wysyłane przez aplikacje lub system

Jawne intencje  - najprostsze to stworzenia

  • zawierają informacje o komponencie
  • zawsze ukierunkowane na instancję typu
  • cała pozostała zawartość intencji ignorowana, kiedy komponent jest wybierany

Intencje oferują dużo więcej niż tylko proste tworzenie obiektów

  • dostarczają kontrolę nad zarządzaniem komponentem
  • wysyłanie intencji może tworzyć nową instancję komponentu
  • -  // - przekierowywać intencję na istniejącą instancję komponentu
  • flagi kontrolujące zachowanie podczas aktywacji komponentu

Każdy rodzaj komponentu ma inny dostęp do intencji

  • serwisy
    • Intent automatycznie jest przekazywany do metody onStartCommand
  • broadcast receivery
    • Intent automatycznie jest przekazywany do metody onReceive
  • aktywności
    • intencja, która utworzyła aktywność jest dostępna przez metodę getIntent
    • każda inna intencja wysłana do aktywności jest przekazywana do metody onNewIntent

Najprostsze wywołania

Intent intent1 = new Intent(this, YActivity.class);

startActivity(intent1);

 

Intent intent2 = new Intent(this, ZService.class);

startActivity(intent2);

Flagi

intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);

Niejawne intencje

  • znoszą ograniczenia na znajomość nazwy komponentu (wywołania między różnymi aplikacjami i procesami)
  • dopasowywane do komponentów przy użyciu akcji, kategorii i/lub danych
  • znalezienie komponentu oparte jest na filtrach intencji dostarczanych przez komponenty
    • aktywacja działa tak samo jak przy intencjach jawnych
    • jedyna różnica jest w sposobie wybierania komponentu
  • nie zawierają nazwy komponentu
    • jeśli występuje, intencja nie jest porównywana z żadnymi filtrami intencji

Filtry intencji dostarczane przez komponent

  • definiowane w manifeście aplikacji
  • pasuje, jeśli wszystko w nim pasuje (akcja, kategoria, dane)
  • komponent pasuje, jeśli jeden lub więcej filtrów pasuje
  • komponent bez filtra może być aktywowany tylko przez jawną intencję

Akcja

  • nazwa w stylu pakietowym (często “action” na końcu nazwy pakietu)  np. com.xxx.action.DO_WORK
  • dla wielu standardowych akcji w kodzie używamy stałych Intent.ACTION_xxx (w manifeście używamy zawsze jawnej wartości string)
  • intencja może posiadać najwyżej jedną akcję
  • filtr intencji zawiera zero lub więcej akcji (w praktyce jedna lub więcej)

Intent intent = new Intent(“com.xxx.action.LOG_TIME”);

startService(intent);

 

<intent-filter>

          <action android:name=”com.xxx.action.LOG_TIME” />

          <action android:name=”com.xxx.action.LOG_DATE” />

</intent-filter>

Dopasowanie akcji

  • wielkość liter ma znaczenie
  • dopasowanie, jeśli akcja intencji zawiera się w liście akcji filtra
  • filtr z pustą listą akcji nie pasuje do żadnych intencji
  • intencja bez akcji pasuje do wszystkich filtrów, które mają co najmniej jedną akcję

Extras

  • informacje mogą być silnie typowane
    • string i większość typów prymitywnych
    • tablice typów
    • typy Serializable (Java) i Parceable (Android)
  • nie biorą udziału przy dopasowywaniu komponentów (nie ma ich w filtrach)

Kategorie

  • nazwa w stylu pakietowym (często na końcu “category”)
  • dla wielu standardowych kategorii w kodzie używamy stałych Intent.CATEGORY_xxx (w manifeście używamy zawsze jawnej wartości string)
  • intencja może zawierać 0 lub więcej kategorii (kategorie najczęściej pojawiają się w generowanych przez system intencjach)
  • filtr intencji zawiera 0 lub więcej kategorii

Dopasowywanie kategorii

  • wielkość liter ma znaczenie
  • dopasowanie, jeśli wszystkie kategorie w intencji są obecne w filtrze
  • intencja z pustą listą kategorii przechodzi test kategorii dla wszystkich filtrów (także filtry z pustą listą kategorii)
  • czasami kategorie są wymagane w filtrze, nawet jeśli ich nie ma w intencji
    • CATEGORY_DEFAULT - wymagana dla aktywności, która chce być dostępna przez startActivity przez intencję niejawną
    • CATEGORY_LAUNCHER - wymagane dla aktywności, która ma się pojawiać na ekranie Android Launcher

Dane

  • URI / mime (np. audio/mp4)
  • intencja zawiera zero lub więcej elementów określających dane
  • filtr zawiera zero lub więcej testów na dane
  • typy mime / uri mogą zawierać *
  • test URI składa się z kilku części: schemat(protokokół), host, port, ścieżka

Dopasowywanie danych

  • wielkość liter ma znaczenie
  • dopasowanie, jeśli dane intencji przechodzą przynajmniej jeden test danych z filtra
  • komplikacje
  • scenariusze dopasowania
    • intencja bez typu lub URI + filtr bez typu i URI
    • intencja z typem bez URI + filtr z pasującym typem bez URI
    • intencja z URI bez typu + filtr z pasującym URI bez typu
    • intencja z URI i typem + filtr z pasującym typem bez URI lub z pasującymi typem i URI

Konieczność

<activity …>

         <intent-filter>

                   <action android:name=”com.xxx.yyy.ABC_DEF” />

                   <category android:name=”android.intent.category.DEFAULT”/>

         </intent-filter>

</activity>

Intent intent = new Intent(”com.xxx.yyy.ABC_DEF”);

startActivity(intent);

Jeśli przygotujemy dwie aktywności z takimi filtrami jak powyżej, to przy takim samym wywołaniu Android wyświetli okno dialogowe pozwalające wybrać użytkownikowi aktywność (z opcją zapamiętania domyślnego wyboru).

Proces dopasowywania intencji do filtrów jest łatwo dostępny

  • możliwość logowania i uzyskania wyników dopasowania
  • logowanie
    • dodajemy do intencji Intent.FLAG_DEBUG_LOG_RESOLUTION
    • zapisywanie do Logcat
      • zawartość intencji
      • wszystkie pasujące komponenty
      • wybrany komponent
  • PackageManager
    • lista pasujących komponentów dla intencji
      • queryIntentActivities, queryIntentServices, queryBroadcastReceivers
    • komponent, który będzie wybrany dla intencji
      • resolveActivity, resolveService
    • wyniki zawierają opis komponentów
      • obiekty klasy ResolveInfo
      • flaga GET_RESOLVED_FILTER w zapytaniu pozwala uzyskać filtr, który został dopasowany do intencji
    • aktywności: dla naśladowania zachowania startActivity w wywołaniu należy użyć flagi MATCH_DEFAULT_ONLY

 

Delegacje i callbacki w intencjach PendingIntent

Bezpieczny sposób delegacji akcji na komponentach

Często stosowane w notyfikacjach, widgetach, serwisach, aktywnościach.

notification.setLatestEventInfo(this, “aaa”, “aaa”, pi);

PendingIntent enkapsuluje

  • Intent
  • akcję powiązaną z Intent
  • dane uwierzytelniające do komponentu
  • tworzenie pakietu komponentu

Zawartość PendingIntent  nie jest widzialna dla innych aplikacji

  • nie jest wystawiana poza tworzący ją komponent
  • przechowywana jako wpis w systemie Android

Współdzielenie PendingIntent jako referencji

Zduplikowane requesty o PendingIntent otrzymują referencję na tą samą pozycję

  • requesty muszą pochodzić z tej samej aplikacji
  • przekazane intencje muszą być równe jak w definicji Intent.filterEquals

Flagi PendingIntent

Flagi do metod getXXX pozwalają kontrolować zachowanie referencji

  • FLAG_UPDATE_CURRENT - jeśli pozycja już istnieje, zaktualizowanie jej extras z nowego Intent
  • FLAG_NO_CREATE - nowa pozycja nie jest tworzona, zwracana jest referencja do istniejącej pozycji, w przeciwnym razie null
  • FLAG_CANCEL_CURRENT - tworzona jest nowa pozycja, jeśli istnieje zgodna z nią pozycja oznaczona zostanie jako anulowana
  • FLAG_ONE_SHOT - pozycja może zostać wysłana tylko raz (możliwe jest wiele referencji do pozycji), jeśli istnieje zgodna z nią pozycja i nie zawiera tej flagi, tworzona jest nowa pozycja (obie pozycje są ważne)

W każdym przypadku ten sam PendingIntent bez extras:

Intent intent1 = new Intent(“com.xxx.ABC_DEF”);

PendingIntent pi1 = PendingIntent.getActivity(this, 0, intent1, 0);

 

Intent intent2 = new Intent(“com.xxx.ABC_DEF”);

intent2.putExtra(“KLM”, “xyz”);

PendingIntent pi2 = PendingIntent.getActivity(this, 0, intent2, 0);

Ta sama pozycja z extras:

PendingIntent pi2 = PendingIntent.getActivity(this, 0, intent2, PendingIntent.FLAG_UPDATE_CURRENT);

Jak naprawić (pierwsza bez extras, druga z extras) ?

PendingIntent pi2 = PendingIntent.getActivity(this, 0, intent2, PendingIntent.FLAG_ONE_SHOT);

Wysyłanie PendingIntent

Wykonywanie zawartości

  • wywołanie metody send wysyła Intent  (kilka przeładowań)
  • możliwość robienia ograniczonych modyfikacji z Intent
    • możliwość wysyłania wartości Intent nie ustawionych już w Intent w PendingIntent
    • Intent.fillIn - wykonanie modyfikacji
  • możliwość wysłania kodu rezultatu do celu PendingIntent
    • ograniczone scenariusze, gdzie PendingIntent jest dostarczycielem wyniku
    • często używane w połączeniu z Activity.createPendingResult

class XActivity extends Activity {

        …

        private ComponentName getWallPaperServiceComponentName()  {

                final String serviceClassName = “com.android.internal.service.wallpaper.ImageWallPaper”;

                ComponentName wallpaperService = null;

               

                ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);

                List<ActivityManager.RunningServiceInfo> services = am.getRunningServices(Integer.MAX_VALUE);

               

                for(ActivityManager.RunningServiceInfo theService: services)  {

                       if  (serviceClassName.equalsIgnoreCase(theService.service.getClassName())) {

                               wallpaperService = theService.service;

                               break;

                       }

                }

 

                return wallpaperService;

        }

        public void showWallpaperControlPanel() {

                ComponentName wallpaperComponentName = getWallPaperServiceComponentName();

 

                ActivityManager am = (ActivityManager)  getSystemService(Context.ACTIVITY_SERVICE);

                PendingIntent pi = am.getRunningServiceControlPanel(wallpaperComponentName);

 

                try {

                       pi.send(); 

                }  catch (PendingIntent.CanceledException e)  {

                }             

        }

}

 

Testy danych w filtrze intencji

Uri

  • w intencjach jako pojedyncza wartość
  • testy w filtrze intencji jako części Uri

Testy danych

  • Uri - nie wszystkie części są wymagane
  • mimeType
  • case sensitive

<data android:scheme=”http”/>

<data android:host=”www.xyz.com”/>

<data android:port=”80”/>

<data android:path=”/xxx/yyy/zzz”/>

<data android:mimeType=”audio/mp4”/>

Test Uri

  • musi zawierać przynajmniej schemat (inne części testu są ignorowane, jeśli nie ma schematu)
  • przy podaniu portu trzeba podać hosta
  • przy podaniu path, pathPrefix, pathPattern należy podać hosta

Relacje między atrybutami danych

  • wiele atrybutów może być umieszczonych na jednym elemencie data
  • każdy atrybut może być umieszczony na osobnym elemencie data
  • ten sam atrybut dla data może pojawiać się kilka razy w filtrze -warunek “OR”
    • jeden atrybut na osobnych elementach data - prosta alternatywa
    • gdy chcemy mieć alternatywne kombinacje np. protokołów z hostami robimy to w osobnych filtrach

public class XActivity extends Activity {

        …

        private List<String> getMatchingActivityNames(Intent intent)  {

                List<String> activityNames = new LinkedList<~>();

 

                PackageManager pm = getPackageManager();

                List<ResolveInfo> resolveInfoList = pm.queryIntentActivities(intent, PackageManager.GET_INTENT_FILTERS | …);

 

                for(ResolveInfo resolveInfo : resolveInfoList)

                        activityNames.add(resolveInfo.activityInfo.name);

 

                return activityNames;

        }

}

pathPattern

  • x* - 0 lub więcej wystąpień znaku x
  • .* - 0 lub więcej dowolnego znaku

typy mime

  • każdy standardowy
  • własne wartości (często w połączeniu z własnym content providerem)
  • * - pasuje do wszystkich podtypów np. image/*
  • test zawierający tylko typ mime zakłada komunikację lokalną
    • pliki lokalne (schemat “file”)
    • dane trzymane w content providerach (schemat “content”)

 

Systemowe funkcje dostępne przez intencje

Wykrywanie zmian w stanie systemu

  • bootowanie / zamykanie systemu
    • Intent.ACTION_BOOT_COMPLETED
    • Intent.ACTION_SHUTDOWN
  • podłączenie / odłączenie słuchawek
    • Intent.ACTION_HEADSET_PLUG
    • extras: stan, typ, mic
  • zmiany w zasilaniu
    • Intent.ACTION_POWER_CONNECTED
    • Intent.ACTION_POWER_DISCONNECTED
    • Intent.ACTION_BATTERY_LOW
    • Intent.ACTION_BATTERY_OKAY

Przechwytywanie przegapionych notyfikacji

  • do odebrania większości notyfikacji systemowych potrzebujemy wcześniej zarejestrować broadcast receivery
  • niektóre notyfikacje mają trwałą ważność, nowi odbiorcy mogą je otrzymywać po fakcie
    • dokowanie urządzenia
      • Intent.ACTION_DOCK_EVENT
    • mało miejsca w storage
      • Intent.ACTION_DEVICE_STORAGE_LOW

Rejestrowanie się na systemowe zdarzenia

  • większość wspiera rejestrację w manifeście
  • wszystkie wspierają rejestrację w run-time za pomocą context.registerReceiver
  • niektóre mogą być rejestrowane tylko w run-time
    • Intent.ACTION_BATTERY_CHANGED - szczegółowe zmiany w stanie zasilania systemu (także zmiana temperatury, podłączenie, odłączenie), spore różnice pomiędzy urządzeniami (np. co ile % zmiany w naładowaniu odpalane jest zdarzenie)
    • Intent.ACTION_TIME_TICK - upływ każdej minuty czasu systemowego

AlarmManager

  • kolejkowanie akcji w przyszłości
  • używamy intencji PendingIntent
  • akcje mogą być wykonywane nawet jeśli program nie wykonuje się
  • opcjonalnie może budzić urządzenie
  • nie jest przewidziany do zdarzeń czasowych w działającej aplikacji (zbyt duży narzut)
  • trzy opcje do odpalania PendingIntent
    • AlarmManager.set - jednorazowo o podanym czasie
    • AlarmManager.setRepeating - o podanym czasie i dokładnie równych odstępach
    • AlarmManager.setInexactRepeating - mniej więcej o podanym czasie i aproksymowanych odstępach (większa oszczędność energii)
  • specyfikacja czasu
    • relatywny do czasu systemowego (SystemClock.elapsedRealtime)
    • relatywny do czasu UTC (System.currentTimeMillis)
    • opcjonalna możliwość budzenia urządzenia
    • flagi
      • ELAPSED_REALTIME
      • ELAPSED_REALTIME_WAKEUP
      • RTC
      • RTC_WAKEUP

long alarmTime = SystemClock.elapsedRealtime() + 5000;

AlarmManager alarmManager = (AlarmManager)  getSystemService(Context.ALARM_SERVICE);

alarmManager.set(AlarmManager.ELAPSED_REALTIME, alarmTime, pendingIntent);

Wiele funkcjonalności platformy Android jest zwykłymi aktywnościami

  • w większości przypadków używamy startActivity  np. przeglądarka web i telefon
  • dla otrzymywania rezultatów odbieramy startActivityForResult  np. kamera & kontakty

Wyświetlanie zawartości Web

  • data intencji: “http”/”https”
  • akcja intencji:  Intent.ACTION_VIEW

Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(“http://kult.art.pl”));

startActivity(intent);

Telefon

  • dane intencji: “tel” uri
  • Intent.ACTION_DIAL - wyświetlenie ekranu do zadzwonienia
  • Intent.ACTION_CALL - od razu zadzwonienie (wymagane pozwolenie w manifeście permission.CALL_PHONE)

Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(“tel:0123456789”));

startActivity(intent);

Robienie zdjęć (już było)

Lista kontaktów

  • dane intencji:  ContactsContract.Contacts.CONTENT_URI
  • akcja intencji:  Intent.ACTION_PICK
  • po wyborze użytkownika wywoływane jest onActivityResult
    • dane intencji zawierają uri do kontakty w content providerze
    • Intent.EXTRA_SHORTCUT_NAME zawiera wyświetlaną nazwę kontaktu

Intent intent = new Intent(Intent.ACTION_PICK,  ContactsContract.Contacts.CONTENT_URI);

startActivityForResult(intent, 200);

 

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent data)  {

         if (resultCode == Activity.RESULT_OK)  {

                 switch (requestCode)  {

                         case 200:

                                 String displayName = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);

                                 Uri contactUri = data.getData();

                                 String email = getContactEmail(contactUri);

                                 String phoneNumber = getContractPhoneNumber(contactUri);

                                 break;

                 }

         }

}

 

private String getContactEmail(Uri contactUri)  {

          return getContactCommonDataItem(contactUri, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE,

          ContactsContract.CommonDataKinds.Email.CONTACT_ID, ContactsContract.CommonDataKinds.Email.DATA);

}

 

private String getContactPhoneNumber(Uri contactUri)  {

          return getContactCommonDataItem(contactUri, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,

          ContactsContract.CommonDataKinds.Phone.CONTACT_ID, ContactsContract.CommonDataKinds.Phone.DATA);

}

 

private String getContactCommonDataItem(Uri contactUri,  Uri dataStoreUri,  String idColumnName)  {

          String contactId = contactUri.getLastPathSegment();

          String resultValue = null;

 

          Cursor cursor = managedQuery(dataStoreUri,  null, idColumnName + “=?”, new String[] { contactId }, null);

          if (cursor.moveToFirst()) {

                  …

          }

}

sobota, 11 października 2014

Windows 10 Technical Preview - kilka newsów z tygodnia

Trochę newsów i ciekawostek, jakie wyłowiłem tego tygodnia na temat nowego Windows:

Pojedynek z Androidem - odc. 13 procesy i wątki

Poziom podstawowy i średni w miarę za nami, pora spróbować wznieść się trochę na ten wyższy. W ramach tego udało mi się dotrzeć do kilku istotnych, a zarazem ciekawych zagadnień z anatomii systemowej, których może nie każdy jest świadomy.

Sama idea piaskownicy dla aplikacji nie jest jakaś oryginalna, ot izolację aplikacji o unikalnej tożsamości jako izolowanego procesu i systemu plików spotykamy także w rodzinie Windows. W Android mamy jednak więcej jawnych form komunikacji między procesami. Tak porównując to Intent i Messenger w Windows/Windows Phone próbowałbym nazwać kontraktem, funkcjonalnością, taskiem. ContentResolver?  Źródła danych w ekosystemie Windows mamy, ale są to raczej bazy plikowe per aplikacja nie działające w osobnych procesach (chyba że myślimy o jakichś usługach systemowych dostarczających informacji,  które mogą czasami działać w ramach innego procesu). Binder?  Niskopoziomowych form komunikacji międzyprocesowej pomiędzy aplikacjami Windows Store raczej nie używamy. 

Dzielenie usera pomiędzy app-kami Android daje nam niezłą furtkę. Definiując w manifestach obu aplikacji ten sam identyfikator użytkownika i podpisując je tym samym certyfikatem możemy sprawić, że jedna aplikacja może odczytywać pliki drugiej aplikacji, jej zasoby czy współdzielone ustawienia.

Dzielenie procesu między app-kami Android zakłada, że dzielimy się userami. Dodatkowo tylko manifestach interesujących nas aplikacji ustawiamy ten sam identyfikator procesu. Jak coś będzie w jednym procesie to będzie wydajniej, w ContentResolver i IBinder znika narzut komunikacji międzyprocesowej.  Autor filmu pokazuje korzystanie z serwisu jednej aplikacji przez drugą (generowanie stub-ów z *.aidl kojarzy mi się z dawnymi zajęciami na studiach, gdzie były generowane stuby do Corby). Co prawda możemy to czynić bez sharowania usera czy procesu, ale możemy też doprowadzić, że wszystko będzie się nam wykonywało w jednym procesie!   W Windows\Windows Phone nie mamy możliwości użytkowania serwisu (czy raczej background task) przez różne aplikacje.  Komponenty też do niedawna mogły być używane tylko przez każdą aplikację mobilną Windows osobno, ale w Windows 8.1 pojawiła się pewna furtka dla aplikacji Enterprise tzw. Brokered Windows Runtime Components (przy czym jest to komunikacja między różnymi procesami). W Windows 8.1 aplikacje Enterprise mogą też wykorzystywać tzw. network loopback do komunikacji z lokalnymi serwisami.

Obsługa wielowątkowości, znów dużo możliwości, do których w Windows znajdzie się kilka odpowiedników. Jeśli chodzi o komunikat, że “aplikacja nie odpowiada”, to w mobilnym Windows\Windows Phone akurat czegoś takiego nie mamy. Całe WinRT API jest asynchroniczne, więc stosunkowo rzadko musimy ręcznie coś samemu pakować do innych wątków, bo coś nam przytnie UI. Czegoś na kształt loopera czy nawet handler threada w wysokopoziomowym API Windows nie kojarzę, są pętle z message, ale w kodzie niezarządzanym. Obsługa kodu w handlerach czasami może być wywoływana w Windows z poziomu innych wątków, ale nie ma z tym problemu (najwyżej modyfikacje na UI wywołujemy wtedy w tzw. dispatcherze). AsyncTask Androida to nieco mniej doskonały odpowiednik async z WinRT/.NET, o czym już kiedyś pisałem.  Pula wątków?  Ha, może zaczynam rozumieć po co na sprzęcie z Androidem są wprowadzane tak szybko procesory wielordzeniowe…

Mała wzmianka o kodzie natywnym w Android. Jest dedykowane do tego specjalne SDK i całe aktywności mogą być w pełni natywne. Oczywiście możemy też z poziomu języka Java wywoływać kod natywny przez JNI.  Samo JNI pozwoliłem sobie użyć kiedyś na studiach, kiedy kierowany wygodą napisałem cały program w Javie (C# nie był dozwolony), który wywołuję bibliotekę w C. Było to znacznie bardziej toporne od integracji C# 1.0 z kodem natywnym, nie wspominając o dzisiejszym niemal przezroczystym WinRT. Ciekawe na ile Java z JNI poszła do przodu? Jakie są ograniczenia z korzystania kodu natywnego na Android? Co możemy wywołać?  Szkoda, że autor filmu ograniczył się jedynie do ogólników, bo rozwinięcie tej tematyki mogło być bardzo ciekawe.

 

Intro

“Sandbox”

  • unikalna tożsamość
    • mapowanie aplikacji na user id
    • trochę różne w Android 4.2
  • unikalny proces (Dalvik, odpowiednik JVM)
  • unikalny system plików / zasobów

Komunikacja międzyprocesowa

  • IPC z linuksa/unixa nie pasuje do Androida
  • sieciowe sockety - możliwa, ale mało wydajna
  • udogodnienia Android
    • Intent
      • forma IPC
    • Messenger
      • obiekty przesyłane między procesami
      • referencja na handler między procesami
    • ContentResolver
      • IPC ukierunkowane na storage
      • używany z ContentProvider
      • standardowe operacje na SQL: Insert, Delete, Query
      • wsparcie dla wywoływania metod typowych dla danego content providera
    • IBinder & Binder
      • wysoka wydajność
      • podstawowy mechanizm IPC w Android, inne mechanizmy IPC wykorzystują go
      • wykorzystuje specjalne właściwości jądra Android
        • współdzielona pamięć pomiędzy procesami
      • używa specjalnie serializowanego formatu danych
        • wspiera każdy obiekt implementujący Parceable
      • może używać własnych interfejsów i metod
        • używane przez serwisy z wewnętrznego frameworku
        • wywołania metod pomiędzy procesami
        • definicja przy użyciu AIDL (Android Interface Definition Language)

Eclipse

  • dwa projekty z aktywnościami
  • menu kontekstowe na projekcie Debug As –> Debug Configurations… –> zakładka Remote Java Application (konfigacja portów)
  • możliwość debugowania za jednym razem aktywności wywołującej i wywoływanej

 

Dzielenie procesów i user id

Prawie unikalne User ID

  • wiązane z aplikacją w czasie instalacji
  • ustawienie w manifeście  <manifest … android:sharedUserId=”xxx.yyy.sharedId”
  • współdzielone ID jest stringiem (nie jest prawdziwym ID użytkownika)
  • aplikacje muszą być podpisane tym samym certyfikatem

Plusy i minusy współdzielonego user ID

  • Plus - dostęp do danych innej aplikacji
    • pliki
    • zasoby
    • współdzielone ustawienia
  • Minusy
    • nieefektywny RAM
    • trudny dostęp do danych innej aplikacji

Dostęp do danych innej aplikacji

public class XActivity extends Activity {

         private PackageManager mPM;

         …

         @Override

          public void onCreate(Bundle savedInstanceState)  {

                   …

                   mPM = getPackageManager();

          }

          @Override

          public void onStart()  {

                   …

                   try {

                           ApplicationInfo ai = mPM.getApplicationInfo(“com.xxx.yyy.SharedApp”);

                           string path = ai.dataDir + “/files”;

                           File inFile = new File(path, “data.txt”);

                           FileInputStream in = new FileInputStream(inFile);

                           int len = (int) inFile.length();

                           …

                           byte[] dataBytes = new byte[len];

                           in.read(dataBytes, 0, len);

                           in.close();

                           string data = new String(dataBytes);                          

                   } catch {

                   }                  

          }

}

Jeśli aplikacja 1 i 2 mają taki sam sharedUser w swoich manifestach, to aplikacja 2 może odczytać plik z wewnętrznego storage aplikacji 1.

Współdzielony proces

  • Definiujemy nazwę procesu w manifeście w tagu <application  … android:process=”xxx.yyy.sharedProcess”
    • tylko referencja (jak przy shared user)
    • odrobina semantyki
      • rozpoczynanie od ‘:’ dla prywatnego procesu w aplikacji
      • rozpoczynanie od małej litery dla globalnego (współdzielonego)
  • Musimy współdzielić user ID !
  • Współdzielenie procesu, współdzielenie zasobów
  • Większa wydajność - redukcja IPC dla własnych komponentów takich jak
    • IBinder
    • ContentProvider (dla wystawienia storage schema globalnie)

Wspólny serwis (przykład)

<service … android:exported=”true”

 

IShareService.aidl

 

interface IShareService  {

        boolean isSameProcess(int clientPid);

}

 

SampleShareService.java

public class SampleShareService extends Service  {

          …

          private ShareServiceImpl mBinder = new ShareServiceImpl();

          public IBinder onBind(Intent intent)  {

                   return mBinder;

          } 

          …

          private class ShareServiceImpl extends IShareService.Stub  {

                   public boolean isSameProcess(int clientPid)  {

                            return clientPid == Process.myPid();

                   }

          }  

}

IShareService.Stub - autogenerowana klasa na podstawie *.aidl  (tutaj: IShareService.java)

Aktywność “kliencka” - nie mamy w manifeście shared user id ani shared process

public class Client1Activity extends Activity  {

         …

         private IShareService mService;

         private ShareServiceConnection mConn;

         @Override

         public void onCreate(Bundle savedInstanceState)  {

                  …

                  mConn = new ShareServiceConnection();

                  Intent intent = new Intent(Intent.ACTION_MAIN);

                  intent.setClassName(“com.xxx.yyy.ShareService”, “com.xxx.yyy.ShareService.ShareService.SampleShareService”);

                  if (!bindService(intent, mConn, BIND_AUTO_CREATE))  {

                           …

                  }

         }

         …

         @Override

          public void onDestroy()  {

                   if (mConn != null) {

                           unbindService(mConn);

                           mConn = null;

                   }

                   super.onDestroy();

          }

          private class ShareServiceConnection implements ServiceConnection {

                   public void onServiceConnected(ComponentName name, IBinder binder) {

                            mService = IShareService.Stub.asInterface(binder);

                            try {

                                    if (mService.isSameProcess(Process.myPid()))  {

                                            …

                                    }

                            }

                   }

                   public void onServiceDisconnected(ComponentName name) {

                            mService = null;

                   }

          }

}

 

Wątki

Wielowątkowość

  • główny wątek UI
  • pula wątków używana przez framework

Wątek UI

  • Activity
  • Service
  • BroadcastReceiver

ANR: Application Not Responding

  • brak odpowiedzi na reakcję użytkownika po zadanym czasie
  • BroadcastReceiver nie może zakończyć działania po zadanym czasie
  • błąd krytyczny
  • nowsze wersje Android pozwalają ukrywać niektóre z tych komunikatów

Thread

Looper/Handler

  • Looper powiązany jest z konkretną instancją Thread
  • Looper czeka na obiekty Message
  • Looper dostarcza obiekty Message do Handler’a
  • rozszerzamy Handler lub dostarczamy Handler.Callback
  • używane dla dobrze zdefiniowanych interfejsów
    • zawartości Message
    • możliwość wykonywania obiektów Runnable w Handler

public class XActivity extends Activity implements Handler.Callback  {

          …

          private GenerateReceiver mgenRec;

          private DataGenThread mDGThread;

          protected Handler mHandler;

          …

          @Override

          public void onCreate(Bundle savedInstanceState)  {

                   …

                   mGenRec = new GenerateReceiver();

 

                   mHandler = new Handler(this);

                   mDGThread = new DataGenThread();

                   mDGThread.start();

          }

          @Override

          public void onStart() {

                   …

                  registerReceiver(mGenRec, new IntentFilter(“xxx”));

          }

          @Override

          public void onPause() {

                  …

                  unregisterReceiver(mGenRec);

          }

          @Override

          public void onDestroy() {

                  mDGThread.mWorkerHandler.obtainMessage(5);

                  try {

                          mDGThread.join();

                  }  catch (InterruptedException e)  {

                  }

                  …                 

          }

          @Override

          public void onClick(View v)  {

                  Message startMsg = mDGThread.mWorkerHandler.obtainMessage(1);

                  startMsg.sendToTarget();

          }      

          private class GenerateReceiver extends BroadcastReceiver  {

                  @Override

                  public void onReceive(Context context, Intent intent)  {

                          …

                  }

          } 

          @Override

          public boolean handleMessage(Message msg)  {

                  …

                  return false;

          }

          private class DataGenThread extends Thread implements Handler.Callback  {

                   private Looper mWorkerLooper;

                   protected Handler mWorkerHandler;

                   @Override

                   public void run()  {

                           Looper.prepare();

                           mWorkerLooper = Looper.myLooper();

                           mWorkerHandler = new Handler(mWorkerLooper, this);

                           Looper.loop();

                   }

                   @Override

                   public boolean handleMessage(Message msg)  {

                          if (…)  {

                                  mWorkerLooper.quit();

                          }

                           return false;

                   }

          }

}

HandlerThread

  • uproszczona wersja Thread/Looper/Handler
  • automatycznie tworzy Thread i Looper
  • możemy przechwycić moment inicjalizacji (onLooperPrepared)
  • pojedyncze wywołanie, może długo się wykonywać

private class DataGridThread extends HandlerThread implements Handler.Callback  {

        protected Handler mWorkerHandler;

        …

        @Override

        protected void onLooperPrepared()  {

                mWorkerHandler = new Handler(getLooper(), this);

        }

        @Override

        public boolean handleMessage(Message msg) {

                …

                if (…)

                        getLooper().quit();

                return true;

        }

}

AsyncTask

  • raczej do stosunkowo krótkich operacji (tj. kilka sekund)
  • ograniczenia na tworzenie i wykonywanie
  • operacje zmieniały się razem z wersjami Android
    • na początku sekwencyjne wykonywanie w wątku w tle
    • Donut (API 4) używał równoległego
    • Honeycomb (od API 11) powrócił do sekwencyjnego
      • możliwość nadpisania
  • tylko pojedyncze wywołanie
  • łatwe wycieki

Anulowanie taska przy niszczeniu aktywności

task.cancel(true);

try  {

       task.get();

}  catch (Exception e)  {

}

W środku doInBackground możemy sprawdzić, czy ktoś “nas” nie anulował przez metodę isCancelled()

Pule wątków

  • Rozszerzalny framework
    • długo wykonujące się taski nie wymagające interakcji z UI
    • operacje równoległe
    • wydajne używanie wątków przez reużytkowanie
  • Różne polityki dla anulowania, odrzucenia
  • Rozmiar puli

public class XActivity extends Activity implements Handler.Callback  {

       …

       private ExecutorService mExec;

       @Override

       public void onCreate(Bundle savedInstanceState)  {

              …

              //na nowych urządzeniach ilość rdzeni

              int numProcessors = Runtime.getRuntime().availableProcessors();

              mExec = Executors.newFixedThreadPool(numProcessors);

       }

       …

       @Override

       public void onDestroy()  {

              mExec.shutdown();

              try  {

                      if (!mExec.awaitTermination(60, TimeUnit.SECONDS))  {

                              mExec.shutdownNow();

                              mExec.awaitTermination(60,  TimeUnit.SECONDS);

                      }

              }   catch (InterruptedException e)  {

                      mExec.shutdownNow();

                      Thread.currentThread().interrupt();

              }

              …

       }

       …

       private void scheduleGeneration()  {

              mHandler.obtainMessage(1).sendToTarget();

              for (int i = 0; i < 10; i++)  {

                      DataGenRunnable curGen = new DataGenRunnable(i);

                      if (mExec.submit(curGen)  == null)  {

                               try  {

                                        Thread.sleep(1);

                                }  catch (…) {

                                }

                      }

              }

       }

       …

       private class DataGenRunnable implements Runnable  {

              …

              @Override

               public void run()  {

                      …

                      mHandler.obtainMessage(2).sendToTarget();

               }

       }

}

Natywne wątki

  • Natywność?
    • C/C++ dostępny przez Java
    • od API 9 (GingerBread) możliwość pisania natywnych aktywności (i aplikacji)
    • wymagane Android NDK
    • wymagane JNI (dostępność kodu natywnego z poziomu Java)
  • Dwie opcje używania wątków do natywnego kodu
    • wątki Java, dostęp do natywnych metod (wydajność ?)
    • natywne pthreads pod spodem  (trudność w zarządzaniu)
  • Duża elastyczność, wysoka wydajność
  • Brak wbudowanej we framework koordynacji