3、簡單分析例子
1)、原始頁面效果
2)、控制台初始化列印的線程ID
分析:在onCreate方法裡面列印的主線程的Id為1,有3個按鈕,分别是主線程向子線程發消息,子線程向主線程發消息,子線程和子線程發送消息,我們一開始就是對HandlerThread程序初始化,其實它就是一個線程,後面會分析,然後執行了start方法,我們把HandlerThread的looper對象傳遞給了Handler,之前的文章已分析,一個線程隻能有一個looper對象,Handler擁有了HandlerThread的looper對象,就相當于這個Handler在HandlerThread線程同樣的線程Id,可以了解為Handler 在子線程裡面建構,為什麼我這裡還有其它的Handler建構,因為想搞清楚在哪裡建構屬于哪個線程以及子線程的handler是否可以更新UI,主線程建構的handler是否可以更新UI,
3)、依次點選3個按鈕後控制台列印的日志
我們可以看到在onCreate方法裡面始化handler的時候傳遞了一個HandlerThread的looper對象,點選第一個按鈕後後沒有開啟線程,目前線程依然是主線程,handler發送了一個消息,然後初始化的handlMessage收到消息了,也就完成了主線程到子線程的通信,當收到消息的時候,我們發現線程的id是6314,是以這個時候雖然是在onCreate裡面建構的handler裡面的handlerMessage方法,但是線程Id是和HandlerThread線程Id是一樣的,然後初始化handler去更新界面,我們代碼是用mHandlerCtopP去更新的,因為它的初始化是在主線程建構的,是以可以post可以更新UI,但是這個時候用擁有HandlerThread的looper對象的handler更新界面就會出問題,和子線程裡面的handler去更新界面異常一樣,如下圖
然後點選第二個按鈕,是現實子線程向主線程通信,我們發現點選時間裡面的線程Id,和handler收到消息的 handleMessage方法裡面的線程id, 都是一樣,和主線程Id一樣,是以我們可以用這個handler直接post來更新UI,
點選第三個按鈕,是實作子線程和子線程的通信,執行點選方法,我們開啟了一個線程,自然線程Id會和主線程的不一樣,為6318, handler收到消息的 handleMessage方法裡面的線程id為6314,是以這裡可以了解為為子線程線程裡面建構了handler,然後用主線程建構的handler更新ui
4)、依次點選3個按鈕後手機效果
4、HandlerThread.java源碼分析
1、上源代碼
package android.os;
/**
* Handy class for starting a new thread that has a looper. The looper can then be
* used to create handler classes. Note that start() must still be called.
*/
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
/**
* Constructs a HandlerThread.
* @param name
* @param priority The priority to run the thread at. The value supplied must be from
* {@link android.os.Process} and not from java.lang.Thread.
*/
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
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;
}
/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason is isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
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;
}
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
/**
* Returns the identifier of this thread. See Process.myTid().
*/
public int getThreadId() {
return mTid;
}
}
1、我們知道這個類在android.os目錄下,然後繼承了Thread,也就是一個線程
2、構造方法會傳一個字元串,這裡可以随便寫,隻作為一個辨別而已,初始化的時候會調用start方法,然後會執行run()方法
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
先執行了Looper,prepare(),然後給目前mLooper指派,然後進行Looper.loop(),進行輪尋,這個代碼和标準的子線程用handler的方式差不多,也可以了解為這種方式的封裝,是以才能顯示子線程和子線程和主線程之間的通信,原理都是一樣。
3、getLooper()中有個wait(),這有什麼用呢?因為的mLooper在一個線程中執行建立,而我們的handler是在UI線程中調用getLooper()初始化的,必須等到mLooper建立完成,才能正确的傳回。getLooper();wait(),notify()就是為了解決這兩個線程的同步問題。
4、這裡有個退出的方法是
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
我之前寫代碼的時候,以為停止線程就調用了,mHandlerThread.stop()方法,後面沒發現什麼問題,直到我們有個功能需要,從控制台下發消息讓手機恢複預設出場,函數執行到了這裡,導緻程序崩潰了,然後就發現這裡有問題,需要調用mHandlerThread.quit()方法正常退出。
5、總結
有時還需要頻繁更新UI,或則主線程向子線程通信,以及子線程和子線程經常通信的時候,我們可以使用HandlerThread,如果哪裡沒說清楚,或則講得有問題,歡迎點評