コードをHandler
からAsyncTask
に変換しています。後者は、メインUIスレッドでの非同期の更新と結果の処理が優れています。私にとって不明確なのは、AsyncTask#doInBackground
で何かがうまくいかない場合に例外を処理する方法です。
私がやる方法は、エラーハンドラを用意して、それにメッセージを送信することです。それはうまくいきますが、それは「正しい」アプローチですか、それともより良い代替手段がありますか?
また、エラーハンドラをアクティビティフィールドとして定義すると、UIスレッドで実行される必要があることも理解しています。ただし、Handler#handleMessage
からトリガーされたコードが間違ったスレッドで実行されているという例外を受け取ることがあります(非常に予測不可能です)。代わりにActivity#onCreate
でエラーハンドラーを初期化する必要がありますか? runOnUiThread
をHandler#handleMessage
に配置することは冗長に思えますが、非常に確実に実行されます。
それはうまくいきますが、それは「正しい」アプローチですか、より良い代替手段がありますか?
Throwable
インスタンス自体のException
またはAsyncTask
を保持してから、onPostExecute()
でそれを処理するため、エラー処理には次のオプションがあります。画面にダイアログを表示します。
AsyncResultオブジェクトを作成します(他のプロジェクトでも使用できます)。
public class AsyncTaskResult<T> {
private T result;
private Exception error;
public T getResult() {
return result;
}
public Exception getError() {
return error;
}
public AsyncTaskResult(T result) {
super();
this.result = result;
}
public AsyncTaskResult(Exception error) {
super();
this.error = error;
}
}
AsyncTask doInBackgroundメソッドからこのオブジェクトを返し、postExecuteで確認します。 (このクラスを他の非同期タスクの基本クラスとして使用できます)
以下は、WebサーバーからJSON応答を取得するタスクのモックアップです。
AsyncTask<Object,String,AsyncTaskResult<JSONObject>> jsonLoader = new AsyncTask<Object, String, AsyncTaskResult<JSONObject>>() {
@Override
protected AsyncTaskResult<JSONObject> doInBackground(
Object... params) {
try {
// get your JSONObject from the server
return new AsyncTaskResult<JSONObject>(your json object);
} catch ( Exception anyError) {
return new AsyncTaskResult<JSONObject>(anyError);
}
}
protected void onPostExecute(AsyncTaskResult<JSONObject> result) {
if ( result.getError() != null ) {
// error handling here
} else if ( isCancelled()) {
// cancel handling here
} else {
JSONObject realResult = result.getResult();
// result handling here
}
};
}
AsyncTask
の例外を適切に処理する必要があると感じたら、これをスーパークラスとして使用します。
public abstract class ExceptionAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
private Exception exception=null;
private Params[] params;
@Override
final protected Result doInBackground(Params... params) {
try {
this.params = params;
return doInBackground();
}
catch (Exception e) {
exception = e;
return null;
}
}
abstract protected Result doInBackground() throws Exception;
@Override
final protected void onPostExecute(Result result) {
super.onPostExecute(result);
onPostExecute(exception, result);
}
abstract protected void onPostExecute(Exception exception, Result result);
public Params[] getParams() {
return params;
}
}
通常、サブクラスでdoInBackground
をオーバーライドしてバックグラウンド作業を行い、必要に応じて例外をスローします。次にonPostExecute
(抽象的であるため)を実装する必要があり、これはパラメーターとして渡されるすべてのタイプのException
を処理することを穏やかに思い出させます。ほとんどの場合、例外は何らかのタイプのui出力につながるため、onPostExecute
はそれを行うのに最適な場所です。
他の利点をもたらすRoboGuiceフレームワークを使用する場合は、追加のCallback onException()があるRoboAsyncTaskを試すことができます。本当にうまく機能し、私はそれを使用しています。 http://code.google.com/p/roboguice/wiki/RoboAsyncTask
成功と失敗のコールバックを定義するインターフェイスを持つ独自のAsyncTaskサブクラスを作成しました。したがって、AsyncTaskで例外がスローされると、onFailure関数に例外が渡されます。そうでない場合、onSuccessコールバックに結果が渡されます。なぜAndroidにはもっと良いものがないのは私を超えています。
public class SafeAsyncTask<inBackgroundType, progressType, resultType>
extends AsyncTask<inBackgroundType, progressType, resultType> {
protected Exception cancelledForEx = null;
protected SafeAsyncTaskInterface callbackInterface;
public interface SafeAsyncTaskInterface <cbInBackgroundType, cbResultType> {
public Object backgroundTask(cbInBackgroundType[] params) throws Exception;
public void onCancel(cbResultType result);
public void onFailure(Exception ex);
public void onSuccess(cbResultType result);
}
@Override
protected void onPreExecute() {
this.callbackInterface = (SafeAsyncTaskInterface) this;
}
@Override
protected resultType doInBackground(inBackgroundType... params) {
try {
return (resultType) this.callbackInterface.backgroundTask(params);
} catch (Exception ex) {
this.cancelledForEx = ex;
this.cancel(false);
return null;
}
}
@Override
protected void onCancelled(resultType result) {
if(this.cancelledForEx != null) {
this.callbackInterface.onFailure(this.cancelledForEx);
} else {
this.callbackInterface.onCancel(result);
}
}
@Override
protected void onPostExecute(resultType result) {
this.callbackInterface.onSuccess(result);
}
}
Cagatay Kalan のソリューションに対するより包括的なソリューションを以下に示します。
AsyncTaskResult
public class AsyncTaskResult<T>
{
private T result;
private Exception error;
public T getResult()
{
return result;
}
public Exception getError()
{
return error;
}
public AsyncTaskResult(T result)
{
super();
this.result = result;
}
public AsyncTaskResult(Exception error) {
super();
this.error = error;
}
}
ExceptionHandlingAsyncTask
public abstract class ExceptionHandlingAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, AsyncTaskResult<Result>>
{
private Context context;
public ExceptionHandlingAsyncTask(Context context)
{
this.context = context;
}
public Context getContext()
{
return context;
}
@Override
protected AsyncTaskResult<Result> doInBackground(Params... params)
{
try
{
return new AsyncTaskResult<Result>(doInBackground2(params));
}
catch (Exception e)
{
return new AsyncTaskResult<Result>(e);
}
}
@Override
protected void onPostExecute(AsyncTaskResult<Result> result)
{
if (result.getError() != null)
{
onPostException(result.getError());
}
else
{
onPostExecute2(result.getResult());
}
super.onPostExecute(result);
}
protected abstract Result doInBackground2(Params... params);
protected abstract void onPostExecute2(Result result);
protected void onPostException(Exception exception)
{
new AlertDialog.Builder(context).setTitle(R.string.dialog_title_generic_error).setMessage(exception.getMessage())
.setIcon(Android.R.drawable.ic_dialog_alert).setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
//Nothing to do
}
}).show();
}
}
タスク例
public class ExampleTask extends ExceptionHandlingAsyncTask<String, Void, Result>
{
private ProgressDialog dialog;
public ExampleTask(Context ctx)
{
super(ctx);
dialog = new ProgressDialog(ctx);
}
@Override
protected void onPreExecute()
{
dialog.setMessage(getResources().getString(R.string.dialog_logging_in));
dialog.show();
}
@Override
protected Result doInBackground2(String... params)
{
return new Result();
}
@Override
protected void onPostExecute2(Result result)
{
if (dialog.isShowing())
dialog.dismiss();
//handle result
}
@Override
protected void onPostException(Exception exception)
{
if (dialog.isShowing())
dialog.dismiss();
super.onPostException(exception);
}
}
変数メンバーの共有に依存しない別の方法は、キャンセルを使用することです。
これはAndroid docs:
public final boolean cancel(boolean mayInterruptIfRunning)
このタスクの実行をキャンセルしようとします。タスクがすでに完了している場合、キャンセルされている場合、または他の何らかの理由でキャンセルできない場合、この試行は失敗します。成功し、キャンセルが呼び出されたときにこのタスクが開始されていない場合、このタスクは実行されません。タスクが既に開始している場合、mayInterruptIfRunningパラメーターは、タスクを停止しようとしてこのタスクを実行しているスレッドを中断する必要があるかどうかを決定します。
このメソッドを呼び出すと、doInBackground(Object [])が返された後にonCancelled(Object)がUIスレッドで呼び出されます。このメソッドを呼び出すと、onPostExecute(Object)が呼び出されないことが保証されます。このメソッドを呼び出した後、isCancelled()がdoInBackground(Object [])から定期的に返す値をチェックして、タスクをできるだけ早く終了する必要があります。
したがって、catchステートメントでcancelを呼び出して、onPostExcuteが呼び出されることはなく、代わりにonCancelledがUIスレッドで呼び出されることを確認できます。そのため、エラーメッセージを表示できます。
このシンプルなクラスはあなたを助けることができます
public abstract class ExceptionAsyncTask<Param, Progress, Result, Except extends Throwable> extends AsyncTask<Param, Progress, Result> {
private Except thrown;
@SuppressWarnings("unchecked")
@Override
/**
* Do not override this method, override doInBackgroundWithException instead
*/
protected Result doInBackground(Param... params) {
Result res = null;
try {
res = doInBackgroundWithException(params);
} catch (Throwable e) {
thrown = (Except) e;
}
return res;
}
protected abstract Result doInBackgroundWithException(Param... params) throws Except;
@Override
/**
* Don not override this method, override void onPostExecute(Result result, Except exception) instead
*/
protected void onPostExecute(Result result) {
onPostExecute(result, thrown);
super.onPostExecute(result);
}
protected abstract void onPostExecute(Result result, Except exception);
}
実際、AsyncTaskはFutureTaskとExecutorを使用し、FutureTaskは例外チェーンをサポートしていますまず、ヘルパークラスを定義しましょう
public static class AsyncFutureTask<T> extends FutureTask<T> {
public AsyncFutureTask(@NonNull Callable<T> callable) {
super(callable);
}
public AsyncFutureTask<T> execute(@NonNull Executor executor) {
executor.execute(this);
return this;
}
public AsyncFutureTask<T> execute() {
return execute(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
protected void done() {
super.done();
//work done, complete or abort or any exception happen
}
}
第二に、使用しましょう
try {
Log.d(TAG, new AsyncFutureTask<String>(new Callable<String>() {
@Override
public String call() throws Exception {
//throw Exception in worker thread
throw new Exception("TEST");
}
}).execute().get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
//catch the exception throw by worker thread in main thread
e.printStackTrace();
}