Android 開(kāi)發(fā)藝術(shù)探索讀書(shū)筆記 11 -- Android 的線程和線程池

本篇文章主要介紹以下幾個(gè)知識(shí)點(diǎn):

  • 主線程和子線程
  • Android 中的線程形態(tài)
  • Android 中的線程池
hello税稼,夏天 (圖片來(lái)源于網(wǎng)絡(luò))

11.1 主線程和子線程

從用途上來(lái)說(shuō),線程分主線程和子線程棚点。

主線程舔琅,指進(jìn)程所擁有的線程碗硬,Android 中也叫 UI 線程扛或,主要處理和界面相關(guān)的事情绵咱。

子線程,也叫工作線程(除主線程以外的線程都是子線程)熙兔,往往用于執(zhí)行耗時(shí)操作悲伶。

注:若在主線程中執(zhí)行耗時(shí)操作會(huì)出現(xiàn) ANR。

11.2 Android 中的線程形態(tài)

Android 中的線程形態(tài)除傳統(tǒng)的 Thread 外住涉,還包含 AsyncTask麸锉、HandlerThreadIntentService 等舆声。

11.2.1 AsyncTask

AsyncTask 是一種輕量級(jí)的異步任務(wù)類(lèi)花沉,可在線程池中執(zhí)行后臺(tái)任務(wù),把執(zhí)行的進(jìn)度和最終結(jié)果傳給主線程并更新 UI媳握。它封裝了 ThreadHandler主穗,但不適合進(jìn)行特別耗時(shí)的后臺(tái)任務(wù)(建議使用線程池)。

AsyncTask 是一個(gè)抽象的泛型類(lèi)毙芜,如下:

// 三個(gè)泛型參數(shù):
// 1. Params 參數(shù)的類(lèi)型
// 2. Progress 后臺(tái)任務(wù)的執(zhí)行進(jìn)度的類(lèi)型
// 3. Result 后臺(tái)任務(wù)的返回結(jié)果的類(lèi)型
public abstract class AsyncTask<Params, Progress, Result>{ ... }

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

/**
 * Function:模擬文件下載過(guò)程
 * 輸入?yún)?shù)類(lèi)型為URL争拐,后臺(tái)任務(wù)進(jìn)程參數(shù)為Integer
 * 當(dāng)要執(zhí)行下載任務(wù)時(shí):new DownloadFilesTask().execute(url1, url2, url3);
 */

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

    /**
     * 核心方法 1:
     * 在主線程中執(zhí)行腋粥,在異步任務(wù)執(zhí)行之前調(diào)用,做一些準(zhǔn)備工作
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    /**
     * 核心方法 2:
     * 在主線池中執(zhí)行架曹,用于執(zhí)行異步任務(wù)隘冲,params 是異步任務(wù)的輸入?yún)?shù)
     * 此方法中可用 publishProgress 來(lái)更新任務(wù)進(jìn)度,它會(huì)調(diào)用 onProgressUpdate
     * 另外绑雄,此方法需要返回計(jì)算結(jié)果給 onPostExecute
     */
    @Override
    protected Long doInBackground(URL... params) {
        // 執(zhí)行具體的下載任務(wù)并通過(guò) publishProgress更新下載進(jìn)度
        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;
        }
        // 返回下載的總字節(jié)數(shù)
        return totalSize;
    }

    /**
     * 核心方法 3:
     * 在主線程中執(zhí)行展辞,當(dāng)后臺(tái)任務(wù)的執(zhí)行進(jìn)度發(fā)生改變時(shí)調(diào)用
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        setProgressPercent(values[0]);
    }

    /**
     * 核心方法 4:
     * 在主線程中執(zhí)行,在異步任務(wù)執(zhí)行之后調(diào)用万牺,result 是后臺(tái)任務(wù)的返回值
     */
    @Override
    protected void onPostExecute(Long result) {
        showDialog("Download " + result + "bytes");
    }

    /**
     * 當(dāng)異步任務(wù)被取消時(shí)調(diào)用
     */
    @Override
    protected void onCancelled() {
        super.onCancelled();
    }
}

AsyncTask 在具體使用過(guò)程中也有一些條件限制罗珍,如下:

1. AsyncTask 的類(lèi)必須在主線程中加載洽腺,即第一次訪問(wèn) AsyncTask 必須發(fā)生在主線程。

2. AsyncTask 的對(duì)象必須在主線程中創(chuàng)建覆旱。

3. execute 方法必須在 UI 線程調(diào)用蘸朋。

4. 不要在程序中直接調(diào)用 AsyncTask 提供的4個(gè)核心方法。

5. 一個(gè) AsyncTask 對(duì)象只能執(zhí)行一次扣唱,即只能調(diào)用一次 execute 方法藕坯。

11.2.2 AsyncTask 的工作原理

源碼分析。噪沙。炼彪。

11.2.3 HandlerThread

HandlerThread 繼承了 Thread,是一種可使用 HandlerThread正歼,其實(shí)現(xiàn)核心在 run 方法中:

public void run(){
    mTid = Process.myTid();
    // 創(chuàng)建消息隊(duì)列
    Looper.prepare();
    synchronized(this){
        mLooper = Looper.myLooper();
        notifyAll(); 
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    // 開(kāi)啟消息循環(huán)
    Looper.loop();
    mTid = -1;
}

普通 Thread 主要用于在 run 方法中執(zhí)行一個(gè)耗時(shí)任務(wù)辐马,而 HandlerThread 在內(nèi)部創(chuàng)建了消息隊(duì)列,外界需要通過(guò) Handler 的消息方式來(lái)通知 HandlerThread執(zhí)行一個(gè)具體的任務(wù)朋腋。

HandlerThreadrun 方法是一個(gè)無(wú)線循環(huán)齐疙,當(dāng)不用 HandlerThread 時(shí)可通過(guò)其 quitquitSafely 來(lái)終止線程的執(zhí)行。

11.2.4 IntentService

IntentService 是一個(gè)繼承了 Service 的抽象類(lèi)旭咽,是一種特殊的 Service贞奋。

IntentService 可用于執(zhí)行后臺(tái)耗時(shí)任務(wù),任務(wù)執(zhí)行后它會(huì)自動(dòng)停止穷绵,其優(yōu)先級(jí)比單純的線程要高轿塔,時(shí)候執(zhí)行一些高優(yōu)先級(jí)的后臺(tái)任務(wù)。

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.創(chuàng)建一個(gè) HandlerThread 
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        // 2.構(gòu)造一個(gè) Handler 對(duì)象 mServiceHandler 
        // 這樣通過(guò) mServiceHandler 發(fā)送的消息最終都會(huì)在 HandlerThread 中執(zhí)行
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

每次啟動(dòng) IntentService勾缭,它的 onStartCommand 方法就會(huì)調(diào)用一次,處理每個(gè)后臺(tái)任務(wù)的 Intent目养,onStartCommand 調(diào)用了 onStart 方法如下:

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

其中 ServiceHandler 的實(shí)現(xiàn)如下:

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

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

IntentServiceonHandleIntent 是一個(gè)抽象方法俩由,其作用是從 intent 參數(shù)中區(qū)分具體的任務(wù)并執(zhí)行這些任務(wù)。

11.3 Android 中的線程池

線程池的優(yōu)點(diǎn)可概括為以下3點(diǎn):

1. 重用線程池中的線程癌蚁,避免因?yàn)榫€程的創(chuàng)建和銷(xiāo)毀所帶來(lái)的性能開(kāi)銷(xiāo)幻梯。

2. 能有效控制線程池的最大并發(fā)數(shù),避免大量的線程之間因互相搶占系統(tǒng)資源而導(dǎo)致的阻塞現(xiàn)象努释。

3. 能夠?qū)€程進(jìn)行簡(jiǎn)單的管理碘梢,并提供定時(shí)執(zhí)行以及指定間隔循環(huán)執(zhí)行等功能。

Android 中的線程池都是直接或間接通過(guò)配置 ThreadPoolExecutor 來(lái)實(shí)現(xiàn)的伐蒂。

11.3.1 ThreadPoolExecutor

ThreadPoolExecutor 是線程池的真正實(shí)現(xiàn)煞躬,其構(gòu)造方法提供了一系列參數(shù)來(lái)配置線程池如下:

// corePoolSize 線程池的核心線程,默認(rèn)情況下會(huì)在線程池中一直存活
// maximumPoolSize 線程中所能容納的最大線程數(shù)
// keepAliveTime 非核心線程閑置時(shí)的超時(shí)時(shí)長(zhǎng),超過(guò)這個(gè)時(shí)長(zhǎng)非核心線程就會(huì)被回收
// unit 用于指定 keepAliveTime 參數(shù)的時(shí)間單位
// workQueue 線程池中的任務(wù)隊(duì)列恩沛,通過(guò)線程池的 execute 方法提交的 Runnable 對(duì)象會(huì)存儲(chǔ)在此參數(shù)中
// threadFactory 線程工廠在扰,為線程池提供創(chuàng)建新線程的功能
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable>workQueue,
                          ThreadFactory threadFactory)

ThreadPoolExecutor 執(zhí)行任務(wù)時(shí)大致遵循如下規(guī)則:

1. 若線程池中的線程數(shù)量未達(dá)到核心線程的數(shù)量,那么會(huì)直接啟動(dòng)一個(gè)核心線程來(lái)執(zhí)行任務(wù)复唤。

2. 若線程池中的線程數(shù)量已達(dá)到或超過(guò)核心線程的數(shù)量健田,那么任務(wù)會(huì)被插入到任務(wù)隊(duì)列中等待執(zhí)行。

3. 若在步驟 2 中無(wú)法將任務(wù)插入到任務(wù)隊(duì)列中佛纫,此時(shí)若線程數(shù)量未達(dá)到線程池規(guī)定的最大值妓局,則會(huì)立刻啟動(dòng)一個(gè)非核心線程來(lái)執(zhí)行任務(wù)。

4. 若步驟 3 中線程數(shù)量已達(dá)到線程池規(guī)定的最大值呈宇,那么就拒絕執(zhí)行任務(wù)好爬,會(huì)調(diào)用 RejectedExecutionHandlerrejectExecution 來(lái)通知調(diào)用者。

ThreadPoolExecutor 的配置參數(shù)可在 AsyncTask 中體現(xiàn)甥啄,配置后的線程池規(guī)格如下:

  • 核心線程數(shù)等于 CPU 核心數(shù) + 1

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

  • 核心線程無(wú)超時(shí)機(jī)制存炮,非核心線程子閑置時(shí)的超時(shí)時(shí)間為 1 秒

  • 任務(wù)隊(duì)列的容量為 128

11.3.2 線程池的分類(lèi)

1. FixedThreadPool

通過(guò) executorsnewFixedThreadPool 方法來(lái)創(chuàng)建,是一種線程數(shù)量固定的線程池蜈漓,當(dāng)線程處于空閑時(shí)不會(huì)被回收(除非線程池被關(guān)閉了)穆桂,其實(shí)現(xiàn)如下:

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

2. CachedThreadPool

通過(guò) executorsnewCachedThreadPool 方法來(lái)創(chuàng)建融虽,是一種線程數(shù)量不定的線程池享完,只有非核心線程,且最大線程數(shù)為 Integer.MAX_VALUE有额,其實(shí)現(xiàn)如下:

public static ExecutorService newCachedThreadPool(){
    // 線程池中的空閑線程都有超時(shí)機(jī)制時(shí)長(zhǎng)為60秒般又,超過(guò)60秒閑置線程就會(huì)被回收
    // CachedThreadPool 的任務(wù)隊(duì)列相當(dāng)于一個(gè)空集合,任何任務(wù)都會(huì)被立即執(zhí)行
    // 這類(lèi)線程池比較適合執(zhí)行大量的耗時(shí)較少的任務(wù)
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>());
}

3. ScheduledThreadPool

通過(guò) executorsnewScheduledThreadPool 方法來(lái)創(chuàng)建巍佑,其核心線程數(shù)固定茴迁,非核心線程數(shù)無(wú)限制,其實(shí)現(xiàn)如下:

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

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

4. SingleThreadExecutor

通過(guò) executorsnewSingleThreadExecutor 方法來(lái)創(chuàng)建萤衰,內(nèi)部只有一個(gè)核心線程堕义,確保所有的任務(wù)都在同一個(gè)線程中按順序執(zhí)行。其實(shí)現(xiàn)如下:

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

以上便是 Android 中常見(jiàn)的 4 種線程池胳螟,實(shí)際開(kāi)發(fā)也可根據(jù)需要靈活配置線程池。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 后執(zhí)行 command
scheduledThreadPool .schedule(command, 2000, TimeUnit.MILLISECONDS);
// 延遲 10ms 后筹吐,每隔 1000ms 執(zhí)行 一次command
scheduledThreadPool .scheduleAtFixedRate(command, 10, 1000, TimeUnit.MILLISECONDS); 

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

本篇文章就介紹到這。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末秘遏,一起剝皮案震驚了整個(gè)濱河市丘薛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌邦危,老刑警劉巖洋侨,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舍扰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡希坚,警方通過(guò)查閱死者的電腦和手機(jī)边苹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)裁僧,“玉大人个束,你說(shuō)我怎么就攤上這事×钠#” “怎么了茬底?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)获洲。 經(jīng)常有香客問(wèn)我阱表,道長(zhǎng),這世上最難降的妖魔是什么贡珊? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任最爬,我火速辦了婚禮,結(jié)果婚禮上门岔,老公的妹妹穿的比我還像新娘爱致。我一直安慰自己,他們只是感情好固歪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布蒜鸡。 她就那樣靜靜地躺著,像睡著了一般牢裳。 火紅的嫁衣襯著肌膚如雪逢防。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,590評(píng)論 1 305
  • 那天蒲讯,我揣著相機(jī)與錄音忘朝,去河邊找鬼。 笑死判帮,一個(gè)胖子當(dāng)著我的面吹牛局嘁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播晦墙,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼悦昵,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了晌畅?” 一聲冷哼從身側(cè)響起但指,我...
    開(kāi)封第一講書(shū)人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后棋凳,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體拦坠,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年剩岳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贞滨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拍棕,死狀恐怖晓铆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情莫湘,我是刑警寧澤尤蒿,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站幅垮,受9級(jí)特大地震影響腰池,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜忙芒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一示弓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呵萨,春花似錦奏属、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至忱嘹,卻和暖如春嘱腥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拘悦。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工凯正, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留水泉,地道東北人膳沽。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓崔列,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親屁桑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子医寿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 主席(任玉剛)當(dāng)時(shí)在他群里發(fā)這書(shū)簡(jiǎn)介的時(shí)候,只看目錄就知道是我想要的蘑斧,哈哈糟红,書(shū)還是有些難度的艾帐,不過(guò)真是一本超棒的書(shū)...
    HuDP閱讀 5,740評(píng)論 14 69
  • Android中的線程 線程,在Android中是非常重要的盆偿,主線程處理UI界面,子線程處理耗時(shí)操作准浴。如果在主線程...
    shenhuniurou閱讀 755評(píng)論 0 3
  • 從用途上來(lái)說(shuō)事扭,線程分為主線程和子線程,主線程主要處理和界面相關(guān)的事情乐横,子線程則往往用于執(zhí)行耗時(shí)操作求橄。 除了Thre...
    小柏不是大白閱讀 629評(píng)論 0 3
  • ====《學(xué)習(xí)的藝術(shù)》讀書(shū)筆記==== 摘自豆瓣 前言 p4 讀此書(shū)時(shí),不要做筆記葡公。首次閱讀時(shí)罐农,若讀小說(shuō)。瀏覽后—...
    侯波尼閱讀 670評(píng)論 0 0
  • 微風(fēng)帶不動(dòng)細(xì)雨 傳來(lái)陣陣漣漪 遠(yuǎn)近都是月的影 映襯出朵朵烏云 月光開(kāi)始潑灑 隨著風(fēng)逐的浪花 水草變得靜謐 荷葉下跳...
    慎思篤行月閱讀 398評(píng)論 1 2