Android 開發藝術探索讀書筆記 11 -- Android 的線程和線程池

本篇文章主要介紹以下幾個知識點:

  • 主線程和子線程
  • Android 中的線程形態
  • Android 中的線程池
hello,夏天 (圖片來源于網絡)

11.1 主線程和子線程

從用途上來說,線程分主線程和子線程。

主線程,指進程所擁有的線程,Android 中也叫 UI 線程,主要處理和界面相關的事情。

子線程,也叫工作線程(除主線程以外的線程都是子線程),往往用于執行耗時操作。

注:若在主線程中執行耗時操作會出現 ANR。

11.2 Android 中的線程形態

Android 中的線程形態除傳統的 Thread 外,還包含 AsyncTaskHandlerThreadIntentService 等。

11.2.1 AsyncTask

AsyncTask 是一種輕量級的異步任務類,可在線程池中執行后臺任務,把執行的進度和最終結果傳給主線程并更新 UI。它封裝了 ThreadHandler,但不適合進行特別耗時的后臺任務(建議使用線程池)。

AsyncTask 是一個抽象的泛型類,如下:

// 三個泛型參數:
// 1. Params 參數的類型
// 2. Progress 后臺任務的執行進度的類型
// 3. Result 后臺任務的返回結果的類型
public abstract class AsyncTask<Params, Progress, Result>{ ... }

AsyncTask 提供了4個核心方法,典型示例如下:

/**
 * Function:模擬文件下載過程
 * 輸入參數類型為URL,后臺任務進程參數為Integer
 * 當要執行下載任務時:new DownloadFilesTask().execute(url1, url2, url3);
 */

public class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {

    /**
     * 核心方法 1:
     * 在主線程中執行,在異步任務執行之前調用,做一些準備工作
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    /**
     * 核心方法 2:
     * 在主線池中執行,用于執行異步任務,params 是異步任務的輸入參數
     * 此方法中可用 publishProgress 來更新任務進度,它會調用 onProgressUpdate
     * 另外,此方法需要返回計算結果給 onPostExecute
     */
    @Override
    protected Long doInBackground(URL... params) {
        // 執行具體的下載任務并通過 publishProgress更新下載進度
        int count = params.length;
        long totalSize = 0;
        for (int i = 0; i < count; i++){
            totalSize += Downloader.downloadFile(params[i]);
            publishProgress((int)(i/(float)count) * 100);
            if (isCancelled())//判斷是否被取消
                break;
        }
        // 返回下載的總字節數
        return totalSize;
    }

    /**
     * 核心方法 3:
     * 在主線程中執行,當后臺任務的執行進度發生改變時調用
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        setProgressPercent(values[0]);
    }

    /**
     * 核心方法 4:
     * 在主線程中執行,在異步任務執行之后調用,result 是后臺任務的返回值
     */
    @Override
    protected void onPostExecute(Long result) {
        showDialog("Download " + result + "bytes");
    }

    /**
     * 當異步任務被取消時調用
     */
    @Override
    protected void onCancelled() {
        super.onCancelled();
    }
}

AsyncTask 在具體使用過程中也有一些條件限制,如下:

1. AsyncTask 的類必須在主線程中加載,即第一次訪問 AsyncTask 必須發生在主線程。

2. AsyncTask 的對象必須在主線程中創建。

3. execute 方法必須在 UI 線程調用。

4. 不要在程序中直接調用 AsyncTask 提供的4個核心方法。

5. 一個 AsyncTask 對象只能執行一次,即只能調用一次 execute 方法。

11.2.2 AsyncTask 的工作原理

源碼分析。。。

11.2.3 HandlerThread

HandlerThread 繼承了 Thread,是一種可使用 HandlerThread,其實現核心在 run 方法中:

public void run(){
    mTid = Process.myTid();
    // 創建消息隊列
    Looper.prepare();
    synchronized(this){
        mLooper = Looper.myLooper();
        notifyAll(); 
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    // 開啟消息循環
    Looper.loop();
    mTid = -1;
}

普通 Thread 主要用于在 run 方法中執行一個耗時任務,而 HandlerThread 在內部創建了消息隊列,外界需要通過 Handler 的消息方式來通知 HandlerThread執行一個具體的任務。

HandlerThreadrun 方法是一個無線循環,當不用 HandlerThread 時可通過其 quitquitSafely 來終止線程的執行。

11.2.4 IntentService

IntentService 是一個繼承了 Service 的抽象類,是一種特殊的 Service

IntentService 可用于執行后臺耗時任務,任務執行后它會自動停止,其優先級比單純的線程要高,時候執行一些高優先級的后臺任務。

IntentService 封裝了 HandlerThreadHandler,這在其 onCreate 方法中可知:

    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();
        // 1.創建一個 HandlerThread 
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        // 2.構造一個 Handler 對象 mServiceHandler 
        // 這樣通過 mServiceHandler 發送的消息最終都會在 HandlerThread 中執行
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

每次啟動 IntentService,它的 onStartCommand 方法就會調用一次,處理每個后臺任務的 IntentonStartCommand 調用了 onStart 方法如下:

public void onStart(Intent intent, int startId){
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent;
    // IntentService 通過 mServiceHandler 發送了一個消息
    // 這個消息會在 HandlerThread 中被處理
    mServiceHandler.sendMessage(msg);
}

其中 ServiceHandler 的實現如下:

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

    @Override
    public void handleMessage(Message msg){
        onHandleIntent((Intent)mag.obj);
        // 停止服務
        stopSelf(msg.arg1);
    }
}

IntentServiceonHandleIntent 是一個抽象方法,其作用是從 intent 參數中區分具體的任務并執行這些任務。

11.3 Android 中的線程池

線程池的優點可概括為以下3點:

1. 重用線程池中的線程,避免因為線程的創建和銷毀所帶來的性能開銷。

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

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

Android 中的線程池都是直接或間接通過配置 ThreadPoolExecutor 來實現的。

11.3.1 ThreadPoolExecutor

ThreadPoolExecutor 是線程池的真正實現,其構造方法提供了一系列參數來配置線程池如下:

// corePoolSize 線程池的核心線程,默認情況下會在線程池中一直存活
// maximumPoolSize 線程中所能容納的最大線程數
// keepAliveTime 非核心線程閑置時的超時時長,超過這個時長非核心線程就會被回收
// unit 用于指定 keepAliveTime 參數的時間單位
// workQueue 線程池中的任務隊列,通過線程池的 execute 方法提交的 Runnable 對象會存儲在此參數中
// threadFactory 線程工廠,為線程池提供創建新線程的功能
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable>workQueue,
                          ThreadFactory threadFactory)

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

1. 若線程池中的線程數量未達到核心線程的數量,那么會直接啟動一個核心線程來執行任務。

2. 若線程池中的線程數量已達到或超過核心線程的數量,那么任務會被插入到任務隊列中等待執行。

3. 若在步驟 2 中無法將任務插入到任務隊列中,此時若線程數量未達到線程池規定的最大值,則會立刻啟動一個非核心線程來執行任務。

4. 若步驟 3 中線程數量已達到線程池規定的最大值,那么就拒絕執行任務,會調用 RejectedExecutionHandlerrejectExecution 來通知調用者。

ThreadPoolExecutor 的配置參數可在 AsyncTask 中體現,配置后的線程池規格如下:

  • 核心線程數等于 CPU 核心數 + 1

  • 線程池的最大線程數為 CPU 核心數的 2 倍 + 1

  • 核心線程無超時機制,非核心線程子閑置時的超時時間為 1 秒

  • 任務隊列的容量為 128

11.3.2 線程池的分類

1. FixedThreadPool

通過 executorsnewFixedThreadPool 方法來創建,是一種線程數量固定的線程池,當線程處于空閑時不會被回收(除非線程池被關閉了),其實現如下:

public static ExecutorService newFixedThreadPool(int nThreads){
    // FixedThreadPool 只有核心線程并且沒用超時機制,任務隊列也沒有大小限制
    // 意味著它能夠更加快速地響應外界的請求
    return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockQueue<Runnable>());
}

2. CachedThreadPool

通過 executorsnewCachedThreadPool 方法來創建,是一種線程數量不定的線程池,只有非核心線程,且最大線程數為 Integer.MAX_VALUE,其實現如下:

public static ExecutorService newCachedThreadPool(){
    // 線程池中的空閑線程都有超時機制時長為60秒,超過60秒閑置線程就會被回收
    // CachedThreadPool 的任務隊列相當于一個空集合,任何任務都會被立即執行
    // 這類線程池比較適合執行大量的耗時較少的任務
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>());
}

3. ScheduledThreadPool

通過 executorsnewScheduledThreadPool 方法來創建,其核心線程數固定,非核心線程數無限制,其實現如下:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize){
    // 這類線程池主要用于執行定時任務和具有固定周期的重復任務
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

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

4. SingleThreadExecutor

通過 executorsnewSingleThreadExecutor 方法來創建,內部只有一個核心線程,確保所有的任務都在同一個線程中按順序執行。其實現如下:

public static ExecutorService newSingleThreadExecutor(){
    // SingleThreadExecutor 的意義在于統一所有的外界任務到一個線程中,使得這些任務之間不需要處理線程同步的問題
    return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}

以上便是 Android 中常見的 4 種線程池,實際開發也可根據需要靈活配置線程池。4 種線程池的使用方法如下:

Runnable command = new Runnable(){
    @Override
    public void run(){
        SystemClock.sleep(2000);
    }
};

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

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

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4);
// 2000ms 后執行 command
scheduledThreadPool .schedule(command, 2000, TimeUnit.MILLISECONDS);
// 延遲 10ms 后,每隔 1000ms 執行 一次command
scheduledThreadPool .scheduleAtFixedRate(command, 10, 1000, TimeUnit.MILLISECONDS); 

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
singleThreadExecutor .execute(command);

本篇文章就介紹到這。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容