Im bardziej w las tym więcej drzew. Podobnie jest z aplikacjami na Androida, im bardziej rozbudowana aplikacja tym więcej funkcji. W tym gąszczu funkcji na pewno są serwisy, które wykonują różnorakie rzeczy, np aktualizacja informacji. O ile stworzenie powiadomień nie jest trudne to aktualizacja aktywności może już spowodować pewne problemy. W jaki sposób przekazać dane z serwisu do aktywności dane? Dziś postaram się odpowiedzieć na to pytanie w jaki sposób przekazywać dane. W tej części pokaże Ci podstawowe metody komunikacji między serwisem a aktywnością w androidzie, w drugiej części znajdziesz metody bardziej zaawansowane.
1. Założenia.
Aby lepiej zrozumieć komunikację przedstawię założenia projektu. Mamy serwis, który co sekundę zwiększa liczbę o 1. Nasz serwis może wyglądać tak:
public class LocalService extends Service { private boolean mStartCounting = false; private int mCounter = 0; private Context mContext; @Override public void onCreate() { super.onCreate(); mContext = getApplicationContext(); if (!mStartCounting) startCounting(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); } private void startCounting() { mStartCounting = true; new Thread(new Runnable(){ public void run() { while (mStartCounting) { SystemClock.sleep(1000); mCounter++; } } }).start(); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } }
Teraz musimy przesłać mConuter z serwisu do aktywności. Natomiast z aktywności zatrzymujemy liczenie.
2. Skorzystaj z Intenta.
Najprostsza forma przekazywania danych między aktywnościami, ale także między service a activity to skorzystanie z Intenta. Aby wysłać informacje o zatrzymaniu liczenia, z aktywności wykonujemy taki kod:
Intent sendIntent = new Intent(MainActivity.this,LocalService.class); sendIntent.putExtra("startCounting", false); startService(sendIntent);
Aby odebrać dane z intencji w serwisie to w metodzie onStartCommand() dodajemy:
if (intent.getExtras() != null) { mStartCounting = intent.getBooleanExtra("startCounting", false); }
Jeszcze pozostało nam poinformowanie aktywności o liczbie. W serwisie w pętli while dodajemy:
Intent intent = new Intent(mContext, MainActivity.class); intent.putExtra("mCounter",mCounter); startActivity(intent);
Zaś w aktywności odbieramy intencję w ten sposób:
if (getIntent().getExtras() != null) { int mCounter = getIntent().getIntExtra("mCounter",0); Log.d("TAG", "Activity: "+mCounter ); }
Użyj tej metody, jeśli przekazujesz prymitywne dane. Możesz także przekazywać obiekty, które implementuje Serializable. Serializable niestety jest wolny dlatego lepszym rozwiązaniem jest skorzystanie z Parcelable (sporo kodu do pisania). Kolejną wadą tej metody jest to, że aktywność będzie tworzona na nowo lub wznawiana w zależności od cyklu życia aktywności. Tą metodę warto stosować gdy faktycznie musimy wyświetlić ekran po zakończeniu działania jakiejś czynności. No chyba, że preferujesz lepszą praktykę i skorzystasz z powiadomień. Jeżeli masz bardziej złożone obiekty i chcesz jednak skorzystać z intencji to użyj gson, aby przekształcić obiekt w JSON.
3. Statyczna metoda, zmienna.
Innym sposobem poinformowania serwisu o zaprzestaniu liczenia jest stworzenie statycznej metody lub zmiennej. W aktywności wywołujemy taki kod:
LocalService.setCounting(false);
A w serwisie tworzymy taką metodę:
public static void setCounting(boolean counting){ mStartCounting = counting; }
Wtedy naszą zmienną musimy zmienić na statyczną:
private static boolean mStartCounting = false;
Nic nie stoi na przeszkodzie abyśmy zrobili ją publiczną i zmieniali wartość bezpośrednio w aktywności.
Minus tego rozwiązania będzie tworzenie geterów i seterów – kiepskie rozwiązanie. Na pewno ten sposób nie jest bezpieczny, ale już lepiej ponieważ aktywność nie jest wielokrotnie „przetwarzana”.
Oczywiście też możesz wysłać instancję aktywności przez Intent i odwołać się do metod prywatnych, ale czy to ma sens? Wątpię, są o wiele lepsze metody.
4. Podłącz się do serwisu.
Najczęstszym sposobem i też bezpiecznym jest podłączenie się do usługi. Jeśli Twoja usługa jest używana przez lokalną aplikację i nie musi pracować pomiędzy procesami, możesz zaimplementować własną klasę Binder, która zapewnia klientowi (aktywności) bezpośredni dostęp do publicznych metod w usłudze. Dodajmy do naszej usługi taki kod:
private final IBinder mBinder = new LocalBinder(); @Nullable @Override public IBinder onBind(Intent intent) { return mBinder; } public class LocalBinder extends Binder { LocalService getService() { return LocalService.this; } } public void setCounting(boolean counting){ mStartCounting = counting; }
Binder wykonuje jedną z następujących czynności:
- Zawiera publiczne metody, które może wywoływać klient,
- Zwraca bieżącą instancję usługi, która ma publiczne metody i może je wywołać klient,
- Zwraca instancję innej klasy hostowanej przez usługę i posiada również publiczne metody, które mogą być wywołane przez klienta.
Teraz zobacz jak zastosować to w klasie z aktywnością:
private LocalService mService; private boolean mBound = false; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { LocalService.LocalBinder binder = (LocalService.LocalBinder) service; mService = binder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName name) { mService = null; mBound = false; } }; @Override protected void onStop() { super.onStop(); if (mBound) { unbindService(mConnection); mBound = false; } }
Aby podłączyć się do usługi i wywołać zatrzymanie liczenia robisz tak:
bindService(intent, mConnection, Context.BIND_AUTO_CREATE); mService.setCounting(false);
Podłączając się do usługi, metoda getService() zwraca instancję usługi. Jak już mamy taki obiekt możemy wywoływać publiczne metody, które znajdują się w usłudze. Kod powyższy pozwala Ci sterować z poziomu aktywności usługą. Natomiast jeśli chcesz uzyskać informację od serwisu o liczbie możesz zastosować implementacje interfejsu. W usłudze dodaj taki kod:
private ServiceCallbacks serviceCallbacks; public interface ServiceCallbacks{ void showNumber(int number); }
W klasie LocalBinder dodaj metodę:
public void setCallbacks(ServiceCallbacks callbacks) { serviceCallbacks = callbacks; }
Teraz w aktywności w metodzie onServiceConnected() pobierz callback’a:
binder.setCallbacks(serviceCallbacks);
I jeszcze implementacja:
private LocalService.ServiceCallbacks serviceCallbacks = new LocalService.ServiceCallbacks() { @Override public void showNumber(int number) { Log.d("TAG", "Activity: "+number ); } };
Ok, masz teraz komunikację serwisem a aktywnością i odwrotnie. Pamiętaj, że usługa i klient muszą znajdować się w tej samej aplikacji.
5. BroadcastReceiver.
Jeszcze innym sposobem na komunikację miedzy serwisem a aktywnością jest BroadcastReceiver. Ta metoda przydaje się gdy chcemy nadsłuchiwać zdarzeń w systemie, a serwis te zdarzenia przetwarza na przykład sprawdza wiadomości gdy internet jest dostępny. Mamy dwa rodzaje rejestracji receivera: statycznie przez plik manifestu oraz dynamicznie w kodzie. Poniżej zobaczysz przykład broadcastreceiver zarejestrowanego dynamicznie. Będzie działać tylko wtedy gdy aplikacja będzie działać. W usłudze dodajemy:
final String mBroadcastStringAction = "net.myenv.broadcast.mStartCounting"; private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(mBroadcastStringAction)) { mStartCounting = false; } } }; private void setupReceiver() { IntentFilter mIntentFilter = new IntentFilter(); mIntentFilter.addAction(mBroadcastStringAction); registerReceiver(mReceiver,mIntentFilter); }
W metodzie onCreate() wywołaj setupReceiver(). Jeżeli chcesz wyrejestrować to wywołujesz:
unregisterReceiver(mReceiver);
Natomiast w aktywności wywołujesz zatrzymanie liczenia w ten sposób:
final String mBroadcastStringAction = "net.myenv.broadcast.mStartCounting"; Intent broadcastIntent = new Intent(); broadcastIntent.setAction(mBroadcastStringAction); broadcastIntent.putExtra("startCounting", false); sendBroadcast(broadcastIntent);
Szybkie i proste, prawda? Masz teraz komunikację aktywność –> serwis. Aby z serwisu pobierać dane robisz podobnie tylko w drugą stronę. Ze względu na pewne ograniczenia wprowadzone w Android O zalecam korzystanie z dynamicznych broadcasterów.
6. Inne Metody.
Oprócz wyżej wymienionych metod możesz jeszcze skorzystać z takich opcji jak:
- możesz stworzyć publiczną klasę ze statycznymi obiektami i na nich oprzeć komunikację,
- skorzystanie z bazy danych,
- shared preferences,
- zapisywanie danych na dysku.
O ile powyższe metody są ok, to ich zastosowanie jest nie zawsze poprawne. Ponieważ w aktywności i serwisie musiałbyś zaimplementować odczytywanie tych wartości co jakiś określony czas, w powyższym przypadku co sekundę. Więc te metody nie zawsze mogą się sprawdzić pod względem wydajności. Pamiętaj o tym też, że system Android może unicestwić aktywnoś
7. Podsumowanie.
To nie koniec metod komunikacji między serwisem a aktywnością w androidzie. Jest jeszcze sporo innych, tutaj ogranicza nas tylko wyobraźnia. Gdybym chciał Ci przedstawić wszystkie wpis byłby ogromy, dlatego uznałem, że podzielę to zagadnienie na dwie części. W tej części przedstawiłem tylko podstawowe i najczęściej spotykane metody. W kolejnej części poznasz bardziej zaawansowane metody.
Pamiętaj, że to od Ciebie i Twojego projektu zależy, który sposób wykorzystasz. Nie ma jednej uniwersalnej metody komunikacji między serwisem a aktywnością w androidzie.