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.
