Broadcast Receivers.

Wstęp

BroadcastReceiverjest jedną z sześciu cegiełek dzięki którym możemy budować aplikacje na platformę Android(mamy Activity, Service, Content Providers, Intents, Broadcast Receviers oraz Notifications). BroadcastReceiver pozwala nam na odbieranie powiadomień(Systemu bądź innej aplikacji) wewnątrz naszej aplikacji. Takim powiadomieniem może być na przykład informacja o nowej wiadomości SMS bądź rozładowanej baterii. Jednak możliwości tego mechanizmu nie kończą się jedynie na możliwości odbierania powiadomień(i oczywiście reagowania na nie), ponieważ my także możemy nadać własne powiadomienie na przykład w momencie kiedy nasza aplikacja skończy pobieranie jakiegoś pliku.

Żeby zbudować nasz własny BroadcastReceiver musimy wykonać dwie czynności:

  • Stworzyć BroadcastReceiver który będzie reagował na powiadomienie.

  • Zarejestrować go.

Budujemy BroadcastReceiver

Tworzymy nową klasę która rozszerzy klasę BroadcastReceiver oraz implementujemy metodę onReceive().

public class FirstReveiver extends BroadcastReceiver{

    @Override
    public void onReceive(Context arg0, Intent arg1) {
    //tutaj operujemy na naszym powiadomieniu, dane które zostały nam przekazane wyciągamy z parametru arg1.
    }

}

Możemy stworzyć nasz receiver dynamicznie, w kodzie - co wiąże się także z obowiązkiem wyrejestrowaniem go.

public class MainActivity extends Activity {

    private IntentFilter filter =
            new IntentFilter("com.example.broadcastsample.PRZYKLADOWY");

    private BroadcastReceiver broadcast = new BroadcastReceiver(){
        @Override
        public void onReceive(Context arg0, Intent arg1) {
        //Tutaj tak samo jak w poprzednim przykładzie.
        }
    };

    @Override  
    public void onResume() {
        super.onResume();
        registerReceiver(broadcast, filter);
     //nie interesuje nas tutaj czym jest filter, widzimy jednak że rejestrujemy nasz receiver w kodzie.
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    ...

    @Override
    public void onPause() {
        unregisterReceiver(broadcast); 
        // trzeba zawsze po sobie posprzątać w tym przypadku wyrejestrować receiver.
        super.onPause(); 
    }

Dobrą praktyką jest rejestrowanie i wyrejestrowywanie receiver'a w metodachonResume() oraz onPause()- daje nam to pewność że nasłuchujemy tylko kiedy nasza aplikacja jest w "centrum uwagi"(czyli działa).

Rejestracja

Są dwa sposoby na rejestracje, w kodzie oraz pliku manifestu(AndroidManifest.xml). Rejestracja w kodzie aplikacji(tak jak zostało to zrobione w poprzednim przykładzie) wiąże się z tym że nasza aplikacja nasłuchuje tylko gdy jest aktywna. W przypadku rejestracji w manifeście nasz BroadcastReceiver zostanie odpalony nawet gdy aplikacja aktualnie nie jest aktywna, czyli nasłuchujemy bez przerwy.

Zakładając że stworzyliśmy sobie klasę FirstReveiver, rejestracja w pliku AndroidManifest wyglądałaby następująco:

<application
   android:icon="@drawable/ic_launcher"
   android:label="@string/app_name"
   android:theme="@style/AppTheme" >

   <receiver android:name="FirstReceiver">
      <intent-filter>
         <action android:name="example.przykladowy.filtr">
      </action>
      </intent-filter>
   </receiver>

</application>

Rozpoczynamy tagiem <receiver />, android:name przyjmuje nazwę naszej klasy(receivera), w naszym przypadku jest to właśnie FirstReceiver. Ważny jest także filtr, jest to wartość dla której dla której zamierzamy reagować. Jest kilka które generuje system, mogą to być na przykład:

  • android.intent.action.BATTERY_LOW - wskazuje na słabą baterie.
  • android.intent.action.BATTERY_OKAY
  • android.intent.action.CALL - wykonywana jest rozmowa.
  • android.provider.Telephony.SMS_RECEIVED - otrzymaliśmy sms'a

Jest ich oczywiście więcej, to tylko przykłady.

Rejestracja w kodzie została pokazana wyżej, ale opiszmy sobie to szerzej:

public class MainActivity extends Activity {

    private IntentFilter filter =
            new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
            //Ustawiliśmy sobie filtr na reakcje na nadchodzącego SMS'a

    private BroadcastReceiver broadcast = new BroadcastReceiver(){
        @Override
        public void onReceive(Context arg0, Intent arg1) {
            Toast.makeText(arg0, "Masz nowego sms'a", Toast.LENGTH_LONG).show();
        }
    };

    @Override  
    public void onResume() {
        super.onResume();
        registerReceiver(broadcast, filter);

    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

  @Override
    public void onPause() {
        unregisterReceiver(broadcast);
        // pamiętaj żeby wyrejestrować receivera !
        super.onPause(); 
    }
}

Metoda registerReceiver() przypisuje filtr do receivera, w tym przypadku na nadejście sms'a zareagujemy wywołując Broadcast przypisany do zmiennej broadcast. Kiedy sms nadejdzie, pokaże się powiadomienie typu Toast.

Żeby ten przykład zadziałał, musimy jednak dodać w manifeście pozwolenie na odczytywanie sms'ów.

<uses-permission android:name="android.permission.RECEIVE_SMS"/> dodajemy to przed tagiem <application>.

Jeżeli korzystasz z Emulatora znajdującego się w Eclipse, sms możesz wysłać wchodząc w window->show views->other->Emulator control.

Wysyłanie powiadomienia

Tak jak wspomniałem wyżej, my także możemy wysłać powiadomienie, musimy tylko stworzyć Intent, ustawić dla niego akcje("sample.przykladowa.akcja") oraz wysłać go za pomocą metody sendBroadcast(). Załóżmy że nasz filtr zamiast dla android.provider.Telephony.SMS_RECEIVED nasłuchuje dla wartości **android.nasze.powiadomienie,**wtedy możemy go wywołać na przykład tak:

Intent intent = new Intent();
intent.setAction("android.nasze.powiadomienie");
sendBroadcast(intent);

Zadziała dla receivera zarejestrowanego w kodzie jak i dla tego zarejestrowanego w manifeście.

OrderedBroadcast

Android udostępnia nam możliwość obierania powiadomień w pewnej kolejności - czyli priorytetem który ustawiamy sami.

Schemat jest prosty: powiadomienie ->pierwsz receiver ->drugi receiver ->trzeci receiver ->ostatni receiver.

Jednak ostatnim jest ten którego zarejestrujemy podczas wysyłania powiadomienia. To najlepiej zobaczyć na przykładzie. Zbudujemy aplikacje która zareaguje na powiadomienie wywołując po kolei trzy powiadomienia + ten który zarejestrujemy podczas wysyłania tego powiadomienia.

Zaczniemy od budowy trzech BroadcastReceiver'ów(czyli klas rozszerzających BroadcastReceiver).

public class FirstReceiver extends BroadcastReceiver{

    @Override
    public void onReceive(Context arg0, Intent arg1) {
        Toast.makeText(arg0, "Pierwszy", Toast.LENGTH_LONG).show();
    }

}
public class SecondReceiver extends BroadcastReceiver{

    @Override
    public void onReceive(Context arg0, Intent arg1) {
        Toast.makeText(arg0, "Drugi", Toast.LENGTH_LONG).show();
    }

}
public class ThirthReceiver extends BroadcastReceiver{

    @Override
    public void onReceive(Context arg0, Intent arg1) {
        Toast.makeText(arg0, "Trzeci", Toast.LENGTH_LONG).show();
    }

}

Następnie rejestrujemy je w pliku AndroidManifest tak ja robiliśmy to wcześniej, jednak dodatkowo dla każdego dodajemy priorytet z którym będą wywoływane będą poszczególne receivery(im wyższy priorytet tym szybciej zostanie wywołany).

        <receiver android:name="FirstReceiver" >
            <intent-filter android:priority="3">
                <action android:name="com.example.broadcastsample.SHOW_TOAST" >
                    </action>
            </intent-filter>
        </receiver>
        <receiver android:name="SecondReceiver" >
            <intent-filter android:priority="2">
                <action android:name="com.example.broadcastsample.SHOW_TOAST" >
                    </action>
            </intent-filter>
        </receiver>
        <receiver android:name="ThirthReceiver" >
            <intent-filter android:priority="1">
                <action android:name="com.example.broadcastsample.SHOW_TOAST" >
                    </action>
            </intent-filter>
        </receiver>

Teraz już orientujemy się w kodzie i wiemy że filtr nasłuchuje wartości "com.example.broadcastsample.SHOW_TOAST ", widzimy też dodatkowy parametr android:priority, to właśnie nasz priorytet. FirstReceiver ma najwyższy priorytet więc zostanie wywołany pierwszy, dalej SecondReceiver na końcu ThirthReceiver.

Na koniec, żeby uruchomić to w miarę wygodny sposób, tworzymy sobie np. przycisk Ja ustawie dla niego metodę o nazwie customAction a w jej ciele wrzucę wysyłanie powiadomienia. Wcześniej robiliśmy to przy pomocy metody sendBroadcast, teraz zrobimy to przy pomocy metody sendOrderedBroadcast(). Oczywiście przykład:

	public void customAction(View v) {
		Intent intent = new Intent();
		intent.setAction("com.example.broadcastsample.SHOW_TOAST");
        sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
            	Toast.makeText(context, "Ostatni, zarejestrowany w kodzie", Toast.LENGTH_LONG).show();
            }
        }, null, Activity.RESULT_OK, null, null);
	}

Na pierwszy rzut oka może się to wydać dziwne i trochę skomplikowane. W przypadku metody sendOrderedBroadcast musimy zapewnić intent z ustawioną akcją - tak jak wcześniej - oraz BroadcastReceiver który zostanie wywołany ostatni, nie ważny jest tutaj priorytet.

Jeżeli nie interesuje nas wywołanie ostatniego receivera który tworzyliśmy anonimowo w metodzie sendOrderedBroadcast, to możemy oczywiście wywołać nasze powiadomienie tak jak robiliśmy to wcześniej czyli:

	public void customAction(View v) {
		Intent intent = new Intent();
		intent.setAction("com.example.broadcastsample.SHOW_TOAST");
		sendBroadcast(intent);
	}

Priorytet zostanie zachowany.

Dyskusja i komentarze

Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.