2012年9月13日木曜日

Android AsyncTask#doInBackground内での例外をUIスレッドに通知する

Android AsyncTaskでProgressDialogを表示 その1
Android AsyncTaskでProgressDialogを表示 その2

前回までに作ったProgressBarを表示するAsyncTaskですが
doInBackgroundメソッド内で発生した例外をUIスレッドに通知したいと思います。

バックグラウンドで発生した例外をUIスレッドに通知するにはHandlerクラスを使用します。
詳しい説明や使い方はコチラなどが参考になりました。
愚鈍人 HandlerとMessage - 別スレッドでのGUI操作

ようはUIスレッドでHandlerクラスのインスタンスを生成し、
このインスタンスのsendMessageメソッドでバックグラウンドからUIスレッドに通知できます。
またHandlerクラスのhandleMessageメソッドをオーバライドして、バックグラウンドから通知を受けたときの処理を書きます。

MainActivity.java
ボタンをクリックするとバックグラウンドで処理を実行します。
処理を行うクラス(ReceiveDataクラス)のコンストラクタの引数にHandlerオブジェクトを指定しています。
またHandlerクラスのhandleMessageメソッドをオーバライドして、発生した例外のメッセージを表示します。
※「This Handler class should be static or leaks might occur」と警告が出ますが、今はとりあえず無視します(^^;
package com.example.helloandroid;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends Activity {
    //Activityが破棄されても、オブジェクトを保持できるようにstaticで宣言する。      
    private static ProgressAsyncTask<Void,Boolean> mTask;  

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //プログレスダイアログを再表示する。      
        if (mTask != null && mTask.getIsShowProgress()){      
            mTask.showDialog();      
        }    

    }

    @Override     
    protected void onPause() {      
        //プログレスダイアログを閉じる。      
        if (mTask != null && mTask.getIsShowProgress()) {      
            mTask.dismissDialog();       
        }      
        super.onPause();      
    }      


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

    /**
     * button1クリック処理
     * @param view
     * バックグラウンドで処理を行う
     */
    public void button1Click(View view){
        final Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                Throwable e = (Throwable)msg.obj;
                Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_LONG).show();
            }
        };
        //final MyHandler handler = new MyHandler(this);
        mTask = new ProgressAsyncTask<Void,Boolean>(this){   
            @Override  
            protected Boolean doInBackground(Void... params) {   
                ReceiveData receive = new ReceiveData(mTask,handler);   
                Boolean ret = receive.getMethod1();
                return ret;
            }};   
        mTask.execute();  
    }
}

ProgressAsyncTask.java
プログレスバーを表示するAsyncTaskクラスです。
package com.example.helloandroid;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Bundle;

/**
 * ProgressAsyncTask
 * プログレスダイアログを表示し、バックグラウンドで処理を行う抽象クラスです。
 * @param <Param>
 * @param <Result>
 *
 */
public abstract class ProgressAsyncTask<Param,Result> extends AsyncTask<Param,Bundle,Result>{
    private Context mContext;
    private ProgressDialog mProgressDialog;
    private Boolean isShowProgress;

    /**
     * コンストラクタ
     * @param context
     * @param procName
     */
    public ProgressAsyncTask(Context context){
        mContext = context;
    }

    /**
     * getIsShowProgress
     * プログレスダイアログが表示中かどうかを返します。
     * @return
     */
    public Boolean getIsShowProgress() {
        return isShowProgress;
    }

    /**
     * onPreExecute
     * 最初にUIスレッドで呼び出されます。
     * UIに関わる処理をします。
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        showDialog();
        isShowProgress = true;
    }

    /**
     * onProgressUpdate
     * doInBackground内でpublishProgressメソッドが呼ばれると、 UIスレッド上でこのメソッドが呼ばれます。 
     * このメソッドの引数の型はAsyncTaskの二つ目のパラメータです。 
     * @param values
     */
    @Override
    protected void onProgressUpdate(Bundle... values) {
        super.onProgressUpdate(values);
        Bundle bundle = values[0];  
        if (bundle.containsKey("Message")){
            mProgressDialog.setMessage(bundle.getString("Message"));
        }
        if (bundle.containsKey("Max")){
            mProgressDialog.setMax(bundle.getInt("Max"));
        }
        if (bundle.containsKey("Progress")){
            mProgressDialog.setProgress(bundle.getInt("Progress"));
        }
    }

    /**
     * onPostExecute
     * doInBackground が終わるとそのメソッドの戻り値をパラメータとして渡して onPostExecute が呼ばれます。
     * このパラメータの型は AsyncTask を extends するときの三つめのパラメータです。
     * バックグラウンド処理が終了し、メインスレッドに反映させる処理をここに書きます。
     * @param result
     */
    @Override
    protected void onPostExecute(Result result) {
        super.onPostExecute(result);
        dismissDialog();
        isShowProgress = false;
    }

    /**
     * onCancelled
     * cancelメソッドが呼ばれるとonCancelledメソッドが呼ばれます。
     */
    @Override
    protected void onCancelled() {
        super.onCancelled();
        dismissDialog();
        isShowProgress = false;
    }

    /**
     * showDialog
     * プログレスダイアログを表示します。
     */
    public void showDialog() { 
        mProgressDialog = new ProgressDialog(mContext);
        mProgressDialog.setMessage(""); //途中でメッセージを変更するために必要
        mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mProgressDialog.setIndeterminate(false);
        mProgressDialog.setCancelable(true);
        mProgressDialog.setButton("キャンセル", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                cancel(true); // 非同期処理をキャンセルする
            } 
        });
        mProgressDialog.show();
    }

    /**
     * dismissDialog
     * ダイアログを終了します。
     */
    public void dismissDialog() {
        mProgressDialog.dismiss();
        mProgressDialog = null;
    }

    /**
     * setProgressMessage
     * プログレスダイアログのメッセージを設定します。
     * @param message
     */
    public void setProgressMessage(String message){
        Bundle data = new Bundle();
        data.putString("Message", message);
        publishProgress(data);
    }
    /**
     * setMax
     * プログレスダイアログの最大値を設定します。
     * @param max
     */
    public void setProgressMax(int max){
        Bundle data = new Bundle();
        data.putInt("Max", max);
        publishProgress(data);
    }
    /**
     * setProgress
     * プログレスダイアログの進捗値を設定します。
     * @param max
     */
    public void setProgress(int progress){
        Bundle data = new Bundle();
        data.putInt("Progress", progress);
        publishProgress(data);
    }
}

ReceiveData.java
実際にバックグラウンドで行う処理です。
ループ変数 i が 7 になったときRuntimeExceptionをスローし、 catch句でHandlerオブジェクトのsendMessageメソッドを実行しています。
package com.example.helloandroid;

import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;

public class ReceiveData {
    
    private ProgressAsyncTask<?,?> mTask;
    private Handler mHandler;
    
    /**
     * コンストラクタ
     * @param task
     * @param handler
     */
    public ReceiveData(ProgressAsyncTask<?,?> task, Handler handler){
        mTask = task;
        mHandler = handler;
    }
    
    /**
     * getMethod1
     
     */
    public Boolean getMethod1(){
        try {
            mTask.setProgressMessage("Method1_Start");
            mTask.setProgressMax(10);
            for(int i=0; i<10; i++){
                mTask.setProgress(i+1);
                SystemClock.sleep(1000);
                if (i == 7) {
                    throw new RuntimeException("エラー! i==7です。");
                }
                if (mTask.isCancelled()) {      
                    return false;                   
                } 
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            final Message msg = new Message();
            msg.obj = e;
            new Thread(new Runnable(){
                public void run() {
                    mHandler.sendMessage(msg);
                }}).start();
            mTask.cancel(true);    
            return false;
        }
    }
}

実行するとUIスレッドでメッセージが表示されます。


さて次は「This Handler class should be static or leaks might occur」と警告が出ないようMainActivityを修正します。
修正方法はコチラを参考に
http://stackoverflow.com/questions/11407943/this-handler-class-should-be-static-or-leaks-might-occur-incominghandler
HandlerクラスをStaticクラスにします。

MainActivity
package com.example.helloandroid;

import java.lang.ref.WeakReference;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends Activity {
    //Activityが破棄されても、オブジェクトを保持できるようにstaticで宣言する。      
    private static ProgressAsyncTask<Void,Boolean> mTask;  

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //プログレスダイアログを再表示する。      
        if (mTask != null && mTask.getIsShowProgress()){      
            mTask.showDialog();      
        }    

    }

    @Override     
    protected void onPause() {      
        //プログレスダイアログを閉じる。      
        if (mTask != null && mTask.getIsShowProgress()) {      
            mTask.dismissDialog();       
        }      
        super.onPause();      
    }      


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

    /**
     * button1クリック処理
     * @param view
     * バックグラウンドで処理を行う
     */
    public void button1Click(View view){
        final MyHandler handler = new MyHandler(this);
        mTask = new ProgressAsyncTask<Void,Boolean>(this){   
            @Override  
            protected Boolean doInBackground(Void... params) {   
                ReceiveData receive = new ReceiveData(mTask,handler);   
                Boolean ret = receive.getMethod1();
                return ret;
            }};   
        mTask.execute();  
    }
    
    /**
     * MyHandlerクラス
     * バックグラウンドで例外発生時の処理
     */
    private static class MyHandler extends Handler {    
        private final WeakReference<MainActivity> mActivity;    
        public MyHandler(MainActivity activity) {        
            mActivity = new WeakReference<MainActivity>(activity);    
        }    
        @Override    
        public void handleMessage(Message msg) {        
            MainActivity activity = mActivity.get();        
            if (activity == null){
                return;
            }
            Throwable e = (Throwable)msg.obj;
            Toast.makeText(mActivity.get(), e.getMessage(), Toast.LENGTH_LONG).show();
        }
    } 
}

0 件のコメント: