Androidでソースコード解析からAsyncTaskの使用について


Androidはフレームワーク層に非同期タスククラス、AysncTaskを提供し、バックグラウンドタスクを実行し、実行結果をUIスレッドに更新します.なぜ非同期タスクを使用するのですか?これは、あるタスクに時間がかかりすぎるとUIプライマリ・スレッドがブロックされるためであり、UIスレッドが5秒ブロックされるとANR(Application No Response)エラーが発生し、発生しなくてもプライマリ・インタフェースにカカが表示されることを知っているため、ユーザー・ボリュームがあまりよくないことが明らかになった.
一方、AsyncTaskクラスでは、UIスレッドに影響を与えない場合、TodoListで画像を読み取り、処理後の結果をUIスレッドに更新するなど、時間のかかるタスクを別のサブスレッドで行い、より良いユーザー体験を実現します.
次はAsyncTaskクラスの定義です.
public abstract class AsyncTask<Params, Progress, Result> {
    private static final String LOG_TAG = "AsyncTask";
    ...

上記のコードから、AsyncTaskは抽象クラスであることがわかります.したがって、AsyncTaskを使用する場合、私たちはそれを継承し、サブクラスを実装し、次のように1つの方法を実装します.
	class LoadImageTask extends AsyncTask<String, Void, ImageView>{

		@Override
		protected ImageView doInBackground(String... params) {
			String photoImagePath = params[0];
			Bitmap bitmap = BitmapReader.readBigBitmapFromFile(photoImagePath,REQ_WIDTH);
			ImageView imageView = Helper.createImageViewFromBitmap(DetailActivity.this, bitmap);
			return imageView;
		}
		
		protected void onPostExecute(ImageView result) {
			imageViews.add(result);
			refreshGallery();
	    }
		
	}

AsyncTaskの3つのパラメータ,Params,Progress,Resultと対照的に,汎用パラメータである.
1)Params:例で定義したStringタイプのようなパラメータは、AysncTaskに渡されるdoInBackgroundメソッドであるImageのパスを指す.
2)Progress:これは実行中に使用されるパラメータで、一般的に進捗を表示する際に使用されますが、例では使用されませんので、ここでVoidとして指定できます.
3)Result:これは実行結果オブジェクトであり、onPostExecuteメソッドのパラメータとしてUIスレッドで処理されます.
上記の例からも分かるように,一般に我々が実現するこのサブクラスは,その中の2つの方法を実現する.
1)doInBackground:この方法は,実は別のスレッドで実行される.
2)onPostExecute:このメソッドはUIスレッドに戻って実行されているので、ここでは前のメソッドで実行した結果をUIスレッドに更新することができます.
では、このメカニズムはどうなのか、深く見て、勉強しましょう.
まずActivityで、このAsyncTaskをどのように使用しているのかを見てみましょう.コードは以下の通りです.
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data){		
		if (requestCode == REQUEST_FOR_CAMERA) {
			if(resultCode == RESULT_OK){
				isPhotoTaken = true;			
				photoFileNames.add(tempPhotoFileName);			
			}
		} else if (requestCode == REQUEST_FOR_GALLERY) {
			if(resultCode == RESULT_OK){
				isPhotoTaken = true;			
				ContentResolver resolver = getContentResolver();
				Uri uri = data.getData();
				tempPhotoFileName = Helper.getImagePath(resolver, uri);
				photoFileNames.add(tempPhotoFileName);				
			}
		}
		new LoadImageTask().execute(tempPhotoFileName);//   execute  
	}

TodoListの小さなdemoでは、カメラやライブラリからピクチャへのパスを返すと、AsynctTaskのexecuteメソッドが呼び出されます.では、ここが入り口です.
AsyncTaskクラスに入り、次のようにexecuteメソッドを見つけます.
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }
では、executeOnExecutorメソッドが呼び出されていることがわかります.
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }

        mStatus = Status.RUNNING;

        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }
この方法では、AsynctTaskが非同期タスクの実行状態を表すmStatusのメンバー変数を定義していることがわかります.それぞれpending、running、finishedですが、pending状態のAsnycTaskのみが実行できます.ステータスがPendingの場合、実行を継続し、ステータスをrunningに変更することで、AsyncTaskが一度だけ実行されることを保証できます.
次にonPreExecuteメソッドを呼び出していくつかの処理を行います.このメソッドは実際にはサブクラスで自分で実現することもできます.何か処理が必要な場合は、一般的には使用しません.
次に,我々が伝達したパラメータparamsがmWorkerに伝達され,executeを実行するためのexecuterメソッドがあり,実行するパラメータはmFutureオブジェクトであることが分かった.では、この二つのものは何ですか.
次に、次のコードを参照します.
    public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                return postResult(doInBackground(mParams));
            }
        };

        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occured while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

mWorkerはWorkerRunnableオブジェクトですが、実際にはWorkerRunnableはAsyncTaskの抽象的な内部クラスであり、Callableインタフェースは以下のように実現されています.
    private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
        Params[] mParams;
    }

上記の付与プロセスでは、匿名クラスでWorkerRunnableが実装され、そのcallメソッドでスレッドの優先度がバックグラウンドスレッドに調整され、doInBackgroundメソッドが実行されますが、まだ実行されていません.FutureTaskオブジェクトを見てみましょう.次はその構造関数です.
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        sync = new Sync(callable);
    }

一方、FutureTaskはJavaマルチスレッドモデルの一部であり、その間にRunnable(スレッド属性)インタフェースとFuture(非同期タスク)インタフェースが接地されているが、上記のexecuteOnExecutorメソッドでは、mWorker(Callableインタフェースを実現した)オブジェクトが渡され、メンバー変数syncにカプセル化されている.非同期タスクも作成されました.
上のexecuteOnExecutorの方法からも分かるように、executeを実際に実行するのはExecutorですが、AsyncTaskではSerialExecutorです.そのコンストラクション関数を見てみましょう.
    private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();//                FutureTask run  
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

上から、executeメソッドのパラメータがRunnableであることがわかります.実はFutrueTaskです.ここでは、新しいスレッドにカプセル化され、タスクとしてmTasksキューに追加されます.次に、THREAD_POOL_EXECUTORでmTasksを実行すると、私たちはもう深く入り込まないので、FutureTaskのrun方法を見てみましょう.以下のようにします.
    public void run() {
        sync.innerRun();
    }

syncを呼び出していることがわかりましたInnerRun()ではSyncのinnerRunメソッドは何をしたのでしょうか.
        void innerRun() {
            if (!compareAndSetState(READY, RUNNING))
                return;

            runner = Thread.currentThread();
            if (getState() == RUNNING) { // recheck after setting thread
                V result;
                try {
                    result = callable.call();
                } catch (Throwable ex) {
                    setException(ex);
                    return;
                }
                set(result);
            } else {
                releaseShared(0); // cancel
            }
        }
callableのcallメソッドが呼び出されていることに気づきました.ああ、やっとmWorkerのcallメソッドに来ました.振り返ってみると、そこでdoInBackgroundメソッドが呼び出されたのではないでしょうか.したがって,doInBackgroundは確かに新しいスレッドで実行され,バックグラウンドスレッドであると判断できる.
しかし同時に,その実行が完了するとpostResultにパラメータとして渡され,引き続き見ていくことが分かった.
    private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }
では、ここではsHandlerを使用して非同期メッセージを送信しているが、sHandlerはAsynctTaskで定義されており、以下のようになっている.
private static final InternalHandler sHandler = new InternalHandler();
InternalHandler定義は次のとおりです.
    private static class InternalHandler extends Handler {
        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult result = (AsyncTaskResult) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

MESSAGEならPOST_RESULTでは、mTaskのfinishメソッドが呼び出され、終了しますが、MESSAGE_POST_PROGRESSでは、onProgressUpdateメソッドが呼び出されます.では、このmTaskは私たちが定めたAsyncTaskであることは明らかでしょう.AsyncTaskResultが何が先なのか見てみましょう
    @SuppressWarnings({"RawUseOfParameterizedType"})
    private static class AsyncTaskResult<Data> {
        final AsyncTask mTask;
        final Data[] mData;

        AsyncTaskResult(AsyncTask task, Data... data) {
            mTask = task;
            mData = data;
        }
    }
}
上に渡されたAsyncTaskResultのパラメータtaskはちょうどthisであり、mTaskに値を与えていることが分かったので、このmTaskはActivityで定義したLoadImageTaskであり、メインスレッドで定義されているので、finishメソッドもメインスレッド、つまりUIスレッドで実行されていることが明らかになりました.このfinishメソッドを見てみましょう.
    private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }
ここで、onPostExecuteメソッドの呼び出しをやっと見ました.これは、onPostExecuteがUIスレッドで動作していることを示しています.最後にmStatusもFIINISHEDとして設計され、この非同期タスクも終了しました.
うーん、AsyncTaskの基本原理についてはここで終わりです.