Android開發中四種常用的多線程實現方式

****一般來說,一個應用至少有一個進程,一個進程至少有一個線程。線程是CPU調度的基本單位,進程是系統資源分配的基本單位。**

**進程擁有獨占的內存資源,一個進程可以看作一個JVM一個進程崩潰后,一般不會影響保護模式下的其他進程。同一進程中的線程共享內存資源,一個線程的死亡導致整個進程的死亡。****

Android開發四種常用的多線程實現方式:

  • AsyncTask
  • 異步消息機制
  • IntentService
  • ThreadPoolExcutor

1.AsyncTask

Android AsyncTask 類,它是封裝好的線程池,操作 UI 線程極其方便。
AsyncTask 的三個泛型參數:

public abstract class AsyncTask<Params, Progress, Result>
  • Params ,傳入參數類型,即 doInBackground() 方法中的參數類型;
  • Progress,異步任務執行過程中返回的任務執行進度類型,

即 publishProgress() 和onProgressUpdate() 方法中傳入的參數類型;

  • Result,異步任務執行完返回的結果類型\,即 doInBackground() 方法中返回值的類型。

四個回調方法:

  • onPreExecute(),在主線程執行,做一些準備工作。
  • doInBackground(),在線程池中執行,該方法是抽象方法,在此方法中可以調用 publishProgress() 更新任務進度。
  • onProgressUpdate(),在主線程中執行,在 publishProgress() 調用之后被回調,展示任務進度。
  • onPostExecute(),在主線程中執行,異步任務結束后,回調此方法,處理返回結果。

注意:

  • 當AsyncTask 任務被取消時,回調 onCanceled(obj) ,此時onPostExecute(),不會被調用,AsyncTask 中的 cancel()方法并不是真正去取消任務,只是設置這個任務為取消狀態,需要在 doInBackground() 中通過 isCancelled()判斷終止任務。
  • AsyncTask 必須在主線程中創建實例,execute() 方法也必須在主線程中調用。
  • 每個 AsyncTask實例只能執行一execute() ,多次執行會報錯,如需執行多次,則需創建多個實例。
  • Android 3.0 之后,AsyncTask 對象默認執行多任務是串行執行,即 mAsyncTask.execute() ,并發執行的話需要使用executeOnExecutor()。
  • AsyncTask 用的是線程池機制和異步消息機制(基于 ThreadPoolExecutor和 Handler )。Android 2.3 以前,AsyncTask 線程池容量是 128 ,全局線程池只有 5 個工作線程,如果運用 AsyncTask 對象來執行多個并發異步任務,那么同一時間最多只能有 5 個線程同時運行,其他線程將被阻塞。
  • Android 3.0之后 Google 又進行了調整,新增接口 executeOnExecutor() ,允許自定義線程池(那么核心線程數以及線程容量也可自定義),并提供了 SERIAL_EXECUTOR 和THREAD_POOL_EXECUTOR 預定義線程池。后來 Google 又做了一些調整(任何事物都不完美),將線程池的容量與 CPU的核心數聯系起來,如目前 SDK 25 版本中,預定義的核心線程數量最少有 2 個,最多 4 個,線程池容量范圍 5 ~ 9 。

改動如下:

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;

2. 異步消息機制

異步消息機制的三大主角: Handler ,MessageLooper

Looper 負責創建 MessageQueue 消息對列,然后進入一個無限 for 循環中,不斷地從消息隊列中取消息,如果消息隊列為空,當前線程阻塞,Handler 負責向消息隊列中發送消息。

Looper

Looper 有兩個重要的方法: prepare() 和 loop()。

prepare() , Looper 與當前線程綁定,一個線程只能有一個 Looper 實例和一個 MessageQueue 實例。

public static final void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(true)); 保證 Looper 對象在當前線程唯一
}

// Looper 的構造方法
private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mRun = true;
        mThread = Thread.currentThread();
}

loop ,進入一個無限 for 循環體中,不斷地從消息隊列中取消息,然后交給消息的 target 屬性的 dispatchMessage 方法去處理。

public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();


        // 無限循環體,有沒有想過在 UI 線程里,有這樣一個死循環,為什么界面沒卡死??
        for (;;) { 
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            msg.target.dispatchMessage(msg);

            msg.recycle();
        }
}

Handler

Handler 負責向消息隊列中發送消息。
在 Activity 中我們直接可以 new Handler ,那是因為在 Activity 的啟動代碼中,已經在當前 UI 線程中調用了 Looper.prepare() 和 Looper.loop() 方法。

在子線程中 new Handler 必須要在當前線程(子線程)中創建好 Looper 對象和消息隊列,代碼如下

//在子線程中

Looper.prepare();

handler = new Handler() {

public void handleMessage(Message msg) {
    //處理消息
};

};

Looper.loop();

之后,你拿著這個 Handler 對象就可以在其他線程中,往這個子線程的消息隊列中發消息了。

HandlerThread

HandlerThread 可以看作在子線程中創建一個異步消息處理機制的簡化版,HandlerThread 對象自動幫我們在工作線程里創建 Looper 對象和消息隊列。

使用方法:

mHandlerThread = new HandlerThread("MyHandlerThread");
mHandlerThread.start();

mHandler = new Handler(mHandlerThread.getLooper()){

    @Override
    public void handleMessage(Message msg) {
        //處理消息
    }
};

之后你就可以使用 Handler 對象往工作線程中的消息隊列中發消息了。

看一下源碼片段:

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    protected void onLooperPrepared() {
    }

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

    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }

        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
}

注意:handler 在 UI 線程中初始化的,looper 在一個子線程中執行,我們必須等 mLooper 創建完成之后,才能調用 getLooper ,源碼中是通過 wait 和 notify 解決兩個線程的同步問題。

3. IntentService

IntentService 可以看成是 Service 和 HandlerThread 的合體。它繼承自 Service ,并可以處理異步請求,其內部有一個 WorkerThread 來處理異步任務,當任務執行完畢后,IntentService 自動停止。

如果多次啟動 IntentService 呢? 看到 HandlerThread ,你就應該想到多次啟動 IntentService ,就是將多個異步任務放到任務隊列里面,然后在 onHandlerIntent 回調方法中串行執行,執行完畢后自動結束。

下面對源碼進行簡單的解析,IntentService 源碼:

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }
        @Override
        public void handleMessage(Message msg) {
            //onHandleIntent 方法在工作線程中執行,執行完調用 stopSelf() 結束服務。
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }
    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public IntentService(String name) {
        super();
        mName = name;
    }

    /**
     * enabled == true 時,如果任務沒有執行完,當前進程就死掉了,那么系統就會令當前進程重啟。
     * 任務會被重新執行。
     */
    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }
    @Override
    public void onCreate() {
        super.onCreate();

        // 上面已經講過,HandlerThread 對象 start 之后,會在工作線程里創建消息隊列 和 Looper 對象。
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        // 獲得 Looper 對象初始化 Handler 對象。
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        // IntentService 每次啟動都會往工作線程消息隊列中添加消息,不會創建新的線程。
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    // 官方建議 IntentService onStartCommand 方法不應該被重寫,注意該方法會調用 onStart 。
    @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 onDestroy() {  
        //服務停止會清除消息隊列中的消息,除了當前執行的任務外,后續的任務不會被執行。
        mServiceLooper.quit();
    }
    /**
     * 不建議通過 bind 啟動 IntentService ,如果通過 bind 啟動 IntentService ,那么 onHandlerIntent 方法不會被回調。Activity 與 IntentService 之間的通信一般采用廣播的方式。
     */
    @Override
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }
    /**
     * 子類必須要實現,執行具體的異步任務邏輯,由 IntentService 自動回調。
     */
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}

IntentService 源碼很容易理解,你也可以就自己的應用場景封裝自己的 IntentService 。

幾種場景:

  • 正常情況下,啟動 IntentService ,任務完成,服務停止;
  • 異步任務完成前,停止 IntentService,服務停止,但任務還會執行完成,完成后,工作線程結束;
  • 多次啟動 IntentService,任務會被一次串行執行,執行結束后,服務停止;
  • 多次啟動 IntentService ,在所有任務執行結束之前,停止 IntentService ,服務停止,除了當前執行的任務外,后續的任務不會被執行;

4. ThreadPoolExcutor

image.png

ThreadPool

用來管理一組工作線程,任務隊列( BlockingQueue )中持有的任務等待著被線程池中的空閑線程執行。

常用構造方法:

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
    int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue
);
  • corePoolSize 核心線程池容量,即線程池中所維持線程的最低數量。corePoolSize 初始值為 0,當有新任務加入到任務隊列中,新的線程將被創建,這個時候即使線程池中存在空閑線程,只要當前線程數小于 corePoolSize,那么新的線程依然被創建。
  • maximumPoolSize 線程池中所維持線程的最大數量。
  • keepAliveTime 空閑線程在沒有新任務到來時的存活時間。
  • unit 參數 keepAliveTime 的時間單位。
  • workQueue 任務隊列,必須是 BlockingQueue 。

簡單使用

創建 ThreadFactory ,當然也可以自定義。

private static final ThreadFactory sThreadFactory = new ThreadFactory() {
    private final AtomicInteger mCount = new AtomicInteger(1);

    public Thread newThread(Runnable r) {
        return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
    }
};

創建 ThreadPoolExecutor 。

// 根據 CPU 核心數確定線程池容量。
public static final int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors(); 

mThreadPoolExecutor = new ThreadPoolExecutor(
        NUMBER_OF_CORES * 2, 
        NUMBER_OF_CORES * 2 + 1,
        60L,
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<Runnable>(),
        backgroundPriorityThreadFactory
);

執行。

mThreadPoolExecutor.execute(new Runnable() { 

    @Override  
    public void run() {  
         //do something  
    } 

});

Future future = mThreadPoolExecutor.submit(new Runnable() { 

    @Override  
    public void run() {  
         //do something  
    } 

});

//任務可取消
future.cancel(true);

Future<Integer> futureInt = mThreadPoolExecutor.submit(new Callable<Integer>() {
    @override
    public Integer call() throws Exception {
        return 0;
    }

});

//獲取執行結果
futureInt.get();

FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>(){
    @override
    public Integer call() throws Exception {
        return 0;
    }    

});

mThreadPoolExecutor.submit(task);
task.get();
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內容