再論AsyncTask原理解析


耻ずかしいことに、AsyncTaskという最初から勉强しなければならないものは、こんなに长い间、私はその原理を深く理解したことがありません.16年に入門した时からメモリの漏れを引き起こすと言われていたからです.正式にプロジェクトをして以来、それを使ったことがないので、その原理を理解しようとは思わなかった.先日まで、大物が私にその原理を聞いて、私はやっと私の理解が次の記述性に限られていることに気づいて、そこでよく理解することにしました.
よく考えてみると、本当におかしいです.最初は私を連れて行った大物で、私が彼を知っていたとき、私は今仕事の年限が当時の彼と同じでした.しかし真相は、仕事の年限が彼と同じであることを除いて、技術の中で10万8千里も悪くて、まったく1次元の上でありません.最近ずっと自分の知识を书きたいと思っていますが、いつも自分の书いたものが手に入らないと思っています.小児科ですね.悩んだ后、书くことにしました.私のこの愚かな鸟は、飞ぶのが遅いと思いますが、根気よく前へ飞ばなければ、もっと高く飞ぶことができます.
くだらないことを言わないで、本文を始めましょう.
AsyncTask、皆さんはよく知っていると思います.簡単に言えば、ThreadとHandlerをパッケージ化しているので、スレッドの作成とスレッドの切り替えの問題に注目する必要はありません.これは当初初心者だった私たちに大きな助けを提供しました.しかし周知のように、AsyncTaskはメモリ漏れを引き起こす可能性があるため、グーグル公式に廃棄されている.
AsyncTaskはなぜメモリが漏れたのですか?
研究してみると、AsyncTaskによるメモリ漏れはそれ自体と大きな関係はないようで、本当の原因は私たち自身にあることがわかりました.AsyncTaskを使用するのは規範化されていないか、コードを書くのは規範化されていないためです.みんながそれを使うときは、二つの方法にほかならない.
  • 直接new 1つの内部クラスの方式;
  • new AsyncTaskから継承された非静的内部クラス.

  • 周知のように、activityやfragmentでは静的でない内部クラスが使用されるとメモリが漏洩しますが、上記の2つの方法でメモリが漏洩する可能性があります.おかしいことに、ソースコードの注釈に書かれた使い方は、メモリの漏洩しやすいコードを書くように誘導されやすい.解決策は、AsyncTaskから継承された静的な内部クラスを使用することです......AsyncTaskはなぜ廃棄されたのですか?この投稿はとても上手で,具体的にはこれ以上述べない.この文章を参考にすることができます.AsyncTaskは本当に冤罪だとしか言いようがありませんが...
    以下、AsyncTaskの原理解析を開始します.
    AsyncTaskの原理
    前に私が言ったように、AsyncTaskはThread+Handlerのパッケージで、正しいと言ってもいいが、間違っていると言ってもいいし、アイデアを言っていないとしか言いようがない.スレッドプールとHandlerのパッケージです
    次に、-code 1-
      public AsyncTask(@Nullable Looper callbackLooper) {
         
            mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
                ? getMainHandler()
                : new Handler(callbackLooper);
    
            mWorker = new WorkerRunnable<Params, Result>() {
         
                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<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 occurred while executing doInBackground()",
                                e.getCause());
                    } catch (CancellationException e) {
         
                        postResultIfNotInvoked(null);
                    }
                }
            };
        }
    
     private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
         
            Params[] mParams;
        }
    

    コンストラクション関数を見ると、実は簡単です.新しいスレッドを開くには3つの方法があります.
  • Threadのサブクラスをインスタンス化する.
  • はRunnableを実現し、Threadがインスタンス化されるとrunnableインスタンスに転送される.
  • は、Callableを実装し、そのインスタンスをFutureTaskに渡し、そのインスタンスをThreadに渡す.

  • 注意:この3つのうち、FutureTaskは比較的特殊で、結果を返すことができます.
    コンストラクション関数では、まず2つのものを見てみましょう.mWorker、mFutureです.私たちは彼らがそれぞれ何なのかを見てみましょう.
    意外なことに、WorkerRunnableはCallableを実現し、汎用タイプはコンストラクタのパラメータと戻りタイプであり、FutureTaskは結果を返すため、Callableの汎用宣言が私たちの定義した戻りタイプであることがわかります.mFutureを見てみましょう.彼はFutureTaskで、入参は私たちのmWorker、つまりCallableの例です.もちろん、イベント配信スレッドの切り替えを担当するHandlerも少なくありません.
    もう一度見てみましょう.WorkerRunnableのCallableのcallの実装を見てみましょう.それは私たちのコアメソッドdoInBackgroundを呼び出しています.このメソッドは抽象的な方法で、私たち自身が実現し、結果を返します.このステップは、新しいスレッドの内部、すなわちサブスレッドにあることに注意してください.結果をpostResultで送信します.
    ここまで、AsyncTaskのコアには触れていません.スレッドはどのように管理され、どのように起動されていますか.
    タスクの開始方法
    executeメソッドをポイントします:-code 2-
     	public final AsyncTask<Params, Progress, Result> execute(Params... params) {
         
            return executeOnExecutor(sDefaultExecutor, params);
        }
      
    	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;
        }
    

    ここではまず、同じAsyncTaskのインスタンスでexecuteを繰り返してもいいですか?答えはだめです.
    AsyncTaskはその状態をチェックしており、実行中または実行済みの場合はエラーが投げ出され、使用中にexecuteが繰り返されないことを確認しています.次に、おなじみの方法、onPreExecute()を見てみましょう.この方法は,メインタスク(時間のかかるタスク)すなわちdoInBackGround()の実行前に実行され,いくつかの(UIの)準備作業を行うことを知っているが,このステップがメインスレッドで実行されているかに注意する必要がある.続いてexecが実行する.execute(mFuture)では、doInBackGroundが実行されることを意味しているのではないでしょうか.そうじゃない!
    では、Executorとは何でしょうか.どんな役割がありますか.sDefaultExecutorを見てみましょう
    - code 3 -
     public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    
     private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
    
    

    sDefaultExecutorは我々のアクチュエータであり,静的定数を参照している.この定数はSerialExecutorの例である.次に見る.
    AsyncTaskの本質
    - code 4 -
      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();
                        } finally {
         
                            scheduleNext();
                        }
                    }
                });
                if (mActive == null) {
         
                    scheduleNext();
                }
            }
    
            protected synchronized void scheduleNext() {
         
                if ((mActive = mTasks.poll()) != null) {
         
                    THREAD_POOL_EXECUTOR.execute(mActive);
                }
            }
        }
        
    	public interface Executor {
         
    
        	void execute(Runnable command);
    	}
    

    シーケンスエフェクタSerialExecutorはExecutorを実現し、そのexecute()実現方法では、我々AsyncTaskコンストラクタで実例化されたFutureTask(FutureTaskでもRunnable)を実装し、さらにRunnableにパッケージし、offer方式でmTaskキューのヘッダにRunnableを追加した.mTaskはキューであることがわかります.そしてscheduleNext()で、キューの末尾からタスクを取り出し、THREAD_に渡すPOOL_EXECUTORは実行します.
    ではTHREAD_POOL_EXECUTORって何ですか?
    - code 5 -
       public static final Executor THREAD_POOL_EXECUTOR;
    
        static {
         
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                    CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(), sThreadFactory);
            threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);
            THREAD_POOL_EXECUTOR = threadPoolExecutor;
        }
    

    そう、スレッドプールのインスタンス定数です.
    ここでは、グーグルのプログラマーは本当にすごいと言わざるを得ません.スレッドプールのインスタンスでも、前のシーケンスエフェクタSerialExecutorインスタンスでも定数です.これにより、AsyncTaskが何度インスタンス化されても、インスタンス化されたのはFutureTask(およびCallable)タスクであり、両方ともクラスロード時に初期化されたインスタンスのみであることが保証されます.本当にすごいですね.簡単そうに見えますが、これは何本の髪が変わった知恵なのか分かりません.
    そして、後のタスクはスレッドプールに正式に任せて処理しました・・・
    プライマリ・スレッド・タスクの進捗状況と結果を通知する方法
    ところで、ちょっと忘れましたが、AsyncTaskの場合、メインスレッドのタスク結果とタスクの進捗状況に進捗状況を通知したら?-code 6 -
      @WorkerThread
        protected final void publishProgress(Progress... values) {
         
            if (!isCancelled()) {
         
                getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                        new AsyncTaskResult<Progress>(this, values)).sendToTarget();
            }
        }
    
       private Result postResult(Result result) {
         
            @SuppressWarnings("unchecked")
            Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                    new AsyncTaskResult<Result>(this, result));
            message.sendToTarget();
            return result;
        }
    
        private void finish(Result result) {
         
            if (isCancelled()) {
         
                onCancelled(result);
            } else {
         
                onPostExecute(result);
            }
            mStatus = Status.FINISHED;
        }
    
        private static class InternalHandler extends Handler {
         
            public InternalHandler(Looper looper) {
         
                super(looper);
            }
    
            @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;
                }
            }
        }
    

    publicProgress()とpostResult()の方法を見てみましょう.よく知っていると思いますか.間違いありません.Handlerを通じてメインスレッドにメッセージを送信しています.
    では、最初から撫でてみましょう.
  • AsyncTaskをロードするとき、シーケンスプロセッサとスレッドプールをインスタンス化します(このステップは私たちがしなくてもいいです)
  • newが1つのAsyncTaskを持つたびに、FutureTaskとCallableがnewされます.私たちのdoInbackGroupの主な任務はCallableにあります.
  • AsyncTaskのexecuteメソッドを呼び出し、シーケンスプロセッサを介してタスクパッケージをRunnableと呼び、Runnableを双方向キューのヘッダに追加し、タスクをテールから取り出してスレッドプールに配置し、スレッドプールに渡して実行します.

  • OK、これで終わります!こんなに簡単で、そんなに多くの注釈を加えて、最終的に800行のコードはこれらの任務を実行して、しかしこのようないくつかの行のコード、無数の髪の毛の知恵を含んで、髪の毛の茂った私、これは恐らくこんなにすばらしいコードを書くことができません......
    千里に至るまで積み重ねないで、私は今ゆっくりと蓄積し始めて、絶えず堅持して、私が間もなく他の人の目の中の大物になることを望んでいます.
    問題がなければ、皆様のご叱正を歓迎いたします.