編輯:關於Android編程
初次看到HandlerThread的名字,我們可能會聯想到Handler和Thread這兩個類,沒錯,它其實就是跟Handler和Thread有莫大的關系。HandlerThread繼承自Thread,它本質上就是一個Thread,而且專門用來處理Handler的消息。
看看官方對它的解釋:
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.
大致就是說HandlerThread可以創建一個帶有looper的線程,looper對象可以用於創建Handler類來進行來進行調度,而且start()方法必須被調用。
在Android開發中,不熟悉多線程開發的人一想到要使用線程,可能就用new Thread(){…}.start()這樣的方式。實質上在只有單個耗時任務時用這種方式是可以的,但若是有多個耗時任務要串行執行呢?那不得要多次創建多次銷毀線程,這樣導致的代價是很耗系統資源,容易存在性能問題。那麼,怎麼解決呢?
我們可以只創建一個工作線程,然後在裡面循環處理耗時任務,創建過程如下:
Handler mHandler;
private void createWorkerThread() {
new Thread() {
@Override
public void run() {
super.run();
Looper.prepare();
mHandler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
......
}
};
Looper.loop();
}
}.start();
}
在該工作線程中,
- 調用Looper.prepare()創建與當前線程綁定的Looper實例;
- 使用上面創建的Looper生成Handler實例;
- 調用Looper.loop()實現消息循環;
然後透過Looper的循環,在Handler的handlerMessage()中進行異步任務的循環處理。而這也正好是HandlerThread的實現。
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
那麼我們看下HandlerThread有哪些特點:
- HandlerThread本質上是一個線程類,它繼承了Thread;
- HandlerThread有自己的內部Looper對象,通過Looper.loop()進行looper循環;
- 通過獲取HandlerThread的looper對象傳遞給Handler對象,然後在handleMessage()方法中執行異步任務;
- 創建HandlerThread後必須調用HandlerThread.start()方法來啟動線程。
HandlerThread handlerThread = new HandlerThread("Handler Thread");
//HandlerThread handlerThread = new HandlerThread("Handler Thread",Process.THREAD_PRIORITY_DEFAULT);
HandlerThread默認有兩個構造函數,提供了線程名參數和線程優先級參數的設置。
handlerThread.start();
通過start()方法就可以啟動一個HandlerThread了,該線程會不斷地循環運行。
Handler workderHandler = new Handler(handlerThread.getLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
//運行在工作線程(子線程)中,用於實現自己的消息處理
return true;
}
});
通過將HandlerThread綁定的Looper對象傳遞給Handler作為參數,構建一個異步的Handler對象,為了能實現耗時任務的異步執行,我們重寫了Handler的Callback接口的handleMessage()方法,當然也可以不重寫該方法,而通過post()方法進行耗時任務操作。
Handler workderHandler = new Handler(handlerThread.getLooper());
workderHandler.post(new Runnable() {
@Override
public void run() {
//運行在工作線程(子線程)中,用於實現自己的消息處理
}
});
最後,我們就可以通過調用workerHandler以發送消息的形式發送耗時任務到工作線程HandlerThread中去執行,實際上就是在Handler.Callback裡的handleMessage()中執行。
這裡要注意,在創建Handler作為HandlerThread線程消息執行者的時候必須先調用start()方法,因為創建Handler所需要的Looper參數是從HandlerThread中獲得的,而Looper對象的賦值又是在HandlerThread的run()方法中創建。
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
/**
* Created by Administrator on 2016/9/18.
*/
public class HandlerThreadActivity extends Activity implements Handler.Callback {
private DBHandlerThread mDBHandlerThread;
private Handler mUIHandler; //與UI線程相關聯的Handler
Button mBtnQuery;
TextView mTextResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnQuery = (Button) findViewById(R.id.buttonQuery);
mTextResult = (TextView) findViewById(R.id.result);
mBtnQuery.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//將異步耗時任務發送到HandlerThread中
//Message msg = Message.obtain(null, DBHandlerThread.MSG_QUERY_FRIENDS);
//mDBHandlerThread.getWorkerHandler().sendMessage(msg);
mDBHandlerThread.queryFriends();
}
});
mUIHandler = new Handler(this);
initWorkerThread();
}
protected void initWorkerThread() {
mDBHandlerThread = new DBHandlerThread("Handler Thread");
mDBHandlerThread.setUIHandlerCallBack(mUIHandler);
mDBHandlerThread.start(); //start()後會執行Thread的run()方法
}
@Override
protected void onDestroy() {
super.onDestroy();
mDBHandlerThread.setUIHandlerCallBack(null);
mDBHandlerThread.quit();
mDBHandlerThread = null;
}
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case DBHandlerThread.MSG_QUERY_FRIENDS:
// update UI
break;
}
return false;
}
}
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
/**
* Created by Administrator on 2016/9/18.
*/
public class DBHandlerThread extends HandlerThread implements Handler.Callback {
public static final int MSG_QUERY_FRIENDS = 100;
private Handler mWorkerHandler; //與工作線程相關聯的Handler
private Handler mUIHandler; //與UI線程相關聯的Handler
public DBHandlerThread(String name) {
super(name);
}
public DBHandlerThread(String name, int priority) {
super(name, priority);
}
public void setUIHandlerCallBack(Handler handler) {
this.mUIHandler = handler;
}
public Handler getWorkerHandler() {
return mWorkerHandler;
}
@Override
protected void onLooperPrepared() {
mWorkerHandler = new Handler(getLooper(), this);
}
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_QUERY_FRIENDS:
//...查詢數據庫操作...
Message message = Message.obtain(null, MSG_QUERY_FRIENDS);
mUIHandler.sendMessage(message); //通知UI更新
break;
}
return true;
}
public void queryFriends() {
Message msg = Message.obtain(null, MSG_QUERY_FRIENDS);
mWorkerHandler.sendMessage(msg);
}
}
HandlerThread的源碼不多,先看下它的構造函數:
/**
* 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; //當前線程id
//當前線程持有的Looper對象
Looper mLooper;
public HandlerThread(String name) {
//調用父類默認的方法創建線程
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
//帶優先級參數的構造方法
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
...............
}
從代碼中得知,HandlerThread帶有兩個構造函數,可傳遞兩個參數,一個參數是name,指的是線程的名稱,另一個參數是priority,指的是線程優先級。線程的優先級的取值范圍為-20到19。優先級高的獲得的CPU資源更多,反之則越少。-20代表優先級最高,19最低。我們可以根據自己的需要去設置線程的優先級,也可以采用默認的優先級,HandlerThread的默認優先級是Process.THREAD_PRIORITY_DEFAULT,具體值為0。該優先級是再run()方法中設置的,我們看它的run()方法:
public class HandlerThread extends Thread {
/**
* 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(); //獲得當前線程的id
Looper.prepare(); //准備循環條件
//通過鎖機制來獲得當前線程的Looper對象
synchronized (this) {
mLooper = Looper.myLooper();
//喚醒等待線程
notifyAll();
}
//設置當前線程的優先級
Process.setThreadPriority(mPriority);
//在線程循環之前做一些准備工作(子類可實現也可不實現)
onLooperPrepared();
//啟動loop
Looper.loop();
mTid = -1;
}
}
run()方法主要是通過Looper.prepare()和Looper.loop()構造了一個循環線程。這裡要注意,在創建HandlerThread對象後必須調用其start()方法才能進行run()方法體的執行。
在Looper.prepare()執行後,Looper對象會被創建,然後通過同步鎖機制,將Looper對象賦值給HandlerThread的內部變量mLooper,並通過notifyAll()方法去喚醒等待線程。接著為線程賦予優先級,然後執行onLooperPrepared()方法,該方法是一個空實現,留給我們必要的時候去重寫的,主要用來做一些初始化工作。最後通過執行Looper.loop()在線程中啟動消息隊列。
我們看到在run()方法中進行了喚醒等待線程,為什麼要這麼做呢?答案就在getLooper()方法中:
/**
* 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(); //等待,直到另一個線程調用notify()或notifyAll()來喚醒它
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
該方法用來獲取當前子線程HandlerThread所關聯的Looper對象實例。首先判斷HandlerThread線程是否存活,如果沒有存活就直接返回null,否則繼續執行,進入同步塊並判斷Looper對象是否為空以及線程是否啟動,若都滿足,則調用wait()方法進入阻塞階段,直到Looper對象被成功創建並且通過notifyAll()方法喚醒該等待線程,最後才返回該Looper對象。
Looper對象的創建是在run()方法進行的,也即在子線程中執行的,而getLooper()方法是在UI線程中調用的,若不使用等待喚醒機制,我們就無法保證在UI線程中調用getLooper()方法時Looper對象已經被創建,會面臨一個同步的問題,所以HandlerThread就通過等待喚醒機制來解決該同步問題。
Chromium on Android: Android L平台上WebView的變化及其對浏覽器廠商的影響分析
摘要:Android L平台在圖形渲染方面有一項重要的改進,它引入了一個專門的線程用於執行渲染工作,UI線程負責生成的顯示列表(DisplayList),渲染線程負責重放
閱讀《Android 從入門到精通》(17)——進度條
進度條(ProgressBar)java.lang.Object;android.view.View;android.widget.ProgressBar;Progres
Android處理大圖片
項目中經常碰到需要處理大圖片的問題,因為android對應用分配資源的限制,如果不進行相應的處理,容易造成OOM。 Android處理大圖的方法: 對於大圖先獲取出圖片的
Android批量圖片加載經典系列——使用xutil框架緩存、異步加載網絡圖片
一、問題描述 為提高圖片加載的效率,需要對圖片的采用緩存和異步加載策略,編碼相對比較復雜,實際上有一些優秀的框架提供了解決方案,比如近期在git上比較活躍的xut