vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

【Android】Loading画面とAsyncTask(和歌山トイレマップで遊んでみる 6)

はじめに

今回はMap上にマーカーを置くときに表示するLoading画面と、非同期で処理するためのAsyncTaskについて。

Loading画面

ちょっとわかりづらいですが、以下のように画面の中央でクルクルと回るLoading画面を作成します。

f:id:mslGt:20160129014702p:plain

クルクルと回るものの正体はProgressBarで、まずはLayout.xmlを準備します。

layout_loading.xml

< ?xml version="1.0" encoding="utf-8"? >
< RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center" >
    < ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:indeterminate="true" / >
< / RelativeLayout >

次にLoading画面の表示・非表示を切り替えるclassを作成します。
(別classとして作成しなくても実装可能ですが、コードがシンプルになることから切り分けています)

LoadingPanelViewer.java

package jp.searchwakayamatoilet;

import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.view.KeyEvent;

public class LoadingPanelViewer {
    private ProgressDialog progressDialog;
    public LoadingPanelViewer(Context context, MainPresenter presenter){
        progressDialog = new ProgressDialog(context);
        // 背景色を透明にする(指定しないと白背景で表示される).
        progressDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        // タッチした時にLoadingが止まらないようにCancelできないようにする.
        progressDialog.setCancelable(false);
        // Cancelを無効にする代わりに端末のBackキーでLoadingを止められるようにする.
        progressDialog.setOnKeyListener(new DialogInterface.OnKeyListener(){
            @Override
            public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event){
                if (keyCode == KeyEvent.KEYCODE_BACK) {
                    // CSVデータの読み込みをストップする.
                    presenter.stopLoadingCsvData();
                }
                return false;
            }   
        }
    }
    public void show(){
        // レイアウトをセットしてDialogを表示.
        progressDialog.show();
        progressDialog.setContentView(R.layout.layout_loading);
    }
    public void hide(){
        // 表示したDialogを非表示.
        progressDialog.dismiss();
    }
}

ProgressDialogを使って表示します。
「ProgressDialog.setCancelable」をTrueにすると、Dialog表示中に画面を触った時や端末のBackキーを押した場合にキャンセルされます。

今回はBackキーを押した場合のみキャンセルできるようにしたかったため、「ProgressDialog.setCancelable」をFalseにしてKeyイベントを使用しました。

Dialog表示中のKeyイベントの取得は呼び出し元のActivityではなく、Dialogに設定する必要があります。

あとは「LoadingPanelViewer.show()」でLoading画面の表示開始、「LoadingPanelViewer.hide()」でキャンセルすることができます。

別スレッドの処理をストップする(失敗)

Loading画面がキャンセルされたら、別スレッドで実行しているCSVデータの読み込みもストップしたいところ。

別スレッドでの処理は、「HandlerThread」と「Handler」を使って行っていました(【Android】SQLiteへのデータ追加と検索(和歌山トイレマップで遊んでみる 5) - DBへのデータ追加

この処理は、「HandlerThread.interrupt()」で止められる...と思ったのですが、実際には処理が止まりませんでした...。
オブジェクトの保持やinterruptを実行するスレッドなど試しましたが結果は変わらず。

そこで一旦HandlerThreadは置いといて非同期処理ができるAsyncTaskを使ってみることにしました(ダメな対応)。

AsyncTaskを使う

AsyncTaskは、「AsyncTask<"doInBackgroundの引数", "onProgressUpdateの引数", "doInBackgroundの戻り値">」を継承する別クラスを作成する必要があります。
非同期処理は「doInBackground」で行われます。

ToiletDataLoader.java

~省略~
public class ToiletDataLoader extends AsyncTask {

    private DatabaseAccesser dbAccesser;
    private SQLiteDatabase sqlite;
    private boolean isExistingDataUsed;
    private boolean isTransactionStarted;
    private Activity currentActivity;
    private MainPresenter currentPresenter;
    private boolean isLoadingCancelled;

    private DatabaseAccesser.ToiletInfoModel toiletInfoModel;

    public void init(Activity newActivity, boolean newIsExistingDataUsed, MainPresenter newPresenter){
        // クラス内で使用するオブジェクトの設定.
        currentPresenter = newPresenter;
        currentActivity = newActivity;
        isExistingDataUsed = newIsExistingDataUsed;

        dbAccesser = new DatabaseAccesser(newActivity);
        sqlite = dbAccesser.getWritableDatabase();
        toiletInfoModel = dbAccesser.new ToiletInfoModel();
        isLoadingCancelled = false;
    }
    public void stopLoading(){
        // CancelではdoInBackgroundの処理が止まらないため(※後述)処理を止めるためのフラグを立てる.
        isLoadingCancelled = true;
    }
    @Override
    protected Integer doInBackground(Void... params) {
        // 非同期処理.
        return 0;
    }
    @Override
    protected void onPostExecute(Integer result) {
        // doInBackgroundの処理が終わったら実行される.
    }
    @Override
    protected void onCancelled(){
        // キャンセル時の処理.
    }
}

処理内容としては「HandlerThread」を使ったときとほぼ同じです。
ただし、AsyncTaskを使って処理を行う場合、処理を開始するタイミングで毎回「AsyncTask」を継承したクラスのオブジェクトを初期化する必要があります。

ToiletDataLoader dataLoader = new ToiletDataLoader();
dataLoader.execute();

処理完了後に初期化せずに再度処理を実行しようとするとエラーになります。

※「HanderThread」ではうまくいかなかった処理のキャンセルですが、「AsyncTask.cancel」でも処理を止めることはできませんでした。
そもそも「AsyncTask.cancel」は「doInBackground」の処理を止めるものではないようで、上記のように実行している処理の中でフラグを使って処理を止める必要があるようです。

終わりに

次回は「Lightweight-Stream-API」と「Gradle Retrolambda Plugin(https://github.com/evant/gradle-retrolambda)」を使用してみたのでその辺りの話になる…はずです。

参考

ProgressBar

AsyncTask