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)
- Intent
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
Brak komentarzy:
Prześlij komentarz