Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 從AsyncTask學Android線程池

從AsyncTask學Android線程池

編輯:關於Android編程

android對於主線程的響應時間限制的非常嚴格,稍有不慎就會遇到Application Not Responding(ANR)的彈框。用戶可以輕點手指關掉你的APP。官方文檔寫的非常明確!同時,保持應用隨時響應用戶的操作也是良好用戶體驗的前提。

線程的開始和結束

要做到以上多線程是必不可少的。課本會告訴你什麼時候開辟一個線程,但是很少說的一個很重要的問題是結束。比如,我現在在Activity裡有一個工作需要創建一個線程執行,但是這個Activity在進入後台後不幸遇到系統回收資源被銷毀了。但是這個線程還在漫無目的的游走,耗費資源。

如何結束?先創建一個:

mThread = Thread(Runnable {
    // do something here...
})

mThread?.start()

以上使用kotlin的lambda表達式簡寫了創建Runnable對象部分的代碼。主旨還是創建了一個Runnable對象,並將其作為參數傳入Thread

如何讓一個Thread能夠退出呢?這就要在Runnable身上下功夫了。首先添加一個是否停止的標識isCancelled,一旦值為true則停止線程的運行,否則繼續。我們這裡不討論Thread#interrupt()這個方法,這個方法詭異的地方太多。

首先要給Runnable“添加一個屬性”作為上文的是否停止的標識。直接添加時不可能的,Runnable只是一個interface,不是class。所以要實現這個借口為一個抽象類,這樣就可以添加屬性了。

abstract class CancelableRunnable() : Runnable {
    var isCancelled: Boolean = false
}

這裡使用抽象類,是因為run()方法的實現留給使用的時候給出。

var runnable = object : CancelableRunnable() {
    override fun run() {
        if (isCancelled) {
            var msg = mHandler.obtainMessage(THREAD_CANCELLED)
            mHandler.sendMessage(msg)
            return
        }

        Thread.sleep(2000)

        if (isCancelled) {
            var msg = mHandler.obtainMessage(THREAD_CANCELLED)
            mHandler.sendMessage(msg)
            return
        }

        var msg = mHandler.obtainMessage(THREAD_FINISHED)
        mHandler.sendMessage(msg)
    }
}

Thread.sleep(2000)用來模擬一個費時的任務。開始之前檢測是否取消了線程的執行,執行之後在檢測。之後的檢測是有的時候任務執行之後需要有持久化處理結果或者修改任務完成情況的標識之類的動作,如果已經取消了線程的執行,即使任務執行完成也不持久化結果、不修改完成情況。

最後都檢測完成之後如果沒有取消線程,則發出任務完成執行的消息。

發出和處理這些消息的Handler的定義:

var mHandler = object : Handler() {
    override fun handleMessage(msg: Message?) {
        when (msg?.what) {
            THREAD_CANCELLED -> {
                mResultTextView.text = "thread cancelled"
            }
            THREAD_FINISHED -> {
                mResultTextView.text = "thread finished"
            }
            else -> {
                mResultTextView.text = "what's going on"
            }
        }
    }
}

運行在UI線程的Handler檢測從線程發出的消息,如果是THREAD_CANCELLED那就是線程已經取消了,如果是THREAD_FINISHED那就是線程完全運行結束。之後根據message的消息設置TextView的文本內容。

這裡使用了兩個按鈕來啟動和停止線程:

findViewById(R.id.start_button)?.setOnClickListener { v ->
    runnable.isCancelled = false
    mThread = Thread(runnable)
    mThread?.start()

    mResultTextView.text = "Thread running..."
}

findViewById(R.id.stop_button)?.setOnClickListener { v ->
    this.runnable.isCancelled = true
}

上面用到的Runnable是只做一件事的,如果是連續不斷的循環很多事的話也可以使用white語句來控制是否一直執行線程的工作。一旦設置為停止線程,則停止線程任務的循環跳出Runnable#run()方法,結束線程。

完整代碼放在附錄中
所以,如果你在Activity裡開辟了一個線程,在Activity被回收的時候結束線程就可以這麼做:

override fun onDestroy() {
    super.onDestroy()

    this.runnable.isCancelled = true
}

這樣就再也不用擔心Activity掛了,線程還陰魂不散了。

AsyncTask

既然緣起AsyncTask那就肯定需要讀者一起了解一下相關的概念。

比起來使用Handler+Thread+Runnable的多線程異步執行模式來說,使用AsyncTask是簡單了非常的多的。

先簡單了解一下AsyncTask

public abstract class AsyncTask

AsyncTask是一個抽象泛型類。三個類型ParamsProgressResult分別對應的是輸入參數的類型,精度更新使用的類型,最後是返回結果的類型。其中任何一個類型如果你不需要的話,可以使用java.lang.Void代替。

繼承AsyncTask給出自己的實現,最少需要實現doInBackground方法。doInBackground方法是在後台線程中運行的。如果要在任務執行之後更新UI線程的話還至少需要給出onPostExecute方法的實現,在這個方法中才可以更新UI。

上述的兩個方法已經構成了一個AsyncTask使用的基本單元。在後台線程處理一些任務,並在處理完成之後更新UI。但是如果一個任務比較長,只是在最後更新UI是不夠的,還需要不斷的提示用戶已經完成的進度是多少。這就是需要另外實現onProgressUpdate方法。並在doInBackground方法中調用publishProgress方法發出每個任務的處理進度。

這個AsyncTask總體上就是這樣的了:

inner class DemoAsyncTask() : AsyncTask() {
    //        var isRunning = true
    override fun doInBackground(vararg params: String?): String? {
        Log.i(TAG, "##AsyncTask doing something...")

        var i = 0
        val TOTAL = 100000000
        var progress = 0
        while (i < TOTAL) {
            Log.d(TAG, "doning jobs $i is cancelled $isCancelled")
            i++

            var currentProgress = i.toFloat() / TOTAL
            if (currentProgress > progress && Math.abs(currentProgress - progress) > 0.1) {
                progress = currentProgress
                publishProgress((progress * 100).toInt())
            }
        }
        }

        Log.d(TAG, "doing jobs $i is cancelled $isCancelled")

        return "Task done"
    }

    override fun onPostExecute(result: String?) {
        [email protected]?.text = result
    }

    override fun onProgressUpdate(vararg values: Int?) {
        mAsyncTextView?.text = "${mAsyncTextView?.text ?: "Async task..."} progress: ${values?.get(0) ?: 0}"
    }
}

到這裡各位讀者應該對AsyncTask已經有一個總體的認識了。後台任務在doInBackground處理,處理過程的百分比使用publishProgress方法通知,並在onProgressUpdate方法中更新UI的百分比。最後任務處理全部完成之後在onPostExecute更新UI,顯示全部完成。

怎麼取消一個任務的執行呢?這個機本身還上面的線程的取消基本上一樣。只是AsyncTask已經提供了足夠的屬性和方法完成取消的工作。直接調用AsyncTask#cancel方法就可以發出取消的信號,但是是否可以取消還要看這個方法的返回值是什麼。如果是true那就是可以,否則任務不可取消(但是不可取消的原因很可能是任務已經執行完了)。

調用cancel方法發出取消信號,並且可以取消的時候。isCancelled()就會返回true。同時onPostExecute這個方法就不會再被調用了。而是onCancelled(object)方法被調用。同樣是在doInBackground這個方法執行完之後調用。所以,如果想要在取消任務執行後盡快的調用到onCancelled(object)的話,就需要在onInBackground的時候不斷的檢查isCancelled()是否返回true。如果返回的是true就跳出方法的執行。

inner class DemoAsyncTask() : AsyncTask() {
    //        var isRunning = true
    override fun doInBackground(vararg params: String?): String? {
        Log.i(TAG, "##AsyncTask doing something...")

        var i = 0
        val TOTAL = 1000000
        var progress = 0.0f
        while (i < TOTAL && !isCancelled) {
            Log.d(TAG, "doning jobs $i is cancelled $isCancelled")
            i++

            var currentProgress = i.toFloat() / TOTAL
            if (currentProgress > progress && Math.abs(currentProgress - progress) > 0.1) {
                progress = currentProgress
                publishProgress((progress * 100).toInt())
            }
        }

        Log.d(TAG, "doning jobs $i is cancelled $isCancelled")

        return "Task done"
    }

    override fun onPostExecute(result: String?) {
        [email protected]?.text = result
    }

    override fun onProgressUpdate(vararg values: Int?) {
        mAsyncTextView?.text = "${mAsyncTextView?.text ?: "Async task..."} progress: ${values?.get(0) ?: 0}"
    }

    override fun onCancelled() {
        Log.i(TAG, "##Task cancelled")
//            isRunning = false
        [email protected]?.text = "###Task cancelled"
    }

//        override fun onCancelled(result: String?) {
//            Log.i(TAG, "##Task cancelled")
////            isRunning = false
//            [email protected]?.text = result ?: "Task cancelled"
//        }
}

onCancelled()是API level 3的時候加入的。onCancelled(Result result)是API level 11的時候加入的。這個在兼容低版本的時候需要注意。

但是一點需要格外注意:

AsyncTask一定要在UI線程初始化。不過在**JELLY_BEAN**以後這個問題也解決了。
總之,在UI線程初始化你的`AsyncTask`肯定是不會錯的。

線程池

下面就來看看線程池的概念。顧名思義,線程池就是放線程的池子。把費時費力,或者影響響應用戶操作的代碼放在另外一個線程執行時常有的事。但是如果無顧忌的開辟線程,卻會適得其反,嚴重的浪費系統資源。於是就有了線程池。線程池就是通過某些機制讓線程不要創建那麼多,能復用就復用,實在不行就讓任務排隊等一等

這個機制在線程池的構造函數裡體現的非常明顯:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
corePoolSize 線程池裡閒著也不回收的線程數量。除非allowCoreThreadTimeOut指定可以回收。 * maximumPoolSize* 線程池允許的最大線程數。 * keepAliveTime* 非核心線程(就是如果核心線程數量corePoolSize定義為1的話,第二個就是非核心線程)的超時時間。 unit keepAliveTime的時間單位,毫秒,秒等。 * workQueue* 存放execute(Runnable cmd)方法提交的Runnable任務。 * threadFactory*線程池用來創建新線程用的一個工廠類。 * handler*線程池達到最大線程數,並且任務隊列也已經滿的時候會拒絕execute(Runnable cmd)方法提交任務。這個時候調用這個handler。

知道以上基本內容以後,就可以探討線程池管理線程的機制了。概括起來有三點:
1. 如果線程池的線程數量少於corePoolSize的時候,線程池會使用threadFactory這個線程工廠創建新的線程執行Runnable任務。
2. 如果線程池的線程數量大於corePoolSize的時候,線程池會把Runnable任務存放在隊列workQueue中。
3. 線程池的線程數量大於corePoolSize,隊列workQueue已滿,而且小於maximumPoolSize的時候,線程池會創建新的線程執行Runnable任務。否則,任務被拒。

現在回到AsyncTask。被人廣為诟病的AsyncTask是他的任務都是順序執行的。一個AsyncTask的實例只能處理一個任務。但是在AsyncTask後面處理任務的是一個靜態的線程池。在看這個線程池SerialExecutorexecute方法實現:

final ArrayDeque mTasks = new ArrayDeque();

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) {
        // 執行一個task
    }
}

這個線程池SerialExecutor在處理Runnable的傳入參數的時候對這個任務進行了重新包裝成了一個新的Runnable對象,並且將這個新的對象存入了一個叫做mTasks的隊列。這個新的Runnable對象首先執行傳入的任務,之後不管有無異常調用scheduleNext方法執行下一個。於是整體的就生成了一個傳入的任務都順序執行的邏輯。

這個線性執行的靜態線程池SerialExecutor的實現非常簡單。並不涉及到我們前文所說的那麼多復雜的內容。在實現上,這個線程池只實現了線程池的最頂層接口Executor。這個接口只有一個方法就是execute(Runnable r)。另外需要強調一點:mTasks的類型ArrayDeque是一個不受大小限制的隊列。可以存放任意多的任務。在線程池的討論中遇到隊列就需要看看容量概念。

SerialExecutor只是進行了簡單的隊列排列。但是在scheduleNext方法的實現上又會用到一個復雜一些的線程池來執行任務的具體執行。這線程池叫做
THREAD_POOL_EXECUTOR。我們來具體看看其實現:

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;

public static final Executor THREAD_POOL_EXECUTOR
        = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

這個線程池的實現非常具有現實價值。雖然稍後介紹的系統提供的幾種線程池的實現就夠用。但是難免遇到一些需要自定義線程池的情況。詳細解析如下:
* CORE_POOL_SIZE 線程池的核心線程數量為設備核心數加一。
* * MAXIMUM_POOL_SIZE* 線程池的最大線程數量為核心數的兩倍加一。
* * KEEP_ALIVE* 線程池中非核心線程的超時時間為一秒。
* * sPoolWorkQueue * 線程池存放任務的隊列。最大個數為128個。參考上面說的線程池處理機制,會出現任務被拒的情況。排隊的線程池SerialExecutor存放任務的隊列是可以認為無限長的,但是THREAD_POOL_EXECUTOR的隊列最多存放128個任務,加上線程池核心線程的數量,能處理的任務相對有限。出現任務被拒的情況的幾率比較大。所以,往AsyncTask裡直接添加Runnable對象的時候需要三思。
* * sThreadFactory* 線程池用來創建線程的工廠對象。ThreadFactory是一個只有一個方法Thread newThread(Runnable r);的接口。這裡在實現的時候給新創建的線程添加了一個原子計數,並把這個計數作為線程名稱傳遞給了線程的構造函數。

到這裡,我們就已經很清楚AsyncTask是如何用一個極其簡單的線程池SerialExecutor給任務排隊的。又是如何使用一個復雜一些的線程池THREAD_POOL_EXECUTOR來處理具體的任務執行的。尤其是線程池THREAD_POOL_EXECUTOR,在我們實際應用一個自定義的線程池的時候在設定線程池核心線程數量,線程池最大線程數量的時候都依據什麼?明顯就是設備的CPU核心數。線程分別在不同個CPU核心中做並行的處理。核心數多可以同時處理的線程數就相對較多,相反則會比較少一些。如此設置核心線程數量就會平衡並行處理的任務數量和在處理的過程中耗費的系統資源。

為了讓開發者省時省力,系統默認的提供了四種可以適應不同應用條件的線程池:

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

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

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

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
}
* newFixedThreadPool* 顧名思義,線程數量固定的線程池,且其數量等於參數指定值。這一類型的線程池的核心線程數量和最大線程數量是一樣的。存放任務的隊列的容量可以被認為無限大。一旦線程池創建的線程數量等* nThreads*參數值的時候,新增的任務將會被存放在任務隊列中等待核心線程可用的時候執行。 * newSingleThreadExecutor* newFixedThreadPool的一個特殊情況,當mThreads值為1的時候。 * newCachedThreadPool* 這一類型的線程池中創建的線程都有60秒的超時時間,由於超時時間比較長等於是線程空閒了以後被緩存了60秒。由於核心線程數量為0,所以創建的線程都是非核心線程。也因此超時時間才管用。任務隊列SynchronousQueue非常特殊,簡單理解就是一個任務都存放不了。而線程池的最大線程數量又設定為Integer.MAX_VALUE,可以認為是無限大。根據線程池處理任務的機制,可以認為有新任務過來就會創建一個線程去處理這個任務,但是如果存在空閒沒有超時的線程會優先使用。 * newScheduledThreadPool* 生成一個ScheduledThreadPoolExecutor實例。可以通過其提供的接口方法設定延遲一定的時間執行或者隔一定的時間周期執行。

來一個例子:

import static java.util.concurrent.TimeUnit.*;
class BeeperControl {
    private final ScheduledExecutorService scheduler =
    Executors.newScheduledThreadPool(1);

    public void beepForAnHour() {
        final Runnable beeper = new Runnable() {
            public void run() { System.out.println("beep"); 
        };
        final ScheduledFuture beeperHandle =
            scheduler.scheduleAtFixedRate(beeper, 10, 10, SECONDS);
        scheduler.schedule(new Runnable() {
            public void run() { beeperHandle.cancel(true); }
        }, 60 * 60, SECONDS);
    }
}}

附錄

這裡是上面例子中使用的全部代碼。

線程的停止:

package demo.retrofit2rxjavademo.Activities

import android.os.Bundle
import android.os.Handler
import android.os.Message
import android.support.v7.app.AppCompatActivity
import android.widget.TextView
import demo.retrofit2rxjavademo.R

class CancalableActivity : AppCompatActivity() {
    lateinit var mResultTextView: TextView
    var mHandler = object : Handler() {
        override fun handleMessage(msg: Message?) {
            when (msg?.what) {
                THREAD_CANCELLED -> {
                    mResultTextView.text = "thread cancelled"
                }
                THREAD_FINISHED -> {
                    mResultTextView.text = "thread finished"
                }
                else -> {
                    mResultTextView.text = "what's going on"
                }
            }
        }
    }

    var mThread: Thread? = null

    var runnable = object : CancelableRunnable() {
        override fun run() {
            if (isCancelled) {
                var msg = mHandler.obtainMessage(THREAD_CANCELLED)
                mHandler.sendMessage(msg)
                return
            }

            Thread.sleep(2000)

            if (isCancelled) {
                var msg = mHandler.obtainMessage(THREAD_CANCELLED)
                mHandler.sendMessage(msg)
                return
            }

            var msg = mHandler.obtainMessage(THREAD_FINISHED)
            mHandler.sendMessage(msg)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_cancalable)

        mResultTextView = findViewById(R.id.run_result_text_view) as TextView

        findViewById(R.id.start_button)?.setOnClickListener { v ->
            runnable.isCancelled = false
            mThread = Thread(runnable)
            mThread?.start()

            mResultTextView.text = "Thread running..."
        }

        findViewById(R.id.stop_button)?.setOnClickListener { v ->
            this.runnable.isCancelled = true
        }
    }

    abstract class CancelableRunnable() : Runnable {
        var isCancelled: Boolean = false
    }

    companion object {
        val THREAD_FINISHED = 0
        val THREAD_CANCELLED = 1
    }
}
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved