woshidan's blog

そんなことよりコードにダイブ。

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);
    }