Android 線程相關(guān) | 藝術(shù)探索筆記

在 Android 中,除了 Thread 以外,還有 AsyncTask绢记、HandlerThread 和 IntentService 充當(dāng)線程的作用扁达,它們各自有不同的特點和適用場景。

AsyncTask 封裝了線程池和 Handler蠢熄,它能方便使用者在子線程中更新 UI跪解。HandlerThread 是使用 Handler 的線程。IntentService 是一個服務(wù)签孔,它比一般的后臺線程優(yōu)先級更高叉讥,不容易被系統(tǒng)殺死,所以可以更方便的執(zhí)行后臺線程饥追。

AsyncTask

AsyncTask 是一個輕量級的異步任務(wù)類图仓,它不適合進(jìn)行特別耗時的任務(wù)。

用法

public abstract class AsyncTask<Params, Progress, Result>

AsyncTask 是一個抽象的泛型類但绕,它提供了 Params救崔、Progress、Result 三個泛型參數(shù)捏顺。Params 表示參數(shù)的類型六孵,Progress 表示后臺任務(wù)的執(zhí)行進(jìn)度,Result 表示后臺任務(wù)返回結(jié)果的類型幅骄。如果 AsyncTask 不需要傳遞參數(shù)劫窒,以上三個都可以用 Void 來替代。

AsyncTask 有四個核心方法

  1. onPreExecute()
  2. doInBackground(Params... params)
  3. onProgressUpdate(Progress... values)
  4. onPostExecute()

onPreExecute 方法在主線程執(zhí)行拆座,一般用于準(zhǔn)備工作主巍。

doInBackground 方法會在線程池中執(zhí)行異步任務(wù),并將計算結(jié)果返回給 onPostExecute 方法挪凑。在該方法中孕索,可以調(diào)用 publishProgress 方法來更新 UI。

當(dāng)調(diào)用 publishProgress 方法后岖赋,onProgressUpdate 方法會在主線程中執(zhí)行檬果,它用于進(jìn)行 UI 操作。

當(dāng) doInBackground 方法完成后唐断,onPostExecute 方法將會執(zhí)行,它也存在于主線程杭抠。

需要注意的是脸甘,AsyncTask 還存在一個 onCancelled 方法。當(dāng)異步任務(wù)被取消時偏灿,它會被調(diào)用丹诀。該方法一旦被調(diào)用就不會再調(diào)用 onPostExecute 方法。它也同樣是在主線程中執(zhí)行。

注意事項

  • AsyncTask 必須在主線程中加載
  • AsyncTask 對象必須在主線程中創(chuàng)建
  • 不要在程序中直接使用 onPreExecute铆遭、doInBackground硝桩、onProgressUpdate、onPostExecute 四個方法
  • 一個 AsyncTask 對象只能執(zhí)行一次 execute 方法
  • 從 Android 3.0 開始枚荣,AsyncTask 采用一個線程串行執(zhí)行任務(wù)碗脊,可以使用 executeOnExecutor 方法調(diào)整為并行

源碼分析

先看 execute 方法

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

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

execute 方法調(diào)用了 executeOnExecutor 方法,在 executeOnExecutor 方法中橄妆,onPreExecute 方法首先得到執(zhí)行衙伶,然后是 sDefaultExecute變量的 execute 方法。由于sDefaultExecute是 SerialExecutor 類害碾,于是我們轉(zhuǎn)到該類

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 中可以看到矢劲,execute 方法首先會把 Runnable 對象,也就是傳入的mFuture慌随,插入到隊列 ArrayDeque 中芬沉。當(dāng)隊列頭部存在任務(wù)時,會調(diào)用 FutureTask 類(mFuture)的 run 方法阁猜,并通過 scheduleNext 方法獲取下一個任務(wù)花嘶。如果隊列頭部沒有任務(wù),就會直接調(diào)用 scheduleNext 方法來獲取任務(wù)蹦漠。接著看 FutureTask 的 run 方法

public void run() {

    ...
    
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            ...
        }
    } finally {
        ...
    }
}

可以看到椭员,在這個方法中會調(diào)用 callable 的 call 方法,那這個方法是什么笛园?

public AsyncTask() {
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            mTaskInvoked.set(true);

            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            //noinspection unchecked
            return postResult(doInBackground(mParams));
        }
    };

    mFuture = new FutureTask<Result>(mWorker) {
        ...
    };
}

public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}

根據(jù)這一段代碼隘击,再結(jié)合前面的關(guān)系,execute 方法最終是調(diào)用了mWork的 call 方法研铆。在這個方法中埋同,mTaskInvoked被設(shè)為true,表示當(dāng)前任務(wù)已經(jīng)被調(diào)用棵红,然后會執(zhí)行 doInBackground 方法凶赁,并將返回值傳給 postResult 方法

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

在 postResult 方法中,sHandler 會發(fā)送出MESSAGE_POST_RESULT逆甜,來看 sHandler

private static final InternalHandler sHandler = new InternalHandler();

private static class InternalHandler extends Handler {
    @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;
        }
    }
}

在這段代碼中虱肄,sHandler是一個靜態(tài)的 Handler 對象,而靜態(tài)成員會在加載類時初始化交煞,為了能切換回主線程咏窿,這個sHandler必須在主線程中創(chuàng)建,這就相當(dāng)于要求 AsyncTask 在主線程中加載素征。收到MESSAGE_POST_RESULT后會調(diào)用 finish 方法集嵌,來看

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

如果任務(wù)被取消了萝挤,將會調(diào)用 onCancelled 方法。如果正常執(zhí)行根欧,會調(diào)用 onPostExecute 方法怜珍,參數(shù)是 doInBackground 方法的結(jié)果。

HandlerThread

它是可以使用 Handler 的 Thread凤粗,看它的 run 方法

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

在 run 方法中酥泛,通過Looper.prepare()創(chuàng)建消息隊列侈沪,Looper.loop()開啟消息循環(huán)揭璃。

HandlerThread 和 Handler 一樣,使用時需要通過 sendMessage 方法發(fā)送消息來處理任務(wù)亭罪。使用 HandlerThread 時瘦馍,需要通過 start 開啟線程,并實現(xiàn) handleMessage 方法來完成處理邏輯

HandlerThread handlerThread = new HandlerThread("HandlerThread");
handlerThread.start();
Handler workderHandler = new Handler(handlerThread.getLooper()) {
    @Override
    public boolean handleMessage(Message msg) {
        // 處理消息
        return true;
    }
}

當(dāng)使用完畢后应役,記得通過 quit 或 quitSafely 方法退出線程循環(huán)情组,避免內(nèi)存泄漏。

IntentService

IntentService 是一個抽象類箩祥,它繼承了 Service院崇。使用時需要創(chuàng)建它的子類。IntentService 一般用于執(zhí)行后臺程序袍祖,原因是它屬于 Service底瓣,導(dǎo)致它比一般線程優(yōu)先級要高,更不容易被殺死蕉陋。它適用于高優(yōu)先級的后臺任務(wù)捐凭。

使用方法

必須實現(xiàn) onHandleIntent 方法以執(zhí)行后臺任務(wù)〉树蓿可根據(jù)需求重寫 onCreate茁肠、onStartCommand、onDestroy 方法缩举。

源碼分析

IntentService 內(nèi)部封裝了 Handler 和 HandlerThread垦梆。先看它的 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();
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();

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

第一次啟動 IntentService 會調(diào)用它的 onCreate 方法,這個方法中會創(chuàng)建 HandlerThread仅孩。

再看 onStartCommand 方法

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

onStartCommand 方法調(diào)用了 onStart 方法托猩,那么轉(zhuǎn)到 onStart

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

在 onStart 方法中,intent被賦給msg.obj杠氢,并通過 ServiceHandler 實例發(fā)送了出去站刑,ServiceHandler 繼承自 Handler,意味著這條消息最終會來到 handleMessage 方法中鼻百,于是轉(zhuǎn)到該方法

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

在這里看到绞旅,會在 onHandleIntent 方法中進(jìn)行處理,結(jié)合前面的代碼可以知道温艇,該方法中的 Intent 對象和通過 startIntent 方法中轉(zhuǎn)入的 Intent 對象是相同的因悲,我們可以通過這個 Intent 對象解析出外界啟動 IntentService 時傳入的參數(shù),通過這些參數(shù)就可區(qū)分具體的后臺任務(wù)勺爱。onHandleIntent 是一個抽象方法晃琳,需要我們在子類中實現(xiàn)。

stopSelf(mig.arg1)方法用來嘗試停止任務(wù)琐鲁。注意區(qū)分stoopSelf(int startId)方法和stopSelf()方法卫旱,前者會等所有消息處理完之后才停止服務(wù),而后者會立刻停止服務(wù)围段。

線程池

線程池有以下幾個優(yōu)點:

  • 重用線程池中的線程顾翼,減少線程創(chuàng)建和銷毀的性能開銷
  • 控制最大線程數(shù),避免因線程過多而搶占系統(tǒng)資源導(dǎo)致的阻塞
  • 能夠?qū)M(jìn)程進(jìn)行管理

Android 中的線程池來自 Java 的 Executor奈泪,它是一個接口适贸,真正的實現(xiàn)是 ThreadPoolExecutor。

ThreadPoolExecutor

基本介紹

它的構(gòu)造方法提供了一系列參數(shù)來配置線程池

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}
  • corePoolSize

線程池的核心線程數(shù)涝桅,默認(rèn)情況下拜姿,核心線程會一直在線程池里存活。

  • maximumPoolSize

最大線程數(shù)冯遂,當(dāng)活動線程達(dá)到最大值時蕊肥,后續(xù)的任務(wù)會被阻塞。

  • keepAliveTime

非核心線程存活時間蛤肌,當(dāng)非核心線程閑置時間超過它時壁却,就會被回收。當(dāng) ThreadPoolExecutor 的 allowCoreThreadTimeOut 設(shè)置為true時寻定,keepAliveTime 同樣會作用于核心線程儒洛。

  • unit

keepAliveTime 的時間單位,常用的有狼速,TimeOut.MILLISECONDS(毫秒)琅锻、TimeOut.SECONDS(秒)。

  • workQueue

任務(wù)隊列向胡,execute 方法提交的 Runnable 對象會儲存在這里恼蓬。

  • threadFactory

線程工廠,為線程池提供創(chuàng)建新線程的功能僵芹。它是一個接口处硬。

大致執(zhí)行規(guī)則

  1. 當(dāng)線程池中線程數(shù)小于核心線程數(shù),啟動核心線程執(zhí)行該任務(wù)
  2. 當(dāng)線程池中線程數(shù)大于等于核心線程數(shù)拇派,將任務(wù)加入任務(wù)隊列
  3. 當(dāng)任務(wù)隊列已滿荷辕,并且未達(dá)到線程池最大值凿跳,啟動非核心線程來執(zhí)行該任務(wù)
  4. 當(dāng)任務(wù)數(shù)大于最大線程數(shù)時,拒接執(zhí)行任務(wù)疮方,會調(diào)用 RejectedExecutionHandler 的 rejectedExecution 方法通知調(diào)用者

線程池的分類

  • FixedThreadPool

一種數(shù)量恒定的線程池控嗜,只有核心線程,且核心線程沒有超時機制骡显。這意味著疆栏,除非線程池被關(guān)閉,否則這些線程將一直存在惫谤。并且它的任務(wù)隊列大小沒有限制壁顶。該線程可以通過 newFixedThreadPool 方法來創(chuàng)建,方法如下

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

一種數(shù)量不定的線程池溜歪,只有非核心線程若专,基本上無數(shù)量限制。這些線程的超時時間設(shè)置為 60 秒痹愚。它有一個特殊的任務(wù)隊列 SynchronousQueue富岳,可以被理解為一個無法儲存元素的隊列。所以拯腮,當(dāng)有新的任務(wù)加入且無閑置線程時窖式,會立刻啟動一個線程執(zhí)行該任務(wù)。該線程池適用于執(zhí)行大量耗時少的任務(wù)动壤。它使用 newCachedThreadPool 創(chuàng)建萝喘,方法如下

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

它的核心線程數(shù)是固定的,而非核心線程數(shù)沒有限制琼懊,但只要非核心線程閑置阁簸,就馬上會被回收。它適用于執(zhí)行定時和有固定周期的任務(wù)哼丈。使用 newScheduledThreadPool 創(chuàng)建启妹,方法如下

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

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

該線程池只有一個線程,即核心線程醉旦,它確保所有的任務(wù)都在一個線程中按順序執(zhí)行饶米。當(dāng)所有的任務(wù)都添加到該線程時,可以不必考慮同步問題车胡。它使用 newSingleThreadExecutor 方法創(chuàng)建檬输,如下

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市匈棘,隨后出現(xiàn)的幾起案子丧慈,更是在濱河造成了極大的恐慌,老刑警劉巖主卫,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逃默,死亡現(xiàn)場離奇詭異鹃愤,居然都是意外死亡,警方通過查閱死者的電腦和手機笑旺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門昼浦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來馍资,“玉大人筒主,你說我怎么就攤上這事∧裥罚” “怎么了乌妙?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長建钥。 經(jīng)常有香客問我藤韵,道長,這世上最難降的妖魔是什么熊经? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任泽艘,我火速辦了婚禮,結(jié)果婚禮上镐依,老公的妹妹穿的比我還像新娘匹涮。我一直安慰自己,他們只是感情好槐壳,可當(dāng)我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布然低。 她就那樣靜靜地躺著,像睡著了一般务唐。 火紅的嫁衣襯著肌膚如雪雳攘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天枫笛,我揣著相機與錄音吨灭,去河邊找鬼。 笑死刑巧,一個胖子當(dāng)著我的面吹牛喧兄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播海诲,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼群扶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了下隧?” 一聲冷哼從身側(cè)響起辰企,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蚯斯,沒想到半個月后薄风,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饵较,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年遭赂,在試婚紗的時候發(fā)現(xiàn)自己被綠了循诉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡撇他,死狀恐怖茄猫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情困肩,我是刑警寧澤划纽,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站锌畸,受9級特大地震影響勇劣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜潭枣,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一比默、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧盆犁,春花似錦命咐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至翰铡,卻和暖如春钝域,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锭魔。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工例证, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人迷捧。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓织咧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親漠秋。 傳聞我的和親對象是個殘疾皇子笙蒙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,086評論 2 355

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