앱을 개발하면서 서비스를 구현하고 있는데 Broadcast
, Reciver
, PendingIntent
를 사용하게 되었다. 이것에 대해 정리해보자
안드로이드는 broadcast 메세지를 안드로이드 시스템, 앱 내/외부와 주고 받을 수 있다. 그리고 이런 로직을 정의하는 것이 우리의 역할이다.
앱에 특정 broadcast에 대한 receive들을 정의할 수 있다. receive를 정의해두고 broadcast 메세지를 날리면 os가 자동으로 broadcast들을 receive들에게 라우팅해준다.
broadcast 메시지는 action
문자열로 구분되어 있는Intetnt
객체에 포함되어 전달된다. 물론 Intent
에 여러가지 추가 정보를 넣을 수 있다. bundle
에 넣어서 전달하자.
broadcast를 받는 것은 2가지 방법이 있다. manifest
에 정의되어 있는 receiver로 받거나 context
에서 등록된 receiver로 받는 방법이 있다.
manifest
에서는 아래와 같이 등록하자.
<receiver android:name=".MyBroadcastReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
</intent-filter>
</receiver>
그리고 다음 java 코드도 필요하다. 손자취 앱은 이 방식을 선택했다.
public class MyBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "MyBroadcastReceiver";
@Override
public void onReceive(Context context, Intent intent) {
StringBuilder sb = new StringBuilder();
sb.append("Action: " + intent.getAction() + "\n");
sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
String log = sb.toString();
Log.d(TAG, log);
Toast.makeText(context, log, Toast.LENGTH_LONG).show();
}
}
시스템 패키지 매니저는 receiver를 앱이 설치 되는 시점에 등록한다. 이를 통해 앱이 실행되고 있지 않더라도 receiver를 통해 앱을 실행할 수 있는 entry point
가 된다고 한다.
시스템은 각 broadcast 마다 BroadcastReceiver
를 생성해준다고 한다.
일단 BroadcastReceiver
인스턴스를 생성한다.
BroadcastReceiver br = new MyBroadcastReceiver();
IntentFilter
를 생성하고 registerReceiver()
를 호출하여 recevier
를 등록한다.
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
this.registerReceiver(br, filter);
context-registered 방식을 취하면 broadcast를 등록한 context가 살아있을 때마 수신할 수있다. 그러니까 앱이 종료되면 broadcast
를 받을 수 없다.
unregisterReceiver()
메소드를 호출하여 broadcast
를 그만 받을 수 있다.
BroadcastReceiver
는 실행중이던 실행중이지않던 자신이 포함하고 있는 프로세스의 state
에 영향을 받는다. 그래서 시스템에 의해 종료될 수 있다. 예를들어 프로세스가 receiver를 실행시키면 이녀석은 foreground
프로세스로 간주된다. 그래서 메모리가 부족하면 시스템에 의해 종료된다.
그렇기 때문에 많은 작업 시간이 필요한 작업을 broadcast receiver에서 백그라운드 스레드를 실행시키지 말자 동작을 수행하다가 프로세스가 죽으면 안되니까.
그리고 이를 방지하기 위해서는 goAsync()
메소드를 사용하거나 JobService
, JobScheduler
를 활용하여 스케쥴링을하자.
아래의 코드를 참고하자. 손자취 앱에서도 AsyncTask
를 활용하여 SQLite 로직을 처리했다.
public class MyBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "MyBroadcastReceiver";
@Override
public void onReceive(Context context, Intent intent) {
final PendingResult pendingResult = goAsync();
Task asyncTask = new Task(pendingResult, intent);
asyncTask.execute();
}
private static class Task extends AsyncTask<String, Integer, String> {
private final PendingResult pendingResult;
private final Intent intent;
private Task(PendingResult pendingResult, Intent intent) {
this.pendingResult = pendingResult;
this.intent = intent;
}
@Override
protected String doInBackground(String... strings) {
StringBuilder sb = new StringBuilder();
sb.append("Action: " + intent.getAction() + "\n");
sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
String log = sb.toString();
Log.d(TAG, log);
return log;
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
// Must call finish() so the BroadcastReceiver can be recycled.
pendingResult.finish();
}
}
}
3가지 방법을 통해 broadcast를 보낼 수 있다.
sendOrderedBroadcast()
sendBroadcast()
LocalBroadcastManager.sendBroadcast()
Intent intent = new Intent();
intent.setAction("com.example.broadcast.MY_NOTIFICATION");
intent.putExtra("data","Notice me senpai!");
sendBroadcast(intent);
손자취 앱의 경우 PendingIntent
에 getBroadcast()
메소드를 통해 boradcast를 등록하고 notifiation
에 발행해서 전송했다.