IntentServiceで動かしているバックグラウンドジョブの進捗や結果をLocalBroadCastReceiverで通知する
表題のことがやりたいぞ、というわけで下記あたりを参考に素振りしました。
Reporting Work Status | Android Developers
AndroidのIntentServiceを使って非同期処理を行う方法 | TechBooster
手順
- バックグラウンドジョブを実行するIntentServiceを作る
- IntentServiceにIntentを飛ばす
- IntentServiceから進捗通知や完了通知をLocalBroadCastで飛ばす
- 通知を受け取ったときのリアクションをするBroadcastReceiverを書く
- LocalBroadcastManagerに上の通知を受け取るためのIntentFilterとBroadcastReceiverを登録する
できあがりの全体はこちらです。
バックグラウンドジョブを実行するIntentServiceを作る
IntentServiceクラスを作成する。
Intentで呼び出された時はonHandleIntent
メソッドが呼び出される。
このメソッド内でIntentに付与されたActionやDataによって違う処理を行わせることもできる。
@Override protected void onHandleIntent(Intent intent) { if (intent != null) { final String action = intent.getAction(); Log.d("AddDataService", action); if (ACTION_ADD_DATA.equals(action)) { handleActionAddData(); } } }
肝心の処理内容はhandleActionXXX
に書くのがお作法っぽい。
今回は handleActionAddData
メソッドに書いています。
一部説明し直しますが、肝心の処理はダウンロードしてきたデータを追加する的なのを真似したくて、数秒待った後、追加データっぽいデータをIntentにつけて送るよ、みたいな感じの処理です。
というか、適当だー。
private void handleActionAddData() { try { Thread.sleep(10000); } catch (Exception e) { Log.e("handleActionAddData", e.toString()); } List<Item> additionalData; additionalData = Arrays.asList(new Item("additional1"), new Item("additional2"), new Item("additional3")); Parcelable[] sendingData = new Parcelable[additionalData.size()]; sendingData = additionalData.toArray(sendingData); ArrayList<Parcelable> addedItems = new ArrayList<Parcelable>(); for (Item data : additionalData) { Bundle bundle = new Bundle(); addedItems.add(data); Parcelable[] sendData = new Parcelable[addedItems.size()]; sendData = addedItems.toArray(sendData); Intent intent = new Intent(); intent.putExtra(ListActivity.ADDED_ITEM_TITLE, data.getTitle()); intent.putExtra(ListActivity.ADDED_ITEM_LIST, sendData); intent.putExtra(ListActivity.ADDING_ITEM_LIST, sendingData); Log.d("AddDataService", data.getTitle()); intent.setAction(ListActivity.ACTION_ADD_DATA); LocalBroadcastManager.getInstance(getBaseContext()).sendBroadcast(intent); try { Thread.sleep(2000); } catch (Exception e) { Log.e("handleActionFoo", e.toString()); } } Intent intent = new Intent(); intent.setAction(ListActivity.ACTION_COMPLETE_ADD_DATA); LocalBroadcastManager.getInstance(getBaseContext()).sendBroadcast(intent); }
また、AndroidManifest.xmlにも下記のようにIntentServiceをservice要素として追加。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.woshidan.localbroadcastreceivertest"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> ... <service android:name=".AddDataService" android:exported="false"></service> </application> </manifest>
IntentServiceにIntentを飛ばす
startServiceメソッドでIntentServiceメソッドにサービスを飛ばします。
if (id == R.id.action_add_items) { Intent intent = new Intent(this, AddDataService.class); intent.setAction(AddDataService.ACTION_ADD_DATA); startService(intent); return true; }
IntentServiceから進捗通知や完了通知をLocalBroadCastで飛ばす
1つデータが進むたびであったり、すべてのデータが処理し終わるタイミングでLocalBroadcastManagerからIntentを飛ばすように書きます。
// 1つデータの処理が終わるたびに飛ばす感じ for (Item data : additionalData) { Bundle bundle = new Bundle(); addedItems.add(data); Parcelable[] sendData = new Parcelable[addedItems.size()]; sendData = addedItems.toArray(sendData); Intent intent = new Intent(); intent.putExtra(ListActivity.ADDED_ITEM_TITLE, data.getTitle()); intent.putExtra(ListActivity.ADDED_ITEM_LIST, sendData); intent.putExtra(ListActivity.ADDING_ITEM_LIST, sendingData); Log.d("AddDataService", data.getTitle()); intent.setAction(ListActivity.ACTION_ADD_DATA); LocalBroadcastManager.getInstance(getBaseContext()).sendBroadcast(intent); try { Thread.sleep(2000); } catch (Exception e) { Log.e("handleActionFoo", e.toString()); } }
// for文を抜けて、すべてのデータが終り送ったという意味のActionをセットしてIntentを飛ばす Intent intent = new Intent(); intent.setAction(ListActivity.ACTION_COMPLETE_ADD_DATA); LocalBroadcastManager.getInstance(getBaseContext()).sendBroadcast(intent);
通知を受け取ったときのリアクションをするBroadcastReceiverを書く
先ほど飛ばしたLocalBroadCastを受け取るためのBroadcastReceiverを書きます。
package com.example.woshidan.localbroadcastreceivertest; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.support.design.widget.Snackbar; import android.util.Log; /** * Created by woshidan on 2016/01/11. */ public class NoticeCompleteReceiver extends BroadcastReceiver { private ListActivity mActivity; public NoticeCompleteReceiver(ListActivity activity) { mActivity = activity; } @Override public void onReceive(Context context, Intent intent) { mActivity.runOnUiThread(new Runnable() { @Override public void run() { Snackbar.make(mActivity.findViewById(R.id.fab), "完了しました", Snackbar.LENGTH_SHORT).show(); } }); } }
package com.example.woshidan.localbroadcastreceivertest; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.support.design.widget.Snackbar; import android.util.Log; import android.widget.Toast; /** * Created by woshidan on 2016/01/11. */ public class NoticeProgressReceiver extends BroadcastReceiver { private ListActivity mActivity; public NoticeProgressReceiver(ListActivity activity) { mActivity = activity; } @Override public void onReceive(Context context, Intent intent) { int doneCount = intent.getParcelableArrayExtra(ListActivity.ADDED_ITEM_LIST).length; int toDoCount = intent.getParcelableArrayExtra(ListActivity.ADDING_ITEM_LIST).length; final Item item = new Item(intent.getStringExtra(ListActivity.ADDED_ITEM_TITLE)); Snackbar.make(mActivity.findViewById(R.id.fab), doneCount + "/" + toDoCount, Snackbar.LENGTH_INDEFINITE) .show(); mActivity.runOnUiThread(new Runnable() { @Override public void run() { mActivity.items.add(item); mActivity.recyclerView.getAdapter().notifyDataSetChanged(); } }); } }
ここでポイントというか、通知の際に途中まで追加されたデータも反映したいといった場合、onReceiveメソッド内でふつうにSnackBar,Toast以外のViewを操作しようとするとUnSupported Operationで落ちてしまいます。
なので、ActivityのUIスレッドに実行させたいRunnableオブジェクトを渡す形にしています。
LocalBroadcastManagerに上の通知を受け取るためのIntentFilterとBroadcastReceiverを登録する
BroadCastReceiverのインスタンスとIntentFilterのインスタンスを用意して、LocalBroadcastManagerに登録します。 onStart()で登録して、onStop()で登録を解除するといいと思います。
@Override public onStart() { super.onStart(); IntentFilter noticeProgressFilter = new IntentFilter(); noticeProgressFilter.addAction(ACTION_ADD_DATA); noticeProgressReceiver = new NoticeProgressReceiver(this); localBroadcastManager.registerReceiver(noticeProgressReceiver, noticeProgressFilter); IntentFilter noticeCompleteFilter = new IntentFilter(); noticeCompleteFilter.addAction(ACTION_COMPLETE_ADD_DATA); noticeCompleteReceiver = new NoticeCompleteReceiver(this); localBroadcastManager.registerReceiver(noticeCompleteReceiver, noticeCompleteFilter); } @Override public onStop() { super.onDestroy(); localBroadcastManager.unregisterReceiver(addedReceiver); localBroadcastManager.unregisterReceiver(completeReceiver); }