android源碼解析(5)--AsyncTask源碼分析以及使用中的坑

廢話少說,今天來分析AsyncTask的源碼,看了網上很多人都說使用AsyncTask有最大線程的約束,但是呢,自己寫了一個測試代碼,發現同時扔進去一萬多個線程也沒有問題啊,於是對自己充滿了否定,是不是我用錯了?然後給同事打電話一問,原來就是沒有那個問題了,我去,坑了半重點內容天原來網上分析的都有問題(應該是源碼更新的緣故),還是擼開袖子自己分析源碼吧!

第一.怎麼使用的
首先我們先看看谷歌給的例子,很簡單(源碼中就有)**
   private class DownloadFilesTask extends AsyncTask<URL, Integer, Long>{
      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);
第二.使用很簡單,看看源碼吧
首先我們先分析一下都有什麼屬性,以及作用:
1)第一個線程池構造
    //獲取cup數目
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    //核心線程的數目(構造線程池用)
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    //最大線程數
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    //保活數
    private static final int KEEP_ALIVE = 1;
    //製造線程的工廠
    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());
        }
    };
    //線程隊列,應該是大家看到這裏就都認爲是最多128個線程了
    private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);

    /**
     * An {@link Executor} that can be used to execute tasks in parallel.
     * 這是一個線程池,這是第一個線程池,當然也是真正執行任務的線程池(下面說)
     */
    public static final Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
            TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
2)第二個線程池登場了:
    /**
     * An {@link Executor} that executes tasks one at a time in serial
     * order.  This serialization is global to a particular process.
     */
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    //默認線程池,也就是說我們添加的所有線程默認是添加到這裏的
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

    //看看這個線程池裏有什麼工作吧
    private static class SerialExecutor implements Executor {
        //一個類似於集合的隊列(具體是什麼沒深究)
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        //他的執行方法,下面會調用的
        public synchronized void execute(final Runnable r) {
            //這個方法是說把一個runnable對象添加到隊列的最後面
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            //第一次執行肯定是空了,以後就不會執行了,而是在上面的finally中執行
            if (mActive == null) {
                scheduleNext();
            }
        }

        //操作執行下一個
        protected synchronized void scheduleNext() {
            //這個方法是說獲取隊列中最開始的runnable
            if ((mActive = mTasks.poll()) != null) {
                //重量級登場了,另一個線程池執行任務去了,所以真正執行任務是這個另一個線程池
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }
3)兩個線程池分析完了,再看看handler
    //handler,不用多說吧
    private static InternalHandler sHandler;
    private static class InternalHandler extends Handler {
        public InternalHandler() {
            super(Looper.getMainLooper());
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // 處理完的結果
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    //發佈進度
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

    @SuppressWarnings({"RawUseOfParameterizedType"})
    private static class AsyncTaskResult<Data> {
        //這裏就是下面結束任務是調用finish的mTask
        final AsyncTask mTask;
        final Data[] mData;

        AsyncTaskResult(AsyncTask task, Data... data) {
            mTask = task;
            mData = data;
        }
    }
        //這裏有個判斷,其實就再說明一件事情,就是我們設置取消,不一定會馬上取消(線程就這麼設計的),所以還需要標誌來標識是否取消了
    private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }
4)下面就開始我們從構造器開始看吧:
    //當前的工作
    private final WorkerRunnable<Params, Result> mWorker;
    //對任務的二次封裝
    private final FutureTask<Result> mFuture;

    /**
     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
     * 這裏說創建一個AsyncTask必須要在主線程中,對嗎?分線程不可以嗎?經過本人測試是可以的其實,至於爲
     * 什麼可以,我查閱了一下網上說的爲什麼必須要在主線程,他們說其實主要是因爲handler必須在主線程初始
     * 化,但是我們返回去看看這裏handler是怎麼創建的,
     * public InternalHandler() {
     *      super(Looper.getMainLooper());
     * }
     * 我們可以看到,這裏本來就是得到的主線程的Looper,也就是說不論你在哪個線程創建都會在主線程處理結果
     * 至於爲什麼,那就要分析handler源碼了
     */
    public AsyncTask() {
        //這就是一個具體的工作,傳進去參數,返回來結果
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                Result result = doInBackground(mParams);
                Binder.flushPendingCommands();
                return postResult(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);
                }
            }
        };
    }
我們創建一個AsyncTask,構造器創建了一個要執行的任務,下面就開始分析execute方法:
    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }
    //這裏說明一下:雖然註解註明必須要在主線程中執行,但是經過測試在子線程也可以的,不過onPeExecute
    //方法就不是在主線程中執行了,而是在當前線程執行,onPostExecute方法仍然在主線程
    @MainThread
    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;
    }
好了,任務放到第二個線程池裏了,然後我們可以看看上面第二個線程池幹了什麼工作?將任務放到隊列裏最後面
,然後開始從頭取出任務交給第一個線程池去工作,是不是順起來了。那麼第一個線程池執行的Runnable又是什
麼呢?可以看到其實就是我們構造器中創建的那個mWorker任務,然後再去構造器中看看WorkerRunnable
裏面做了什麼:
         mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);
                //設置優先級
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //開始執行這個方法,具體什麼操作交由我們自己來實現
                Result result = doInBackground(mParams);
                Binder.flushPendingCommands();
                //執行完了還是向主線程發送結果
                return postResult(result);
            }
        };
    //向主線程發送結果
    private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }
然後又回到上面handler裏面處理完成的工作了:
       private static class InternalHandler extends Handler {
        public InternalHandler() {
            super(Looper.getMainLooper());
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // 到這裏處理完成的結果,result.mTask其實就是當前AsyncTask,看上面
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }
繼續:
    private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
        //執行結束
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }
好了,至此整個執行過程全部完整分析結束了。下面開始說說都有什麼坑:

第三:使用中的一些坑
1)就是面試的時候經常會問什麼128個限制啊什麼的,經過分析默認情況下使用是沒有這個限制的,記住是默認,
  但是下面這種用法就會有:
   mMyAsy.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
其實這就是說我們自己傳入了一個線程池,替代了默認的那個線程池,爲什麼會有限制呢?源碼分析分析:
    @MainThread
    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;
    }
2)其實這個坑也不能說是AsyncTask中的坑,就是線程同步問題。我們做相機開發的時候由於bitmap佔用大量內
  存,所以時刻需要主動去回收圖片,但是處理圖片過程還必須放在異步中執行,所以就會出現一個線程已經回收
  了圖片了,另一個還在使用中...掛了。解決方法,個人是說兩個線程各持有一把鎖,如果你想要回收圖片,就
  去檢查對方是否釋放了鎖(也就是處理完了),如果處理完了,則可以回收銷燬,否則我們就把我們自己所持有的
  鎖釋放掉,這樣另一個線程執行完了檢查我們的鎖之後就可以回收了。
3)源碼分析的時候說了,AsyncTask不是只能在主線程中使用,分線程也可以,但是onPreExecute()方法會
  在同一個線程中執行,只有onPostExecute方法會在主線程中執行,原因就是
    private static class InternalHandler extends Handler {
        public InternalHandler() {
            //它默認就去調用主線程的Looper了,所以肯定會到主線程執行最後的結果
            super(Looper.getMainLooper());
        }
        //省略...
    }
不過好像看谷歌的英文註釋,確實是應該在主線程中初始化纔可以,不過谷歌本來就是用來否定的...

4)就是我們調用cancle()方法不一定會立即執行取消操作,因爲這是線程的一個機制,所以會造成什麼結果呢,
  就是加入說我打開了一個頁面,啓動asynctask聯網,這時候呢,我手機屏幕旋轉了,頁面銷燬掉重新創建了
  然後再一次聯網去了,按說頁面銷燬的時候我們會執行取消操作,但是SB不一定馬上執行啊,結果就是第一次聯
  網成功了刷新頁面,頁面早就已經重建了,肯定就Crash了。
發佈了62 篇原創文章 · 獲贊 28 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章