Android School 05

Android School 05

Przetwarzanie długotrwałych zadań w wątkach tła, użycie klasy AsyncTask i handlerów. HTTP — Apache HttpClient, HttpURLConnection, parsowanie XML i JSON.

572884b36b9538ad273ef7dd7e23b228?s=128

Adam Jodłowski

April 15, 2013
Tweet

Transcript

  1. Android School Warsztat 05 Adam Jodłowski

  2. 2 Wątek UI • podobnie do innych środowisk wizualnych, Android

    renderuje interfejs i reaguje na akcje użytkownika w tzw. wątku interfejsu użytkownika • domyślnie całe przetwarzanie aplikacji odbywa się w tym wątku, jeśli nie postanowimy inaczej • długotrwałe obliczenia musimy wykonywać poza wątkiem UI, w przeciwnym przypadku system wyświetli użytkownikowi zachętę do natychmiastowego przerwania działania naszej aplikacji (ANR a.k.a. FC)
  3. 3 Thread • klasyczny wątek znany z Javy (mamy też

    dostęp do klas z java.util.concurrent), pamiętajmy, że nie możemy manipulować interfejsem z wątków tła • Android jest oparty o tzw. single thread model, wątek UI nie zachowuje bezpieczeństwa przy dostępie współbieżnym i nie możemy odwoływać się do niego z innego wątku Thread t = new Thread() { public void run() { // przetwarzanie w tle } }; t.start();
  4. 4 Handler • najlepiej utworzyć go jako pole w klasie

    aktywności i odwołać się do niego z naszej implementacji Thread • jest on automatycznie wiązany z kolejką wiadomości głównego wątku i może aktualizować interfejs private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { // odbior wiadomosci, aktualizacja UI } }; • proces wysyłania komunikatów do handlera: Message message = new Message(); // mozna lepiej Bundle bundle = new Bundle(); bundle.putString("status", "COMPLETED"); message.setData(bundle); handler.sendMessage(message); // handler.sendEmptyMessage(int); .
  5. 5 AsyncTask • API Androida może zająć się zarządzaniem wątkami,

    pozostawiając nam implementację logiki i jednokrotne wykonanie metody execute([1]) zadania • musimy stworzyć własną klasę rozszerzającą AsyncTask<[1], [2], [3]> i przesłonić potrzebne nam metody onPreExecute() // inicjalizacja, UI [3] doInBackground([1]) // glowne przetwarzanie, metoda wymagana onProgressUpdate([2]) // aktualizacja postepu, UI onPostExecute([3]) // przekazanie wynikow, UI • AsyncTask może być uruchomiony tylko jeden raz, aktualizacji postępu dokonujemy dzięki metodzie publishProgress([2]) • możemy też anulować zadanie wywołując metodę cancel(boolean) i sprawdzać cyklicznie czy nie nastąpiło anulowanie za pomocą metody isCancelled() • w przypadku anulowania, zamiast onPostExecute(Object) zostanie wywołana metoda onCancelled(Object)
  6. 6 Timer • pozwala na zlecanie zadań TimerTask do sekwencyjnego

    wykonania we własnym wątku o określonym czasie i opcjonalnie z powtarzaniem Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { // wykonanie dzialania z watku tla } }, 2000); • anulowanie wszystkich zadań wymaga wywołania metody cancel()
  7. 7 Przetestuj mechanizmy wykonywania długotrwałych zadań w tle.

  8. 8 Zmiany konfiguracji • są to wszystkie zmiany stanu urządzenia,

    prowadzące do zniszczenia (wywołując kolejne metody cyklu życia) i restartu widocznej aktywności • może to być zmiana orientacji ekranu, podłączenie stacji dokującej, zestawu słuchawkowego, wysunięcie klawiatury sprzętowej i inne • zapobiegamy restartom deklarując obsługę zmian w manifeście android:configChanges="keyboardHidden|orientation|screenSize" • otrzymamy dzięki temu wywołanie zwrotne przy zmianie konfiguracji @Override public void onConfigurationChanged(Configuration newConfig) { if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { ... } } • wszystkie zasoby (getResources()) zostaną na tym etapie przeładowane i możemy skorzystać z nich w reakcji na zmianę konfiguracji • wymuszamy stałą orientację ekranu aktywności android:screenOrientation="portrait" .
  9. 9 Save/RestoreInstanceState • w przypadku nagłego przerwania działania naszej aktywności,

    możemy zapisać i odtworzyć ulotne dane @Override public void onSaveInstanceState(Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); savedInstanceState.putInt("count", 666); savedInstanceState.putString("name", "Android"); } • tak zapisany obiekt Bundle trafi do onCreate() oraz do onRestoreInstanceState() po restarcie aktywności . . . . .
  10. 10 NonConfigurationInstance • jeśli zrestartowanie aplikacji spowoduje potrzebę długotrwałego odtwarzania

    poprzedniego stanu danych, możemy skorzystać z możliwości utrwalenia własnego obiektu i odebrania go w niezmienionej formie po ponownym uruchomieniu aktywności @Override public Object onRetainNonConfigurationInstance() { final MyData data = collectMyData(); return data; } • jeśli aktywność została zrestartowana, otrzymamy obiekt z powrotem @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); final MyData data = (MyData) getLastNonConfigurationInstance(); if (data == null) { data = loadMyData(); } } • pamiętajmy, aby nie zwracać w ten sposób obiektów ściśle związanych z aktywnością, kontekstem, komponentami wizualnymi i innymi (dlaczego?)
  11. 11 Globalny obiekt aplikacji • za jego pomocą możemy przechowywać

    globalny stan powiązany z kontekstem ważnym przez cały czas życia aplikacji w pamięci public class MyApplication extends Application { public void metodaNaszejAplikacji() { ... } @Override public void onCreate() { super.onCreate(); // inicjalizacja skladowych } } • wskazanie klasy aplikacji w manifeście <application android:name=".MyApplication" ... > • użycie w komponencie składowym naszej aplikacji ((MyApplication) getApplication()).metodaNaszejAplikacji();
  12. 12 Przetestuj sposoby reakcji na zmiany konfiguracji urządzenia i zachowaj

    w odpowiedni sposób ulotne dane instancji.
  13. 13 Łączenie z Internetem • Android pozwala na standardowe tworzenie

    gniazd TCP oraz UDP, zawiera klasę (Http)URLConnection oraz Apache HttpClient URL url = new URL("http://www.android.com/"); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); try { InputStream in = new BufferedInputStream(urlConnection.getInputStream ()); // czytamy stream... } finally { urlConnection.disconnect(); }
  14. 14 Apache HttpClient • utworzenie klienta HttpClient httpClient = new

    DefaultHttpClient(); • przykład użycia GET HttpGet httpGet = new HttpGet("http://host/resource"); httpGet.addHeader("Authorization", "Basic c2NvdHQ6dGlnZXI="); ResponseHandler<String> responseHandler = new BasicResponseHandler(); String response = httpClient.execute(httpGet, responseHandler); • przykład użycia POST List<NameValuePair> postParameters = new ArrayList<NameValuePair>(); postParameters.add(new BasicNameValuePair("body", "body content")); postParameters.add(new BasicNameValuePair("token", "auth token")); HttpPost httpPost = new HttpPost("http://host/resource"); httpPost.setEntity(new UrlEncodedFormEntity(postParameters)); String response = httpClient.execute(httpPost, responseHandler);
  15. 15 Połącz się z Internetem. Zapoznaj się z możliwościami Android

    Asynchronous Http Client (http://loopj.com/android-async-http).
  16. 16 JSON • Android zawiera bilbioteki pakietu json.org do pracy

    z formatem JavaScript Object Notation JSONObject json = new JSONObject(); try { json.put("name", "scott"); json.put("surname", "tiger"); json.put("age", 666); } catch (JSONException e) { e.printStackTrace(); } Log.d("AS", json); // {"name": "scott", "surname": "tiger", "age": 666} • w formacie JSON rozróżniamy obiekty i tablice obiektów, możemy pobierać z nich wartości proste i łańcuchy znaków JSONObject json = new JSONObject(response); JSONArray elements = json.getJSONArray("elements"); JSONObject obj = elements.getJSONObject(0); long id = obj.getLong("id"); • biblioteki takie jak Google GSON i Jackson pozwalają uprościć pracę z modelami obiektów reprezentowanymi jako dane JSON i stosować silne typowanie
  17. 17 XML • Android wspiera obsługę XML Pull Parser, DOM

    w wersji 2 oraz SAX // tworzymy fabryke parserow i nowy parser SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser sp = spf.newSAXParser(); // pobieramy uchwyt do elementu przetwarzajacego XML XMLReader xr = sp.getXMLReader(); // tworzymy i przekazujemy wlasny mechanizm przetwarzania MyXmlHandler myXmlHandler = new MyXmlHandler(); xr.setContentHandler(myXmlHandler); // parsujemy xr.parse(new InputSource(url.openStream())); • do parsowania XML, musimy dostarczyć implementację własnego handlera parsującego treść dokumentu
  18. 18 XmlHandler • przykładowa implementacja private class MyXmlHandler extends DefaultHandler

    { private boolean inName = false; private boolean inSurname = false; @Override public void startDocument() throws SAXException {...} // oraz endDocument() @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { if (localName.equals("name")) { this.inName = true; } else if (localName.equals("surname")) { this.inSurname = true; } } @Override public void endElement(String namespaceURI, String localName, String qName) throws SAXException {...} @Override public void characters(char ch[], int start, int length) { String content = new String(ch, start, length); } }
  19. 19 Przetestuj techniki parsowania danych.

  20. 20 Zadanie domowe: Stwórz aplikację pobierającą dane z wybranej usługi

    sieciowej odporną na zmiany konfiguracji urządzenia.