AsyncTaskメカニズムの原理解析

15605 ワード

AsyncTaskメカニズムの原理解析
Androidは私たちに2つの便利な非同期処理案を提供してくれました.HandlerとAsyncTask、2つの方法が似合うシーンはネットで検索すればわかりますが、なぜですか.この分析はあなたのために答えを明らかにします.Handlerの機構原理を先に解析したが,Handlerの機構原理をまだ熟知していない.本編の学習を通して,AsyncTaskの動作原理を理解する.本編は内容が多く,結論に基づいて先行し,分析は後の原則から見る.
一、使用
使用するのはとても简単で、1つのクラスでAsyncTaskを継承して、このオブジェクトを持って、executeを実行してバックグラウンドのタスクを実行することができます.例を探しましょう
* private class DownloadFilesTask extends AsyncTask {
*     protected Long doInBackground(URL... urls) {
*         int count = urls.length;
*         long totalSize = 0;
*         for (int i = 0; i < count; i++) {
*             totalSize += Downloader.downloadFile(urls[i]);
*             publishProgress((int) ((i / (float) count) * 100));
*             // Escape early if cancel() is called
*             if (isCancelled()) break;
*         }
*         return totalSize;
*     }
*
*     protected void onProgressUpdate(Integer... progress) {
*         setProgressPercent(progress[0]);
*     }
*
*     protected void onPostExecute(Long result) {
*         showDialog("Downloaded " + result + " bytes");
*     }
* }
*new DownloadFilesTask().execute(url1, url2, url3);

この例はAsyncTaskソースコードに注記されている例である.AsyncTaskには3つの汎用パラメータがありますが、それぞれどういう意味ですか.
 *     
  • Params, the type of the parameters sent to the task upon * execution.
  • *
  • Progress, the type of the progress units published during * the background computation.
  • *
  • Result, the type of the result of the background * computation.

  • 簡単に言えば、最初のパラメータ(可変パラメータ)はバックグラウンドタスクに渡されます.2番目のパラメータはバックグラウンドタスク実行中に進捗状況を更新して使用され、3番目のパラメータはバックグラウンドタスク実行後の戻り結果です.それ以外に、いくつかの方法を紹介します.
     * 

    AsyncTask must be subclassed to be used. The subclass will override at least * one method ({@link #doInBackground}), and most often will override a * second one ({@link #onPostExecute}.)


    少なくとも1つのメソッド、doInBackgroundを書き換え、このメソッドはバックグラウンドタスクを実行するために使用され、作業スレッドで実行されます.
        @WorkerThread
        protected abstract Result doInBackground(Params... params);

    通常、バックグラウンドタスクの結果をUIスレッドにpostし、UIスレッドで実行するonPostExecuteも書き換えます.
        @MainThread
        protected void onPostExecute(Result result) {
        }

    実は、他にも2つの方法があります.ここでは4つの方法の注釈を貼り付けます.
     * 

    When an asynchronous task is executed, the task goes through 4 steps:

    *
      *
    1. {@link #onPreExecute()}, invoked on the UI thread before the task * is executed. This step is normally used to setup the task, for instance by * showing a progress bar in the user interface.
    2. *
    3. {@link #doInBackground}, invoked on the background thread * immediately after {@link #onPreExecute()} finishes executing. This step is used * to perform background computation that can take a long time. The parameters * of the asynchronous task are passed to this step. The result of the computation must * be returned by this step and will be passed back to the last step. This step * can also use {@link #publishProgress} to publish one or more units * of progress. These values are published on the UI thread, in the * {@link #onProgressUpdate} step.
    4. *
    5. {@link #onProgressUpdate}, invoked on the UI thread after a * call to {@link #publishProgress}. The timing of the execution is * undefined. This method is used to display any form of progress in the user * interface while the background computation is still executing. For instance, * it can be used to animate a progress bar or show logs in a text field.
    6. *
    7. {@link #onPostExecute}, invoked on the UI thread after the background * computation finishes. The result of the background computation is passed to * this step as a parameter.
    8. *

    onPreExecute、タスク開始前の準備作業、UIスレッドで実行、例えば進捗バーの準備
    onProgressUpdateは、バックグラウンドタスクの実行時、進捗の更新などの動作でUIスレッドで実行されるのが一般的です
    二、原理分析
    解析の前に,AsyncTaskはスレッドプールとHandlerのカプセル化であり,タスクは1回しか実行できず,複数回エラーを報告し,タスクはキャンセルでき,デフォルトのシリアル実行であると結論した.
    2.1、メンバー分析
        private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
        // We want at least 2 threads and at most 4 threads in the core pool,
        // preferring to have 1 less than the CPU count to avoid saturating
        // the CPU with background work
        private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
        private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
        private static final int KEEP_ALIVE_SECONDS = 30;
    ここでは、CPUのコア数を取得することで、コアプール数と最大プール数を決定することができます.このプールは何ですか.スレッドプールでしょう.
        private static final ThreadFactory sThreadFactory = new ThreadFactory() {
            private final AtomicInteger mCount = new AtomicInteger(1);
    
            public Thread newThread(Runnable r) {
                return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
            }
        };
    
        private static final BlockingQueue sPoolWorkQueue =
                new LinkedBlockingQueue(128);

    ここにThreadFactoryがありますが、実はスレッドを生産する工場ですか.BlockingQueue、名前を見るとブロックされたキューですが、実は私たちが実行する任務を置いています.
        /**
         * An {@link Executor} that can be used to execute tasks in parallel.
         */
        public static final Executor THREAD_POOL_EXECUTOR;
    
        static {
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                    CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                    sPoolWorkQueue, sThreadFactory);
            threadPoolExecutor.allowCoreThreadTimeOut(true);
            THREAD_POOL_EXECUTOR = threadPoolExecutor;
        }

    まずExecutorコメントを見て、パラレルタスクを実行できます.ここには静的finalのオブジェクトがあります.名前を見てください.間違いありません.これがスレッドプールです.でも、ここは並行って言ったでしょ、焦らないで、下を見て
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    private static class SerialExecutor implements Executor {
            final ArrayDeque mTasks = new ArrayDeque();
            Runnable mActive;
    
            public synchronized void execute(final Runnable r) {
                mTasks.offer(new Runnable() {
                    public void run() {
                        try {
                            r.run();
                        } finally {
                            scheduleNext();
                        }
                    }
                });
                if (mActive == null) {
                    scheduleNext();
                }
            }
    
            protected synchronized void scheduleNext() {
                if ((mActive = mTasks.poll()) != null) {
                    THREAD_POOL_EXECUTOR.execute(mActive);
                }
            }
        }
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

    ここにはシリアルのExecutorがあります.何ですか.肝心な行を見て、THREAD_を通ります.POOL_EXECUTORはexecuteに行きます.poolは非同期タスクを実行するためにデフォルトで使用されていることがわかります.このSerialExecuterは、受信したタスクを一時保存し、poolにシリアルに渡して処理するコンテナとして理解できます.これはAsyncTaskのデフォルトがシリアルで実行されていることを検証しています.もちろん、自分でパラレル実行にカスタマイズすることができます.
        private static final int MESSAGE_POST_RESULT = 0x1;
        private static final int MESSAGE_POST_PROGRESS = 0x2;

    2種類のメッセージは、説明する必要はないでしょう.
        /**
         * Indicates the current status of the task. Each status will be set only once
         * during the lifetime of a task.
         */
        public enum Status {
            /**
             * Indicates that the task has not been executed yet.
             */
            PENDING,
            /**
             * Indicates that the task is running.
             */
            RUNNING,
            /**
             * Indicates that {@link AsyncTask#onPostExecute} has finished.
             */
            FINISHED,
        }

    この列挙でタスクのステータスを識別し、コメントを見ると、タスクごとに1回しか実行できません.
    private static InternalHandler sHandler;
        private static Handler getHandler() {
            synchronized (AsyncTask.class) {
                if (sHandler == null) {
                    sHandler = new InternalHandler();
                }
                return sHandler;
            }
        }
        private static class InternalHandler extends Handler {
            public InternalHandler() {
                super(Looper.getMainLooper());
            }

    ここまで、わかりましたね、AsyncTask内部にUIスレッドのHandlerがあります.
    AsyncTaskはタスクを取り消すことができますが、実はFutureTaskとCallableを使用しています.ここで紹介しますが、Callableは言い訳で、Runnableと似ていますが、Runnableには戻り値がありません.Callableは戻り値を指定できます.FutureTaskについては、ソースコメントを見てみましょう
     * 

    A {@code FutureTask} can be used to wrap a {@link Callable} or * {@link Runnable} object. Because {@code FutureTask} implements * {@code Runnable}, a {@code FutureTask} can be submitted to an * {@link Executor} for execution.


    FutureTaskはCallableとRunnableをカプセル化し、Executorに実行させることができるという意味です.FutureTaskには2つの重要な方法があります.doneは、タスクの実行が完了したことを示し、cacelは、タスクのキャンセルを示します.AsyncTaskがキャンセルできるのは、実はFutureTaskでキャンセルしたのです
        private final WorkerRunnable mWorker;
        private final FutureTask mFuture;
        private static abstract class WorkerRunnable implements Callable {
            Params[] mParams;
        }
    mWorkerは、Callableを実現したオブジェクトです.ここを見ると、AsyncTaskの特徴について初歩的な認識があるはずですが、大丈夫です.次はさらに深まります.
    2.2、動作原理分析
    まずAsyncTaskの構造方法を見てみましょう
    public AsyncTask() {
            mWorker = new WorkerRunnable() {
                public Result call() throws Exception {
                    mTaskInvoked.set(true);
                    Result result = null;
                    try {
                        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                        //noinspection unchecked
                        result = doInBackground(mParams);
                        Binder.flushPendingCommands();
                    } catch (Throwable tr) {
                        mCancelled.set(true);
                        throw tr;
                    } finally {
                        postResult(result);
                    }
                    return result;
                }
            };
    
            mFuture = new FutureTask(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 occurred while executing doInBackground()",
                                e.getCause());
                    } catch (CancellationException e) {
                        postResultIfNotInvoked(null);
                    }
                }
            };
        }

    ここでは2つのメンバ,mWorker,mFutureを初期化しているが,内部のメソッドは実行されない.ではexecuteメソッドを見てみましょう
        @MainThread
        public final AsyncTask execute(Params... params) {
            return executeOnExecutor(sDefaultExecutor, params);
        }

    ここでは、UIスレッドで実行する方法について説明します.
     @MainThread
        public final AsyncTask 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;
        }

    まずstatusの判断と初期化(ここでは1つのタスクで1回しか実行できない実装)を行い、次にonPreExecuteを見て、書き換える必要があるかもしれません.そしてparamsをmWorkerに割り当てました.exec.executeとは、sDefaultExecuterがexecuteを実行することです.また
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

    だからSERIAL_EXECUTORのexecuteになりました.
    private static class SerialExecutor implements Executor {
            final ArrayDeque mTasks = new ArrayDeque();
            Runnable mActive;
    
            public synchronized void execute(final Runnable r) {
                mTasks.offer(new Runnable() {
                    public void run() {
                        try {
                            r.run();
                        } finally {
                            scheduleNext();
                        }
                    }
                });
                if (mActive == null) {
                    scheduleNext();
                }
            }
    
            protected synchronized void scheduleNext() {
                if ((mActive = mTasks.poll()) != null) {
                    THREAD_POOL_EXECUTOR.execute(mActive);
                }
            }
        }

    ここでは、タスクをシリアルで実行するためにコンテナを使用したことを前に分析しました.poolに渡されたのはmActiveで、実はmFutureのパッケージで、mFuture初期化はまた使用するmWorkerなので、最終的にmWorkerのcallメソッド、つまりメソッドの最初のメンバーを構築する方法に実行されます.ここではdoInbackgroundが実行され、ここでは作業スレッドで実行されます.実行が完了するとresultが得られ、finallyでpostが出ます.
        private Result postResult(Result result) {
            @SuppressWarnings("unchecked")
            Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                    new AsyncTaskResult(this, result));
            message.sendToTarget();
            return result;
        }

    Handlerは先に紹介したが,ここではMessageをカプセル化し,UIスレッドに送信すればよいことを知っている.AsyncTaskResultは内部クラスです
        private static class AsyncTaskResult {
            final AsyncTask mTask;
            final Data[] mData;
    
            AsyncTaskResult(AsyncTask task, Data... data) {
                mTask = task;
                mData = data;
            }
        }

    handleMessageを見て
            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;
                }
            }

    見える、POST_RESULTはfinishメソッドを呼び出します
        private void finish(Result result) {
            if (isCancelled()) {
                onCancelled(result);
            } else {
                onPostExecute(result);
            }
            mStatus = Status.FINISHED;
        }

    ここでは,パラメータがresultであるよく知られたonPostExecute法を見た.ではPOST_PROGRESSはどのように処理されていますか?doInBackgroundは実行に時間がかかる可能性があることを知っていますが、中間のプロセスで進捗バーを更新するにはどうすればいいですか?doInbackgroundでpublishProgressを呼び出すことができます
        @WorkerThread
        protected final void publishProgress(Progress... values) {
            if (!isCancelled()) {
                getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                        new AsyncTaskResult(this, values)).sendToTarget();
            }
        }

    タスクがキャンセルされていない場合はHandlerで送信します.次にhandleMessageに着きます.前のコードを見てください.onProgressUpdate
        @MainThread
        protected void onProgressUpdate(Progress... values) {
        }
    ここを見て、動作原理分析は終わりました.AsyncTaskはスレッドプールとHandlerのパッケージであり、タスクは1回しか実行できません.タスクはキャンセルできます.デフォルトのシリアル実行(なぜこれらの特徴があるのか理解してください)