1. 程式人生 > >Android HandlerThread 完全解析

Android HandlerThread 完全解析

                     

1、概述

話說最近股市變動不變,也成了熱火朝天的話題。不知道大家有沒有考慮做個實時更新股市資料的app呢?假設我們要做一個股市資料實時更新的app,我們可以在網上找個第三方的股市資料介面,然後在我們的app中每隔1分鐘(合適的時間)去更新資料,然後更新我們的UI即可。

當然了,本文不是要教大家做這樣一個app,只是舉個場景。言歸正傳,回到我們的HandlerThread,大家一定聽說過Looper、Handler、Message三者的關係(如果不瞭解,可以檢視Android 非同步訊息處理機制 讓你深入理解 Looper、Handler、Message三者關係),在我們的UI執行緒默默的為我們服務。其實我們可以借鑑UI執行緒Looper的思想,搞個子執行緒,也通過Handler、Message通訊,可以適用於很多場景。

對了,我之前寫過一篇博文Android Handler 非同步訊息處理機制的妙用 建立強大的圖片載入類,這篇博文中就在子執行緒中建立了Looper,Handler,原理和HandlerThread一模一樣,可惜當時我並不知道這個類,不過大家也可以當做HandlerThread應用場景進行學習。

2、HandlerThread例項

下面看我們模擬大盤走勢的程式碼,其實非常簡單,就一個Activity

package com.zhy.blogcodes.intentservice;import android.os.Bundle;import android.os.Handler;import android.os.HandlerThread;import
android.os.Message;import android.support.v7.app.AppCompatActivity;import android.text.Html;import android.widget.TextView;import com.zhy.blogcodes.R;public class HandlerThreadActivity extends AppCompatActivity{    private TextView mTvServiceInfo;    private HandlerThread mCheckMsgThread;    private
Handler mCheckMsgHandler;    private boolean isUpdateInfo;    private static final int MSG_UPDATE_INFO = 0x110;    //與UI執行緒管理的handler    private Handler mHandler = new Handler();    @Override    protected void onCreate(Bundle savedInstanceState)    {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_thread_handler);        //建立後臺執行緒        initBackThread();        mTvServiceInfo = (TextView) findViewById(R.id.id_textview);    }    @Override    protected void onResume()    {        super.onResume();        //開始查詢        isUpdateInfo = true;        mCheckMsgHandler.sendEmptyMessage(MSG_UPDATE_INFO);    }    @Override    protected void onPause()    {        super.onPause();        //停止查詢        isUpdateInfo = false;        mCheckMsgHandler.removeMessages(MSG_UPDATE_INFO);    }    private void initBackThread()    {        mCheckMsgThread = new HandlerThread("check-message-coming");        mCheckMsgThread.start();        mCheckMsgHandler = new Handler(mCheckMsgThread.getLooper())        {            @Override            public void handleMessage(Message msg)            {                checkForUpdate();                if (isUpdateInfo)                {                    mCheckMsgHandler.sendEmptyMessageDelayed(MSG_UPDATE_INFO, 1000);                }            }        };    }    /**     * 模擬從伺服器解析資料     */    private void checkForUpdate()    {        try        {            //模擬耗時            Thread.sleep(1000);            mHandler.post(new Runnable()            {                @Override                public void run()                {                    String result = "實時更新中,當前大盤指數:<font color='red'>%d</font>";                    result = String.format(result, (int) (Math.random() * 3000 + 1000));                    mTvServiceInfo.setText(Html.fromHtml(result));                }            });        } catch (InterruptedException e)        {            e.printStackTrace();        }    }    @Override    protected void onDestroy()    {        super.onDestroy();        //釋放資源        mCheckMsgThread.quit();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119

可以看到我們在onCreate中,去建立和啟動了HandlerThread,並且關聯了一個mCheckMsgHandler。然後我們分別在onResume和onPause中去開啟和暫停我們的查詢,最後在onDestory中去釋放資源。

這樣就實現了我們每隔5s去服務端查詢最新的資料,然後更新我們的UI,當然我們這裡通過Thread.sleep()模擬耗時,返回了一個隨機數,大家可以很輕易的換成真正的資料介面。

佈局文庫

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"                xmlns:tools="http://schemas.android.com/tools"                android:layout_width="match_parent"                android:layout_height="match_parent"                android:paddingLeft="@dimen/activity_horizontal_margin"                android:paddingRight="@dimen/activity_horizontal_margin"                android:paddingTop="@dimen/activity_vertical_margin"                android:paddingBottom="@dimen/activity_vertical_margin">    <TextView        android:id="@+id/id_textview"        android:text="正在載入大盤指數..."        android:layout_width="wrap_content"        android:layout_height="wrap_content"/></RelativeLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

執行效果圖

別問我為什麼要用紅色!!!

ok,當然了,我們的效果很單調,但是你完全可以去擴充套件,比如ListView顯示使用者關注的股票資料。或者是其他的需要一直檢測更新的資料。

HandlerThread 原始碼分析

對於所有Looper,Handler相關細節統一參考上面提到的文章。

我們輕輕的掀開HandlerThread的原始碼,還記得我們是通過

 mCheckMsgThread = new HandlerThread("check-message-coming"); mCheckMsgThread.start(); 
  • 1
  • 2

建立和啟動的物件,那麼隨便掃一眼:

package android.os;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;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

看到了什麼,其實我們就是初始化和啟動了一個執行緒;然後我們看run()方法,可以看到該方法中呼叫了Looper.prepare(),Loop.loop();

prepare()呢,中建立了一個Looper物件,並且把該物件放到了該執行緒範圍內的變數中(sThreadLocal),在Looper物件的構造過程中,初始化了一個MessageQueue,作為該Looper物件成員變數。

loop()就開啟了,不斷的迴圈從MessageQueue中取訊息處理了,當沒有訊息的時候會阻塞,有訊息的到來的時候會喚醒。如果你不明白我說的,參考上面推薦的文章。

接下來,我們建立了一個mCheckMsgHandler,是這麼建立的:

mCheckMsgHandler = new Handler(mCheckMsgThread.getLooper())
  • 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;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

mCheckMsgThread.getLooper()返回的就是我們在run方法中建立的mLooper。

那麼Handler的構造呢,其實就是在Handler中持有一個指向該Looper.mQueue物件,當handler呼叫sendMessage方法時,其實就是往該mQueue中去插入一個message,然後Looper.loop()就會取出執行。

好了,到這我們就分析完了,其實就幾行程式碼;不過有一點我想提一下:

如果你夠細心你會發現,run方法裡面當mLooper建立完成後有個notifyAll(),getLooper()中有個wait(),這是為什麼呢?因為的mLooper在一個執行緒中執行,而我們的handler是在UI執行緒初始化的,也就是說,我們必須等到mLooper建立完成,才能正確的返回getLooper();wait(),notify()就是為了解決這兩個執行緒的同步問題。

不過對於這樣的執行緒間的同步問題,我非常喜歡使用Semaphore。

 

如果你比較細心,可能會發現裡面還有一些訊號量的操作的程式碼,如果你不瞭解什麼是訊號量,可以參考:Java 併發專題 : Semaphore 實現 互斥 與 連線池 。 簡單說一下mSemaphore(訊號數為1)的作用,由於mPoolThreadHander實在子執行緒初始化的,所以我在初始化前呼叫了mSemaphore.acquire去請求一個訊號量,然後在初始化完成後釋放了此訊號量,我為什麼這麼做呢?因為在主執行緒可能會立即使用到mPoolThreadHander,但是mPoolThreadHander是在子執行緒初始化的,雖然速度很快,但是我也不能百分百的保證,主執行緒使用時已經初始化結束。

哈,當時也有很多人問,為什麼使用這個Semaphore,到這裡我想大家應該清楚了。話說假設我當時真的HanderThread這個類,可能之前的程式碼能簡化不少呢~

對了,你可能會問與Timer相比有什麼優勢呢?

ok~~

 

群號:463081660,歡迎入群

   

微信公眾號:hongyangAndroid   (歡迎關注,第一時間推送博文資訊)