Android進階學習(六)Android的線程和線程池

文本是閱讀《Android開發藝術探索》的學習筆記記錄,詳細內容可以自行閱讀書本。

Android的線程和線程池

  線程在Android中是一個很重要的概念,從用途來說,線程分爲主線程和子線程。主線程主要處理和界面相關的事情,而子線程則往往用於執行耗時操作。
  除了Thread本身以外,在Android中可以扮演線程角色的還有很多,比如AsyncTask和IntentService,同時HandlerThread也是一種特殊的線程。

Android線程 底層實現 特性和使用場景
AsyncTask 封裝了線程池和Handler 方便開發者在子線程更新UI
IntentService 底層使用線程 內部採用HandlerThread來執行任務,執行完成後IntentService自動退出。但是IntentService是一種服務,不容易被系統殺死。
HandlerThread 底層使用線程 具有消息循環的線程,在它內部可以使用Handler

  線程是操作系統調度的最小單元,同時線程又是一種受限的系統資源。如果需要頻繁的創建和銷燬線程,正確做法就是使用線程池。Android中的線程池來源於Java,主要是通過Executor來派生特定類型的線程池。

1 主線程和子線程

  Android中的線程除了傳統的Thread以外,還包含AsyncTask、HandlerThread以及IntentService。

2 Android中的線程形態

2.1 AsyncTask

  AsyncTask是一種輕量級的異步任務類,它可以在線程池中執行後臺任務,然後把執行的進度和最終結果傳遞給主線程並在主線程中更新UI。從實現上來說,AsyncTask封裝了Thread和handler,通過AsyncTask可以更加方便執行後臺任務以及在主線程訪問UI,但AsyncTask並不適合進行特別耗時的後臺任務,對於特別耗時任務,建議使用線程池。

  AsyncTask是一個抽象的泛型類,它提供了Params、Progress和Result三個泛型參數。

參數 含義
Params 表示參數類型
Progress 表示後臺任務的執行進度的類型
Result 表示後臺任務的返回結果的類型
public abstract class AsyncTask<Params, Progress, Result> 

  AsyncTask提供了4個核心方法,含義如下

方法名 含義
onPreExecute() 任務開始時執行,用於進行資源的準備和初始化。
doInBackground(Params… params) 在子線程中運行,用於執行耗時異步任務,Params參數表示異步任務的輸入參數。此方法中可以通過調用publishProgress方法來更新任務進度,publishProgress會調用onProgressUpdate方法。另外此方法需要返回計算結果給onPostExecute
onProgressUpdate(Progress… values) 在主線程中執行,當後臺任務的執行進度發送改變時此方法被調用。
onPostExecute(Result result) 在主線程中執行,在異步任務執行之後,此方法被調用。其中result參數是後臺任務的返回值,即doInBackground的返回值。

  AsyncTask使用過程中,需要注意幾點:

  1)AsyncTask對象必須在主線程中創建。
  2)execute方法必須在UI線程調用。
  3)不要在程序中直接調用onPreExecute、doInBackground、onProgressUpdate和onPostExecute方法。
  4)一個AsyncTask對象只能執行一次,即調用一次execute方法,否則會報異常。

2.2 AsyncTask的工作原理

  分析AsyncTask的工作原理,我們先從它的execute方法開始分析,execute方法又調用executeOnExecutor方法,它們的實現如下:

    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;
    }

  從上面代碼中,sDefaultExecutor實際上是一個串行的線程池,一個進程中所有的AsyncTask全部在這個串行的線程池中排隊執行,稍後分析。在executeOnExecutor方法中,會對狀態進行判斷,如果是正在運行或者已經完成則拋出異常。onPreExecute方法會最先執行,然後線程池開始執行。接下來分析線程池的執行過程:

    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);
            }
        }
    }

  從SerialExecutor的實現分析AsyncTask的排隊執行過程。首先系統會把AsyncTask的Params參數封裝爲FutureTask對象,FutureTask是個併發類,在這裏它充當了Runnable的作用。

  接着SerialExecutor的execute方法首先會把FutureTask對象插入任務隊列mTasks中,如果此時沒有正在活動的AsyncTask,那麼會調用
SerialExecutor的scheduleNext方法來執行下一個AsyncTask任務。同時當一個AsyncTask任務執行完成後,AsyncTask會繼續執行其他任務直到所有任務都被執行完成。可以看出,默認情況下,AsyncTask是串行執行的。

  AsyncTask中有兩個線程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一個Handler(InternalHandler)。其中SerialExecutor用於任務的排隊,而線程池THREAD_POOL_EXECUTOR用於真正地執行任務。InternalHandler用於將執行環境從線程池切換到主線程。

  用於FutureTask的run會調用mWorker的call方法,因此mWorker的call方法最終會在線程池中執行。在AsyncTask的構造方法中,聲明mWorker的call方法。

        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;
            }
        };

  從上述代碼可知,首先將mTaskInvoked設爲true,表示當前任務已經被調用過了。然後執行AsyncTask的doInBackground方法,接着將其返回值傳遞給postResult方法。postResult的實現如下:

    private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }

  從上述代碼可知,postResult方法會通過Handler發送一個MESSAGE_POST_RESULT的消息,從代碼中可以看出這個getHandler方法返回的就是InternalHandler對象,這個InternalHandler定義如下:

    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;
            }
        }
    }

  從上述代碼可知,InternalHandler是一個靜態Handler對象,爲了能夠切換到主線程,就需要InternalHandler對象在主線程中創建。用於靜態成員會在加載類的時候進行初始化,因此就要求AsyncTask必須在主線程創建。再來看看finish方法定義:

    private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

  從上述代碼可知,如果AsyncTask取消就調用onCancelled,否則就調用onPostExecute並把doInBackground的返回值傳遞過來。

2.3 HandlerThread

  HandlerThread繼承了Thread,它是一種可以使用Handler和Thread。它在run方法中通過 Looper.prepare()來創建消息隊列,並通過Looper.loop()來開啓消息循環,這樣在實際使用中就允許在HandlerThread中創建Handler了。

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

  從HandlerThread的實現來看,它和普通Thread有着顯著不同之處。普通Thread主要用於在run方法中執行一個耗時任務,而HandlerThread在內部創建了消息隊列,外界要通過Handler的消息方式來通知HandlerThread執行一個具體的任務。它在Android使用的具體場景是IntentService。
  由於HandlerThread的run是無限循環,因此當明確不需要再使用HandlerThread時,可以通過它的quit或者quitSafely來終止線程的執行。

2.4 IntentService

  IntentService是一個特殊的Service,它繼承Service並且是一個抽象類,因此必須創建它的子類纔可以使用。IntentService可用於執行後臺耗時任務,當任務結束後它會自動停止。因爲IntentService是服務的原因,優先級比起單純線程高很多,所有比較適合執行一些高優先級的後臺任務。

  再實現上,IntentService封裝了HandlerThread和Handler,可以從oncreate方法看出,如下:

    @Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

  從上述代碼可以看出,當IntentService第一次被啓動時,它的onCreate方法會被調用,會創建一個HandlerThread,然後使用它的Looper來構造一個Handler對象mServiceHandler。這樣通過mServiceHandler發送的消息最終會在HandlerThread中執行。

  每次啓動IntentService,它的onStartCommand方法就會調用一次,在onStartCommand方法中處理每個後臺任務的Intent。代碼如下:

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

  可以看出,IntentService僅僅是通過mServiceHandler發送了一個消息,這個消息會在HandlerThread中處理。

  然後消息會被mServiceHandler接收,會將Intent對象傳給onHandleIntent方法來處理。通過解析Intent對象就可以區分具體的後臺任務。當onHandleIntent方法執行完畢後,IntentService會通過stopSelf(int startId)方法來停止服務。不使用stopSelf()方法是因爲,立即停止可能會有未處理完成的其他消息。

  qbIntentService的onHandleIntent是一個抽象方法,需要我們在子類中實現。如果目前只有一個存活的後臺任務,那麼onHandleIntent執行完畢後就會停止服務。

  如果目前存在多個後臺任務,由於每次執行一個後臺任務就必須啓動一次IntentService,而IntentService內部通過消息方式向HandlerThread請求執行任務,Handler中的Looper是順序處理消息的。這意味着IntentService也是順序執行後臺任務的,後臺任務會按照外界發起的順序排隊執行。全部執行完畢後,服務才停止。

3 Android中的線程池

  線程池的三個好處:

  1)重用線程池中的線程,避免因爲線程的創建和銷燬所帶來的性能開銷

  2)能有效控制線程池的最大併發數,避免大量的線程之間因互相搶佔系統資源而導致的阻塞現象。。

  3)能夠對線程進行簡單的管理,並提供定時執行以及指定間隔循環執行等功能。

  Android的線程池的概念來源於Java中的Executor。Executor是一個接口,真正線程池的實現爲ThreadPoolExecutor。ThreadPoolExecutor提供了一系列參數來配置線程池。從線程池的功能特性來說,Android的線程池主要分爲4類。

3.1 ThreadPoolExecutor

  ThreadPoolExecutor是線程池的真正實現,它的構造方法提供了一系列參數來配置線程池。下面是常用的一個構造方法。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }
參數名 含義
corePoolSize 線程池的核心線程數。默認情況下,核心線程會一直在線程池中一直存活,即使處於閒置狀態。除非將ThreadPoolExecutor的allowCoreThreadTimeOut屬性設置爲true,那麼閒置核心線程會在超時後終止。
maximumPoolSize 線程池所能容納的最大線程數,當活動線程數達到這個數值後,後續新任務會被阻塞
keepAliveTime 非核心線程閒置時的超時時長,超過這個時間,非核心線程會被回收。
unit 用於指定keepAliveTime參數的時間單位,常用的有TimeUnit.MILLISECONDS、TimeUnit.SECONDS和TimeUnit.MINUTES
workQueue 線程池中的任務隊列,通過線程池的execute方法提交的Runnable對象會存儲在這個對象參數中
threadFactory 線程工廠,爲線程池提供創建新線程的功能。threadFactory是一個接口,它只有一個方法:Thread newThread(Runnable r)
RejectedExecutionHandler 當線程池無法執行新的任務時,ThreadPoolExecutor會調用handler的rejectExecution方法

  ThreadPoolExecutor執行任務大致遵循如下規則:

  1)如果線程池中的線程數量未達到核心線程的數量,那麼會直接啓動一個核心線程來執行任務。

  2)如果線程池中的線程數量已經達到或者超過核心線程的數量,那麼任務會被插入到任務隊列中排隊等待執行。

  3)如果在步驟2中無法再插入任務,這個時候如果未達到線程池規定的最大值,那麼就會立刻啓動一個非核心線程來執行任務。

  4)如果在步驟3也無法再插入任務,那麼就拒絕執行此任務,ThreadPoolExecutor會調用RejectedExecutionHandler的rejectedExecution方法來通知開發者。

3.2 線程池的分類

  Android常見的4類具有不同功能特性的線程池,它們都是直接或者間接地通過配置ThreadPoolExecutor來實現自己的功能特性。

3.2.1 FixedThreadPool

  通過Executors的newFixedThreadPool方法來創建。它是一種線程數量固定的線程池,當線程處於空閒狀態時,它們不會被回收,除非線程池被關閉。由於FixedThreadPool只有核心線程並且這些核心線程不會被回收,這意味着它能夠更加快速地響應外界的請求。

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

3.2.2 CachedThreadPool

  通過Executors的newCachedThreadPool方法來創建。它是一種線程數量不定的線程池。它只有非核心線程,並且其最大線程數量爲Integer.MAX_VALUE。空閒非核心線程的超時時間爲60s。這類CachedThreadPool比較適合執行大量的耗時較少的任務。

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

3.2.3 ScheduledThreadPool

  通過Executors的ScheduledThreadPoolExecutor方法來創建。它的核心線程數量是固定的,而非核心線程數是沒有限制的,並且非核心線程閒置時會被立即回收。這類ScheduledThreadPoolExecutor線程池主要用於執行定時任務和具有固定週期的重複任務。

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }

3.2.4 SingleThreadExecutor

  通過Executors的newSingleThreadExecutor方法來創建。這類newSingleThreadExecutor線程池內部只有一個核心線程,它確保所有任務都在同一個線程中按順序執行,不需要處理線程同步問題。

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

  舉例了經典用法:

    Runnable runnable = new Runnable() {
        @Override
        public void run() {

        }
    };

        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
        fixedThreadPool.execute(runnable);

        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        cachedThreadPool.execute(runnable);

        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(4);
        scheduledExecutorService.schedule(runnable, 2, TimeUnit.SECONDS);
        scheduledExecutorService.scheduleAtFixedRate(runnable, 10, 2, TimeUnit.SECONDS);

        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        singleThreadExecutor.execute(runnable);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章