sobota, 13 września 2014

Pojedynek z Androidem - odc.6 intro po raz drugi (architektura, preferencje, menu, okna dialogowe, deployment)

Następny odcinek z cyklu poświęconemu systemowi Android.  Po obejrzeniu kolejnego szkolenia mającego być wprowadzeniem, tym razem starszego wychwyciłem kilka rzeczy dotąd nieporuszanych jak architektura systemu i API,  ustawienia aplikacji, obsługa menu i okien dialogowych oraz przygotowanie aplikacji do publikacji w sklepie.

Oczywiście nie mogę obejść się bez odrobiny publicystyki, tak więc i dziś zanotuję kilka swoich subiektywnych wrażeń oraz porównań.

Preferencje - taki odpowiednik ApplicationData.LocalSettings plus panel ustawień jak dla mnie, przy czym przynajmniej na razie nie zauważyłem odpowiednika RoamingSettings.  Jak zwykle trochę przekombinowana obsługa, choć kreator i automatyczny interfejs dla różnego rodzaju ustawień może być ciekawą opcją.

Menu - obsługa kontekstowego menu jak zwykle przekombinowana, nie rozumiem upodobania do nadpisywania metod w aktywności, brakuje mi swobody jaką dają kontrolki XAML czy HTML5.  Pomysł na wybieralne menu czy z checkami jakiś taki też nadmiarowy,  patrząc tak wizualnie

Okna dialogowe - tutaj znowu się powtórzę, po kij nadpisywać metody w aktywności, definiowanie callbacków i akcji nawet zgrabne, choć podejście z async w WinRT bardziej mi się podoba. Dialog progress?  Czy nie jest to wizualny przerost formy nad treścią, zwłaszcza jeśli nie można go anulować ? IMHO wystarczyłby sam pasek czy kółko, ale Android ma swoje wytyczne.  Obsługa znów mało banalna, coś notyfikuj, coś nadpisz, odpal coś jawnie w innym wątku itp.  Niestandardowy dialog ma choćby odpowiednika w ContentDialog w WP 8.1.  W Android mimo deklaratywnego XML i tak nadal mamy w cenie imperatywne sterowanie elementami UI, czyli odszukanie ich z poziomu code-behind, by coś na nich wykonać.

Publikacja - w szczegółach różna, ale jednak coś podobnego. Podpisywanie, pakowanie, sklep brzmi  dość znajomo. Przy czym tu jakby sami bawimy się w obfuskację, w Windows Phone Microsoft zabezpiecza nasze app-ki za nas, w Windows nie ma zabezpieczeń, ale mają być wprowadzone.

Zaznaczam że to starsze szkolenie, więc jak dotrę do nowszych materiałów to być może zweryfikuję, to co tutaj napisałem. Ale czasami od czegoś trzeba zacząć.

 

Architektura

  • Jądro Linux
    • abstrakcja sprzętu, sterowniki
  • Biblioteki
    • Standardowa biblioteka C
    • Biblioteki multimedialne
    • Menadżery surfowania (m.in dostęp do wyświetlacza)
    • LibWebCore (WebKit)
    • SGL (2D)
    • Biblioteki 3D (OpenGL, ES)
    • FreeType (renderowanie czcionek)
    • SQLLite
  • Android Runtime
    • podstawowe biblioteki zawierające większość standardowych bibliotek Java
    • nie używa JVM, a Dalvik VM
      • wykonuje pliki .dex
      • każda aplikacja uruchamia swoją własną VM
  • Framework dla aplikacji
    • pełny zbiór serwisów napisanych w Javie
    • widoki i okna
    • zasoby, dostawcy zawartości
    • serwisy telefonu / API
    • notyfikacje
    • cykl życia
  • Aplikacje
    • jeden lub więcej
      • aktywności
      • serwisów
      • broadcast receiver-ów
      • content provider-ów (odpytywanie zbiorów danych np. SQL)

Inne rodzaje API

  • serwisy lokalizacji
  • serwisy telefoniczne
  • audio & video
  • przeglądarka webowa
  • google maps

Bezpieczeństwo i uprawnienia

  • Każda aplikacja wykonuje się ze swoim własnym Linux user ID
  • Wszystkie aplikacje wykonują się domyślnie w piaskownicy
  • Uprawnienia
    • Aplikacje deklarują w manifeście, czego potrzebują (w tym możliwe definicje własnych)
    • W czasie instalacji użytkownik decyduje o zgodzie na wymagane uprawnienia
  • Aplikacje są podpisywane lokalnie (pliki APK)

Instrumentacja

  • możliwość określenia w manifeście
  • junit

public class MainActivity extends Activity {

        @InjectView

        private TextView textView;

}

 

Layout

Layout absolutny

<AbsoluteLayout …

      android:layout_width=”fill_parent”  android:layout_height=”fill_parent”>

      <Button android:text=”Button”  android:layout_width=”wrap_content” android:layout_height=”wrap_content”

         …  android:layout_x=”126dip”  android:layout_y=”61dip”></Button>

</AbsoluteLayout>

Table Layout,  List View, Grid View, Tab View

Date/Time Picker, Dropdown (Spinner), Auto Complete, Gallery, Frame Layout, Google Map, Web View

android:textSize=”16sp”

 

Preferencje

Współdzielone preferencje

  • Sposób na przechowywanie informacji dla aplikacji
  • Dozwolone jest używanie wielu zbiorów preferencji
  • Dwa poziomy
    • aktywność
    • aplikacja
  • To coś innego niż zapisywanie stanu
  • Dlaczego używać
    • Małe ilości danych, które potrzebujemy zapisać
    • Łatwiejsze niż zapisywanie do pliku lub bazy danych
    • Ustawienia użytkownika
  • Kiedy stosować
    • przechowywanie danych pomiędzy wywołaniami aplikacji
    • przechowywanie danych pomiędzy aktywnościami w aplikacji
    • odpowiadanie na zmiany preferencji za pomocą handlerów

public class XApplication extends Application {

         @Override

         public void onCreate()  {

                 super.onCreate();

                 SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());

                 Editor editor = preferences.edit();

                 editor.putString(“test”, “xxx”);

                 editor.commit();

         }

}

Nazwę własnej klasy aplikacji umieszczamy w manifeście

public class MainActivity extends Activity {

         …

         private OnClickListener button1Listener = new OnClickListener()  {

                 @Override

                 public void onClick(View arg0)  {

                         SharedPreferences preferences = getSharedPreferences(“default”, MODE_PRIVATE);

                         string value = preferences.getString(“test2”, “”);

                         …

                 }

         }

         …

        @Override

        public void onCreate(Bundle savedInstanceState)  {

                 …

                 //SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());

                 //String value = preferences.getString(“test”, “”);

                 …

                 SharedPreferences preferences = getSharedPreferences(“default”, MODE_PRIVATE / MODE_WORLD_READABLE / MODE_WORLD_WRITEABLE);

                 Editor editor = preferences.edit();

                 editor.putString(“test2”, “yyy”);

                 editor.commit();

        }

}

Ekran ustawień

Eclipse

  • New Android XML File  -> Preference
    • Add:  CheckBoxPreference, EditTextPreference, ListPreference, Preference, PreferenceCategory,  PreferenceScreen, RingtonePreference
    • Attributes for EditTextPreference
      • Attributes from DialogPreference
      • Attributes from Preference:  Key, …

<PreferenceScreen …>

         <EditTextPreference  android:dialogMessage=”Xxxx”  android:key=”test3” android:dialogTitle=”Yyy”  android:title=”Zzz”></EditTextPreference>

</PreferenceScreen>

 

public class MainPreferenceActivity extends PreferenceActivity {

          private OnPreferenceChangeListener  onPreferenceChangeListener  = new OnPrefeenceChangeListener()  {

                  @Override

                  public boolean onPreferenceChange(Preference arg0,  Object arg1)  {

                          return false;

                  }

          }

 

          @Override

           protected void onCreate(Bundle savedInstanceState)  {

                   super.onCreate(savedInstanceState);

                   addPreferencesFromResource(R.xml.main_preferences);

 

                   Preference p;

                   p.setOnPreferenceChangeListener(onPreferenceChangeListener);

           }

}

Manifest

  • Deklaracja MainPreferenceActivity

Inne opcje przechowywania danych

  • Internal Storage
    • jak isolated storage
    • usuwany z aplikacją
    • proste pisanie do pliku
  • External Storage
    • jak internal, ale może nim być karta SD lub inny nośnik dostępny dla użytkownika
    • może być zamontowany przez USB
    • należy sprawdzić dostępność przed użyciem
    • dostępne specjalne foldery Androida
  • Bazy danych
    • SQLLite
    • Sqllite3 tool
    • kursory do odpytywania danych

 

Menu

Option menu

  • wyświetlane po naciśnięciu sprzętowego przycisku menu
  • 6 pozycji, pozycja “more”
  • Zwykle link do ustawień aplikacji
  • Specyficzne dla aktywności

Context menu

  • dostępne po dłuższym dotknięciu elementu
  • przykłady:  usuwanie,  kopiowanie,  wycinanie,  zmiana nazwy

Submenu

  • tylko jeden poziom zagnieżdżenia

Pozycje z checkiem

  • wszystkie pozycje menu mogą mieć checka
  • tylko menu kontekstowe wyświetla checki
  • mogą być pojedyncze lub grupa
    • pojedynczy wybór
    • multi-select

Eclipse

  • New Android XML File  -> Menu
    • Checkable behavior

<menu  …>

          <group  android:id=”@+id/group1”>

                     <item android:id=”@+id/item1”></item>

                    <item  android:id=”@+id/item2”></item>

          </group>

</menu>

      public class MainActivity extends Activity  {

               …

               @Override

               public void onCreate(Bundle savedInstanceState)  {

                         …

                         registerForContextMenu(button);

               }

 

               @Override

               public void onCreateContextMenu(ContextMenu menu,  View v,  ContextMenuInfo menuInfo)  {

                          super.onCreateContextMenu(menu, v, menuInfo);

                          MenuInflater inflater = getMenuInflater();

                          inflater.inflate(R.menu.context_menu), menu);

               }

                @Override

                public boolean onContextItemSelected(MenuItem item)  {

                          …

                          return super.onContextItemSelected(item);

                }

      }

<menu  …>

      <group …>

            <item …></item>

            <item …>

                   <menu>

                          <item …></item>

                          <item …></item>

                   </menu>

            </item>

      </group>

</menu>

 

Dialogi

showDialog

         onCreateDialog

         onPrepareDialog

dismissDialog   (pozostaje w pamięci, jedynie potem onPrepareDialog)

removeDialog

Alert dialog

  • Zastosowanie
    • Ogólne powiadomienie
    • Pytania OK/Anuluj
    • Proste wybory
  • 1, 2 lub 3 przyciski
  • Możliwość używania list z wyborem

Progress dialog

  • Dwa tryby
    • Spinner (kółko)
    • Horizontal (pasek)
  • Bez Cancel

Custom dialog

  • ten sam layout, co aktywności
  • może wchodzić w interakcję z widokiem w layout
  • zawsze pokazuje tytuł
  • zarządzanie jak w Alert Dialog

public class MainActivity extends Activity  {

         final private int RESET_DIALOG = 0;

         …

         private OnClickListener resetButtonListener = new OnClickListener()  {

                   @Override

                   public void onClick(View arg0)  {

                           showDialog(RESET_DIALOG);

                         

                           //lub dla progress dialog  (drugi sposób)

                           dialog = ProgressDialog.show(MainActivity.this, “Waiting”,  “Doing something…”);

                            Thread thread = new Thread(new Runnable()  {

                                       @Override

                                        public void run()  {

                                                 Thread.sleep(3000);

                                                  handler.sendEmptyMessage(0)

                                        }

                              });                             

                              thread.start();

                   }

         }

         protected android.app.Dialog  onCreateDialog(int id)  {

                 /* 

                 switch(id)  {

                           case  RESET_DIALOG: 

                                    AlertDialog.Builder  builder  = new Builder(this);

                                    return builder

                                           .setMessage(“Xxx …”)

                                           .setNegativeButton(“No”, new DialogInterface.OnClickListener()  {

                                                      @Override

                                                      public void onClick(DialogInterface arg0,  int arg1)  {

                                                               Toast.makeText(MainActivity.this,  “…”, 5).show();

                                                      }

                                           })

                                           .setPositiveButton(“Yes”, new DialogInterface.OnClickListener()  {

                                                      @Override

                                                      public void onClick(DialogInterface arg0,  int arg1)  {

                                                               Toast.makeText(MainActivity.this,  “…”, 5).show();

                                                      }

                                           })

                                           .create();

                     }  */

 

                     /*

                      switch(id)  {

                           case  RESET_DIALOG: 

                                    AlertDialog.Builder  builder  = new Builder(this);

                                    return builder

                                           .setTitle(“Xxx …”)

                                           .setNegativeButton(“No”, new DialogInterface.OnClickListener()  {

                                                      @Override

                                                      public void onClick(DialogInterface arg0,  int arg1)  {

                                                               Toast.makeText(MainActivity.this,  “…”, 5).show();

                                                      }

                                           })

                                           .setPositiveButton(“Yes”, new DialogInterface.OnClickListener()  {

                                                      @Override

                                                      public void onClick(DialogInterface arg0,  int arg1)  {

                                                               Toast.makeText(MainActivity.this,  “…”, 5).show();

                                                      }

                                           })

                                           .setMultiChoiceItems(new CharSequence[] { “A”,  “B”,  “C”},  new boolean[]  { false,  false,  false},  null)

                                           .create();

                     }  */

 

                     /*

                     switch(id)  {

                            case RESET_DIALOG: 

                                        ProgressDialog  progressDialog = new ProgressDialog(this);

                                        progressDialog.setMessage(“…”);

                                        progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);

 

                                        Thread thread = new Thread(new Runnable()  {

                                                 @Override

                                                 public void run()  {

                                                          Thread.sleep(3000);

                                                          handler.sendEmptyMessage(0)

                                                 }

                                        });

                                        dialog  = progressDialog;

                                        thread.start();

                                        return dialog;

                     }  */

 

                     switch  (id)  {

                               case RESET_DIALOG:

                                         dialog = new Dialog(this);

                                         dialog.setContentView(R.layout.custom_dialog);

                                         dialog.setTitle(“…”);

                                         TextView textView = (TextView) dialog.findViewById(R.id.textView1);

                                         textView.setText(“…”);

                                         return dialog;

                     }

 

                      return null;

         }

       

        private Dialog dialog  = null;

        private Handler handler = new Handler()  {

                  public void handleMessage (android.os.Message msg)  {

                            dialog.dismiss();

                  } 

        }

         …

}

 

Deployment

Podpisywanie aplikacji

  • wszystkie aplikacje muszą być podpisane
  • wersja debug jest podpisywana kluczem debug
  • certyfikaty muszą wygasać po 22.10.2033
  • własne podpisywanie (bez centrum autoryzacyjnego)
  • ten sam certyfikat dla
    • aktualizacji
    • współdzielenia kodu

Eclipse

  • Projekt w Package Explorer (menu kontekstowe)
    • –> Android Tools –>  Export (Un)Signed Application Package…
    • -> Export:  Android/Export Android Application

Własne podpisywanie

  • {Program Files}\Java\{wersja}\bin\keytool  -genkey -v -keystore ścieżka -alias Demo  -keyalg RSA  -keysize 2048  -validity 10000
  • jarsigner  -verbose  -keystore ścieżkaCert  ścieżkaPakietu Demo
  • jarsigner -verify -verbose ścieżkaPakietu
  • {android_sdk\tools}zipalign -v 4 ścieżkaPakietu plikPakietu

Proguard

  • obfuscator
  • narzędzie open source w ADT
  • redukuje rozmiar kodu
  • License Manager nie jest wystarczający
  • trudny do konfiguracji z zewnętrznymi bibliotekami
  • trudne debugowanie po zdeployowaniu
  • {android_sdk}\tools\proguard\bin

Android License Manager

  • tylko dla płatnych aplikacji
  • urządzenie lub emulator z API level 8+

Aplikacja – LVL(License Veryfication Library) –> Market App –> Market License Server

Android SDK and AVD Manager

Third party Add-ons

  • Google Inc. (dl-ssl.google.com)
    • Google Market Licensing package, revision 1

Eclipse

  • Kreowanie projektu z biblioteki

Manifest

<uses-permission android:name=”com.android.vending.CHECK_LICENSE” />

public class MainActivity extends Activity {

        private static final byte[] SALT = new byte[] { … };

        private static final String GOOGLE_KEY = “ABC”;

        …

        private LicenseCheckerCallback licenseCheckerCallback;

        private LicenseChecker licenseChecker;

        …

        private OnClickListener helpButtonListener = new OnClickListener()  {

                @Override

                public void onClick(View arg0)  {

                       licenseChecker.checkAccess(licenseCheckerCallback);

                }

        }

        …

 

        @Override

        public void onCreate(Bundle savedInstanceState)  {

                …

                licenseCheckerCallback  = new LicenseCheckerCallbackImpl();

                String deviceId = Secure.getString(this.getContentResolver(),  Secure.ANDROID_ID);

                licenseChecker = new LicenseChecker(getApplicationContext(), 

                new ServerManagedPolicy(getApplicationContext(),  new AESObfuscator(SALT, getPackageName(), deviceId),

                GOOGLE_KEY);

        }

 

        …       

        private class LicenseCheckerCallbackImpl implements LicenseCheckerCallback  {

                 @Override

                 public void allow()  {

                           if (isFinishing())

                                      return;

                           ShowMessage(“…”);

                 }

                 @Override

                 public void dontAllow()  {

                           if (isFinishing())

                                      return;

                           ShowMessage(“…”);

                 }

                 @Override

                 public void applicationError(ApplicationErrorCode errorCode)  {

                 }

        }

 

       private void ShowMessage(String message)  {

                handler.post(new Runnable()  {

                          @Override

                          public void run()  {

                                   Toast.makeText(MainActivity.this, message, 5).show();

                          }

                }

       }

}

Windows 9 Preview (Build 9834) - pierwsze screenshoty i filmiki

Zbliża się data wydania Windows 9 Technology Preview, którą jest 30 września.  Partnerzy już coś tam podobno mają, pojawiają się też pierwsze przecieki. Co prawda raczej sam obraz nie wyciekł, ale pojawiają się już screenshoty oraz ostatnio dwa filmiki video.  Wygląda to wszystko autentycznie, jakby niewielka modyfikacja i rozbudowa tego, co zostało oficjalnie mignięte na ostatnim BUILD. 

Zwróćmy uwagę na opcję “Use the Start menu instead of the Start screen”, jak ją wyłączy się to zamiast kafelków w menu Start będziemy mieć ekran startowy podobnie jak w Windows 8/8.1. 

Wirtualne pulpity to oczekiwany od dawna funkcjonalność. W tej odsłonie widzimy dolny systemowy pasek w stylu Modern UI, dzięki któremu możemy definiować nowy pulpit oraz szybko przełączać się między różnymi pulpitami.  Zawsze też widzimy podgląd w centralnej części ekranu, jakie aplikacje, gdzie są otwarte.

niedziela, 7 września 2014

Pojedynek z Androidem - odc.5 serwisy cz.2, broadcast receiver-y, notyfikacje

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);

środa, 3 września 2014

Pojedynek z Androidem - odc.4 wielowątkowość, praca w tle, serwisy cz.1

W tym odcinku zaczynam zgłębiać komponenty wchodzące w skład app-ki.  Pojawiają się tematy związane z asynchronicznością, wątkami, pracą w tle, które będą kontynuowane także w następnym poście. Kilka słów, uwag, które rzucam na gorąco:

Serwis - co to jest?  Porównując do Windows/Windows Phone jakby odpowiednik background taska, ale są zasadnicze różnice.  W Android serwis może wykonywać się ciągle do końca świata, w Windows/Windows Phone własne serwisy wykonują się tylko krótko raz na jakiś czas lub dłużej, ale przy spełnionych określonych warunkach. 

Broadcast receiver – mam nadzieję, że dowiem się o tym tworze w przyszłości więcej, na razie kojarzy mi się znowu z background taskiem wyzwalanym przez jakieś systemowe zdarzenia

Domyślnie wszystko w jednej app-ce działa w głównym wątku UI. Tak więc idąć najprościej napiszemy serwis, który wykonując dłuższą operację…  zablokuje interfejs użytkownika. Aby to działało naprawdę w tle trzeba trochę opisać się kodu z użyciem HandlerThread.

Sposób na responsywną aplikację -  w Windows/Windows Phone mamy async w .NET/C++ albo then/done w WinJS, które opakowują WinRT API, które w większości jest asynchroniczne (dla każdej operacji mogącej chwilkę potrwać). Bardziej jawną wielowątkowość też mamy oczywiście.  A Android?  Android ma trochę bardziej urozmaicone podejście. Detekcja, że aplikacja nie odpowiada lub klasa StrictMode wykrywająca długotrwające operacje w wątku UI nie wydają się przekonujące, bo kojarzą się z gaszeniem zaistniałego już pożaru wynikłego zbyt liberalnego API. W Windows nie zablokujemy operacją dyskową czy sieciową UI ponieważ  dostępne API jest tylko w postaci asynchronicznej.  A więc nie zaprószajmy ognia w Androidzie  i stosujmy AsyncTask, wątki oraz loopery z handlerami.  Te dwa pierwsze wydają się jakimiś bardziej bezpośrednimi odpowiednikami porównując z aplikacjami Windows Store.  Tak, Android ma coś takiego jak AsyncTask i nawet podobnie jak async zaczyna i kończy pracę w wątku UI, a także na czas notyfikacji o progresie!  Może nieco bardziej toporne, co wynika z ograniczeń języka Java w stosunku do C#, ale zasada ta sama. Hurra!  Loopery z handlerami stanowią ponoć jakiś odpowiednik message loop z Windows, aczkolwiek w wysokopoziomowym Windows Store (.NET/JS) nie zchodzimy tak głęboko,  a nasłuchiwanie zdarzeń np. o zmianie lokalizacji realizujemy za pomocą prostego API ze zwykłymi zdarzeniami (które ewentualnie możemy wrzucić do taska w tle lub skorzystać z systemowego API do dłuższego śledzenia).

Poniżej tradycyjnie już trochę notatek.

 

Model komponentów i aplikacji

Serwisy

Aktywności nie mogą zagwarantować wykonywania zadań w tle ani nasłuchiwania zdarzeń w tle. Ich wykonywanie zależy ściśle od interakcji z użytkownikiem.

Serwisy mogą wykonywać się w tle

  • normalnie mogą wykonywać się aż skończą swoją pracę
  • dziedziczymy po klasie Service
  • w manifeście dodajemy element <service> (opcjonalnie z filtrem intencji określającym sposób uruchomienia serwisu)
  • wywołujemy przez metodę startService z odpowiednim Intent

public class XService extends Service {

}

<service

     android:name=”.XService”>

</service>

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

startService(intent);

Broadcast receivers 

Są automatycznie wykonywane przez system

  • wykonują się w tle, aby przechwycić Intent (monitorowanie systemowych zdarzeń np. zmian w sieci)
  • dziedziczymy po klasie BroadcastReceiver (nadpisujemy metodę onReceive)
  • rejestrujemy umieszczając <receiver> w manifeście
    • zawiera filtr intencji wskazujący na zdarzenie
    • można alternatywnie zarejestrować w runtime metodą registerReceiver

public class XReceiver extends BroadcastReceiver  {

       public void onReceive(…)  {

       }

}

<receiver android:name=”.XReceiver”>

        <intent-filter>

              <action … />

        </intent-filter>

</receiver>

Proces i komponenty

Wszystkie komponenty w pojedynczej aplikacji współdzielą ten sam proces (aktywności, serwisy, broadcast receivery)

  • Typy komponentów nie rozwiązują w niejawny sposób problemów z wątkami
  • Wszystkie typy komponentów działają domyślnie w głównym wątku aplikacji

Zarządzanie zasobami

  • czas życia aktywności
    • niszczone po wywołaniu metody finish
    • typowane do destrukcji, gdy nie są na pierwszym planie
  • czas życia serwisu - w większości przypadków aż do jawnego zatrzymania
  • czas życia broadcast receiver-a
    • stworzony do uruchomienia na krótki okres czasu
    • typowany do destrukcji jeśli wykonuje się więcej niż 10 sekund
  • Czas życia procesu zależy od życia zawieranych przez niego komponentów
    • procesy Androida są zawsze zamykane przez system
    • proces bez aktywnych komponentów jest niszczony
  • Tradycyjne zarządzanie zasobami w oparciu o proces nie jest wystarczające
    • w procesie mogą pozostawać zasoby z pozamykanych już komponentów
    • Android zabezpiecza się przed takimi “wyciekami” wiążąc poprzez kontekst zasoby z komponentami
      • Kontekst jest blisko związany z typami komponentów
        • klasy Activity i Service dziedziczą z klasy Context
        • klasa BroadcastReceiver otrzymuje referencję na Context
      • Zapewnia dostęp do większości informacji o systemie, aplikacji, zasobach
        • dostęp do plików
        • lokalizacje w app storage
        • systemowe serwisy
        • informacje o aplikacji
      • Zapewnia, że zasoby są automatycznie sprzątane razem z komponentem

public class XReceiver extends BroadcastReceiver  {

       public void onReceive(Context context, Intent intent)  {

             boolean state = intent.getBooleanExtra(“state”, false);

             …

       }

}

XReceiver receiver = new XReceiver();

receiver.setLabel(“XXX”);

IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);

this.registerReceiver(receiver, filter);  //w kontekście aktywności

Powiązanie Context-u z czasem życia procesu

Każda instancja Context-u może dostarczyć referencję do Context-u dostępnego dla całej aplikacji

  • Context.getApplicationContext()
  • Powiązany zasób istnieje dopóki nie zostanie jawnie usunięty lub do czasu, gdy istnieje proces

XReceiver receiver = new XReceiver();

receiver.setLabel(“XXX”);

IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);

Context appContext = getApplicationContext();

appContext.registerReceiver(receiver, filter);  //w kontekście aplikacji

Obiekt aplikacji

Każda aplikacja ma pojedynczą instancję klasy Application

  • tworzona przed każdą aktywnością, serwisem, broadcast receiverem
  • jedna instancja istnieje przez życie procesu
  • możliwość dostarczenia własnej klasy
    • musi dziedziczyć po klasie Application
    • trzeba wyspecyfikować klasę w manifeście:  <application android:name=”.XApp”
  • odbiera notyfikacje callbacków
  • może przechowywać stan aplikacji

Stan aplikacji alternatywnie można przechowywać w singletonie.

 

Tworzenie responsywnych aplikacji

Główny wątek jest krytyczny dla user experience

  • zarządza interfejsem użytkownika
  • klucz do prawie każdego innego zachowania aplikacji (domyślnie także w serwisach, broadcast receiverach, callbackach cyklu życia, powrocie do strony startowej, wysyłaniu intencji)

Dwa główne mechanizmy Androida zabezpieczające główny wątek

  • Detekcja
    • Dialog ANR (App Not Responding)
    • klasa StrictMode
  • Odciążenie
    • klasa AsyncTask
    • wątki
    • loopers & handlers

StrictMode wykrywa rzeczy, które mogą wpłynąć na responsywność

  • potencjalnie blokujące operacje w sieci i local storage (dysk/flash)
  • ustawia politykę blokowania operacji (rodzaj operacji, sposób postępowania)
  • najczęściej używana metoda enableDefaults
    • wykrywane są wszystkie operacje w sieci i local storage w głównym wątku
    • logowana jest informacja

Tanie urządzenia z Android używają systemu plików free flash, który pozwala tylko na pojedynczą operację na pliku w zadanym czasie.

Zapis do pliku

FileOutputStream outStream = openOutStream(“file.dat”);

simpleWrite(outStream, “Xxx”);

closeOutStream(outStream);

 

protected FileOutputStream openOutStream(String filename) {

       FileOutputStream outStream = null;

       try {

              outStream = openFileOutput(filename, MODE_PRIVATE);

       }

       catch (IOException e) {

       }

       return outStream;

}

 

protected void closeOutStream(FileOutputStream outStream) {

       try {

              if (outStream != null)

                     outStream.close();

       } catch (IOException e)  {

       }

}

 

protected void simpleWrite(FileOutputStream outStream, String buffer) {

       try {

               outStream.write(buffer.getBytes());

       }  catch (IOException e)  {

       }

}

 

@Override

protected void onCreate(Bundle savedInstanceState) {

        …

        StrictMode.enableDefaults();

}

 

AsyncTask

Wspiera pracę w tle i interakcję z UI

  • najłatwiejszy sposób na przesunięcie pracy wpływającej na UI z głównego wątku
  • enkapsuluje bardzo popularny wzorzec pracy w tle
    • inicjuje pracę z wątku UI
    • wykonuje pracę w wątku w tle
    • podczas wykonywania okresowo aktualizuje UI w wątku UI
    • po zakończeniu pracy aktualizuje UI w wątku UI
  • dla stosunkowo krótkich operacji (kilka sekund lub mniej)

Jest to klasa abstrakcyjna

  • dostarcza trzy główne metody do nadpisania
  • doInBackground - wykonuje długie zadanie (konieczność nadpisania)
  • onProgressUpdate - dostarcza informacje o postępie dla usera
  • onPostExecute – dostarcza informacje dla użytkownika po zakończeniu

Jest to klasa generyczna z trzema typami parametrów

  • pierwszy - dane przekazywane do doInBackground (tablica wartości o zmiennej liczbie elementów)
  • drugi - dane przekazywane do onProgressUpdate (tablica wartości o zmiennej liczbie elementów)
  • trzeci - dane przekazywane do onPostExecute (pojedyncza wartość)
  • używamy typu Void dla nieużywanych typów parametrów

class MyWorker extends AsyncTask<Type1, Type2, Type3> { … }

 

Type1 foo1, foo2;

MyWorker m = new MyWorker();

m.execute(foo1, foo2);   //automatycznie wywołuje doInBackground i przekazuje do niej otrzymane parametry

void onProgressUpdate(Type2… values) {

}

void onPostExecute(Type3 value) {

}

 

Type3 doInBackground(Type1 … values) {

        …

        Type2 bar1, bar2;

        publishProgress(bar1, bar2);

        …

}

Asynchroniczny zapis pliku

final ProgressBar pb = (ProgressBar) findViewById(R.id.progressBarMain);

initializeProgressBar(pb);

displayStartedMessage();

 

new AsyncTask<String, Integer, Void>()  {

       @Override

       protected Void doInBackground(String… strings)  {

              String outputValue = strings[0];

              FileOutputStream outStream = openOutStream(“xxx.dat”);

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

                    slowWrite(outStream,  outputValue); 

                    publishProgress(i);                 

              }

             closeOutStream(outStream);

             return null;

       }

 

      @Override

       protected void onProgressUpdate(Integer… values)  {

              pb.setProgress(values[0]);

       }

 

      @Override

       protected void onPostExecute(Void aVoid)  {

              displayCompletionMessage();

              cleanupProgressBar(pb);

       }

}.execute(messageToWrite);

Wątki

Klasa Thread

  • tworzy oddzielny wątek
  • akceptuje referencję do interfejsu Runnable (implementacja interfejsu wykonuje pracę w metodzie run)
  • wykonuje metodę start instancji Thread przy początku wykonywania

public class MyWorker implements Runnable  {

        void run()  {

        }

}

 

MyWorker worker = new MyWorker();

Thread t = new Thread(worker);

t.start();

Należy zachować ostrożność z wątkami w aktywnościach

  • dodatkowe wątki nie zmienią cyklu życia Activity
  • powinniśmy wyczyścić wszystkie wątki, kiedy user opuszcza
    • w większości przypadków najlepszym wyborem jest onPause
    • onDestroy jest ostatnią szansą na uniknięcie nagłego zabicia
  • dla większości długo-wykonujących się tasków niezwiązanych z UI powinniśmy stosować serwisy

Loopers & handlers

  • Android podobnie jak Windows używa pętli message
  • do rozsyłania wiadomości do danego wątku
  • cała praca UI i większość housekeeping-u zachodzi na message loop głównego wątku
  • możliwość dodania dodatkowych wątków z pętlami message (często wykorzystywane przez systemowe serwisy)
  • Looper w HandlerThread

Monitorowanie lokalizacji, wysyłanie do handlera, wykonywanie w handlerze

public class MainActivity extends Activity  {

         …

         HandlerThread mHandlerThread;

         Handler mHandler;

         LocationListener mLocationListener;

         …

         private void btnCallRunnableOnHandlerOnClick(Button view)  {

               Handler handler = getMyHandler();

               final long callingThreadId = Thread.currentThread().getId();

 

               handler.post(new Runnable()  {

                       @Override

                        public void run()  {

                                long runningThreadId = Thread.currentThread().getId();

                                String msgText = “…”;

                                …

                        }

                });

         }

         private void btnStartLocationMonitoringOnClick(Button view)  {

                LocationManager lm = (LocationManager) getSystemService(LOCATION_SERVICE);

                mLocationListener = new MyLocationListener();              

                HandlerThread handlerThread = getHandlerThread();

                Looper looper = handlerThread.getLooper();

                lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER /*provider*/, 10000 /*min czas*/,

                0.0f /* min odległość*/,  mLocationListener,  looper);   //opcjonalnie: looper

         }

         private void btnStopLocationMonitoringOnClick(Button view)  {

                 stopLocationMonitoring();

         }

         private void stopLocationMonitoring() {

                 if (mLocationListener != null)  {

                         LocationManager lm = (LocationManager) getSystemService(LOCATION_SERVICE);

                         lm.removeUpdates(mLocationListener);

                         mLocationListener = null;

                 }

         }

         private void btnSendMessageToHandlerOnClick(Button view)  {

                 Handler handler  = getMyHandler();

                 long threadId = Thread.currentThread().getId();

                 …

                 Message msg = handler.obtainMessage(0, messageText);

                 msg.sendToTarget();

         }

         private HandlerThread getHandlerThread()  {

                if  (mHandlerThread == null)  {

                        mHandlerThread = new HandlerThread(“HandlerThread”);

                        mHandlerThread.start();

                }

                return mHandlerThread;

         }

         private Handler getMyHandler()  {

                 if  (mHandler  == null)  {

                          HandlerThread handlerThread = getHandlerThread();

                          mHandler  = new MyHandler(handlerThread.getLooper());

                 }

                 return mHandler;

         }

         @Override

          protected void onPause()  {

                  super.onPause();

                  if (mHandlerThread != null)  {

                         stopLocationMonitoring();

                         mHandlerThread.quit();

                         mHandlerThread = null;

                  }                 

          }

}

 

public class MyHandler extends Handler  {

        public MyHandler(Looper looper)  {

               super(looper);

        }

        @Override

        public void handleMessage(Message msg)  {

                String messageText = (String) msg.obj;

                long threadId = Thread.currentThread().getId();

                …

        }

}

Serwisy cz.1

Serwisy w tle

  • niewiele mniejszy priorytet niż bieżąca aktywność użytkownika
  • komponent w aplikacji, nie osobna aplikacja

Implementacja

  • dziedziczymy po klasie Service
  • klasa Service rozszerza klasę Context (dostęp do większości metod Context-u tak jak Activity)
  • wymagana implementacja onBind  (często zwraca null)
  • serwis ma osobny cykl życia
    • onCreate - kiedy serwis jest tworzony
    • onDestroy - przy zamykaniu
    • onStartCommand - przy każdym Intent przekazanym do serwisu
  • w manifeście lista z elementem <service>

public class MyService extends Service  {

       public IBinder onBind()  {  return null;  }

       public void onCreate() {  … }

       public void onDestroy()  { … }

       public void onStartCommand(…)  { … }

}

<service

       android:name=”.MyService”>

       …

</service>

Android Studio

  • New Android Component –> Service (tworzona jest klasa i wpis w manifeście)

public class MyService extends Service  {

        HandlerThread mHandlerThread;

        Handler mHandler;

 

        public IBinder onBind(Intent intent)  {

               return null;

        }

 

        @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;

                                doWork(intent);

                        }

                };

        } 

 

        @Override

        public void onDestroy()  {

                mHandlerThread.quit();

                mHandlerThread = null;

                mHandler = null;

        }   

 

        @Override

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

               Message msg = mHandler.obtainMessage();

               msg.obj  = intent;

               msg.sendToTarget();

               return  0;

        }

 

        private void doWork(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 class MainActivity extends Activity {

        …

        private void btnDoLongRunningWorkOnClick(Button button)  {

                String messageText  = “abcd”;

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

                 intent.putExtra(“MessageText”,  messageText);

                startService(intent);                

        }

}

Aktualny proces:    android.os.Process.myPid()

Aktualny wątek:   Thread.currentThread().getId();