轉載于:
http://blog.csdn.net/guolin_blog/article/details/9991569
http://www.cnblogs.com/JczmDeveloper/p/4403129.html
1.前言
-
Android UI是線程不安全的,如果在子線程中嘗試進行UI操作,程式就有可能會崩潰。
在一個Activity中,當有多個線程更新UI時,并且沒有枷鎖機制,就會出現更新界面混亂,但是如果有枷鎖機制的話,又會導緻性能下降,此時使用Handler機制,通過消息隊列,保證了消息處理的先後順序,進而很好的更新了界面。
- 解決的方案應該也是早已爛熟于心,即建立一個Message對象,然後借助Handler發送出去,之後在Handler的handleMessage()方法中獲得剛才發送的Message對象,然後在這裡進行UI操作就不會再出現崩潰了,解決多線程并發的問題。
在下面介紹handler機制前,首先得了解以下幾個概念:
-
Message
消息,了解為線程間通訊的資料單元。例如背景線程在處理資料完畢後需要更新UI,則可發送一條包含更新資訊的Message給UI線程。
-
Message Queue
消息隊列,用來存放通過Handler釋出的消息,按照先進先出(FIFO)執行。
-
Handler
Handler是Message的主要處理者,負責将Message添加到消息隊列以及對消息隊列中的Message進行處理。
-
Looper
循環器,扮演Message Queue和Handler之間橋梁的角色,循環取出Message Queue裡面的Message,并傳遞給相應的Handler進行處理。
-
線程
UI thread 通常就是main thread,而Android啟動程式時會替它建立一個Message Queue。
每一個線程裡可含有一個Looper對象以及一個MessageQueue資料結構。在你的應用程式裡,可以定義Handler的子類别來接收Looper所送出的消息。
2.代碼分析
(1)Handler的作用
-
傳遞message
用于接受子線程發送的資料,并用此資料配合主線程更新UI。
sendEmptyMessage(int what);
sendMessage(Message msg);
sendMessageAtTime(Message msg,long uptimeMillis);//uptimeMillis 自系統開機到目前時間的毫秒數再加上延遲時間
sendMessageDelayed(Message msg,long delayMillis);
sendMessageAtFrontOfQueue(Message msg);
- 傳遞Runnable對象
post(Runnable run);
postAtTime(Runnable run,long uptimeMillis);
postDelayed(Runnable run,long delayMillis);
-
傳遞Callback對象
Callback用于截獲handler發送的消息,如果傳回true,就截獲成功,不再往下傳遞。
如果傳回false,則繼續執行下面的handleMessage方法。
public Handler m = new Handler(new Handler.Callback(){
public boolean handleMessage(Message msg) {
Toast.makeText(context, "handle intercept", Toast.LENGTH_LONG).show();
return true;
};
}){
public void handleMessage(Message msg) {
Toast.makeText(context, "handleMessage", Toast.LENGTH_LONG).show();
};
};
(2)Handler的原理
- handler封裝了message的發送
-
Looper内部包含一個Message Queue消息隊列,所有handler發送的消息都走向這個隊列 。
Looper.loop()方法是一個for死循環,不斷從Message Queue取消息,有消息就處理消息,沒有消息就阻塞
- Message Queue消息隊列,可以添加消息,處理消息
Handler負責發送message,Looper負責接受Handler發送的message,并直接把message回傳給handler自己,Message Queue就是一個存儲消息的容器。
下圖展示了具體流程:
一個線程中隻有一個Looper執行個體,一個MessageQueue執行個體,可以有多個Handler執行個體。
如下圖:
(3)子線程中建立Handler
1.自定義子線程并建立Handler
class MyThread extends Thread{
private Handler m_Handler = null;
@Override
public void run() {
Looper.prepare();
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
if(==msg.what){
super.handleMessage(msg);
try {
Thread.sleep();
Toast.makeText(getApplicationContext(), "currentThread:"+Thread.currentThread().getName(), Toast.LENGTH_LONG).show();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
m_Handler.sendEmptyMessage();
Looper.loop();
}
}
//啟動線程處理耗時操作
new MyThread().start();
一般UI主線程中不宜做耗時操作,此時就可以通過子線程中的消息機制來處理耗時操作。
主線程中建立Handler時,不需要調用Looper.prepare()和Looper.loop(),是因為系統已經幫我們自動調用們調用了。
子線程中建立的Handler時,需要先調用Looper.prepare(),否則會導緻 crash:Can’t create handler inside thread that has not called Looper.prepare(),而且之後還需要調用Looper.loop()來循環從消息隊列處理消息。之是以會這樣,我們需要看下Handler源碼:
public Handler() {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == ) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = null;
}
源碼中調用了Looper.myLooper()方法擷取了一個Looper對象,如果Looper對象為空,則會抛出一個運作時異常,提示的錯誤正是 Can’t create handler inside thread that has not called Looper.prepare()!那什麼時候Looper對象才可能為空呢?這就要看看Looper.myLooper()中的代碼了,如下所示:
public static final Looper myLooper() {
return (Looper)sThreadLocal.get();
}
從sThreadLocal對象中取出Looper。如果sThreadLocal中有Looper存在就傳回Looper,如果沒有Looper存在自然就傳回空了。是以你可以想象得到是在哪裡給sThreadLocal設定Looper了吧,當然是Looper.prepare()方法!我們來看下它的源碼:
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
首先判斷sThreadLocal中是否已經存在Looper了,如果還沒有則建立一個新的Looper設定進去。這樣也就完全解釋了為什麼我們要先調用Looper.prepare()方法,才能建立Handler對象。同時也可以看出每個線程中最多隻會有一個Looper對象。
接下來就是發送消息,除了sendMessageAtFrontOfQueue()方法之外,其它的發送消息方法最終都會輾轉調用到sendMessageAtTime()方法中,這個方法的源碼如下所示:
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
}
else {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
}
sendMessageAtTime()方法接收兩個參數,其中msg參數就是我們發送的Message對象,而uptimeMillis參數則表示發送消息的時間,它的值等于自系統開機到目前時間的毫秒數再加上延遲時間,如果你調用的不是sendMessageDelayed()方法,延遲時間就為0,然後将這兩個參數都傳遞到MessageQueue的enqueueMessage()方法中。這個MessageQueue又是什麼東西呢?其實從名字上就可以看出了,它是一個消息隊列,用于将所有收到的消息以隊列的形式進行排列,并提供入隊和出隊的方法。這個類是在Looper的構造函數中建立的,是以一個Looper也就對應了一個MessageQueue。
enqueueMessage()方法毫無疑問就是入隊的方法了,我們來看下這個方法的源碼:
final boolean enqueueMessage(Message msg, long when) {
if (msg.when != ) {
throw new AndroidRuntimeException(msg + " This message is already in use.");
}
if (msg.target == null && !mQuitAllowed) {
throw new RuntimeException("Main thread not allowed to quit");
}
synchronized (this) {
if (mQuiting) {
RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
} else if (msg.target == null) {
mQuiting = true;
}
msg.when = when;
Message p = mMessages;
if (p == null || when == || when < p.when) {
msg.next = p;
mMessages = msg;
this.notify();
} else {
Message prev = null;
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
msg.next = prev.next;
prev.next = msg;
this.notify();
}
}
return true;
}
MessageQueue并沒有使用一個集合把所有的消息都儲存起來,它隻使用了一個mMessages對象表示目前待處理的消息。然後觀察上面的代碼的16~31行我們就可以看出,所謂的入隊其實就是将所有的消息按時間來進行排序,這個時間當然就是我們剛才介紹的uptimeMillis參數。具體的操作方法就根據時間的順序調用msg.next,進而為每一個消息指定它的下一個消息是什麼。當然如果你是通過sendMessageAtFrontOfQueue()方法來發送消息的,它也會調用enqueueMessage()來讓消息入隊,隻不過時間為0,這時會把mMessages指派為新入隊的這條消息,然後将這條消息的next指定為剛才的mMessages,這樣也就完成了添加消息到隊列頭部的操作。
現在入隊操作我們就已經看明白了,那出隊操作是在哪裡進行的呢?這個就需要看一看Looper.loop()方法的源碼了,如下所示:
public static final void loop() {
Looper me = myLooper();
MessageQueue queue = me.mQueue;
while (true) {
Message msg = queue.next(); // might block
if (msg != null) {
if (msg.target == null) {
return;
}
if (me.mLogging!= null) me.mLogging.println(
">>>>> Dispatching to " + msg.target + " "
+ msg.callback + ": " + msg.what
);
msg.target.dispatchMessage(msg);
if (me.mLogging!= null) me.mLogging.println(
"<<<<< Finished to " + msg.target + " "
+ msg.callback);
msg.recycle();
}
}
}
可以看到,這個方法從第4行開始,進入了一個死循環,然後不斷地調用的MessageQueue的next()方法,我想你已經猜到了,這個next()方法就是消息隊列的出隊方法。不過由于這個方法的代碼稍微有點長,我就不貼出來了,它的簡單邏輯就是如果目前MessageQueue中存在mMessages(即待處理消息),就将這個消息出隊,然後讓下一條消息成為mMessages,否則就進入一個阻塞狀态,一直等到有新的消息入隊。繼續看loop()方法的第14行,每當有一個消息出隊,就将它傳遞到msg.target的dispatchMessage()方法中,那這裡msg.target又是什麼呢?其實就是Handler啦,你觀察一下上面sendMessageAtTime()方法的第6行就可以看出來了。接下來當然就要看一看Handler中dispatchMessage()方法的源碼了,如下所示:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
在第5行進行判斷,如果mCallback不為空,則調用mCallback的handleMessage()方法,否則直接調用Handler的handleMessage()方法,并将消息對象作為參數傳遞過去。這樣我相信大家就都明白了為什麼handleMessage()方法中可以擷取到之前發送的消息了吧!
2.使用HandlerThread子線程
HandlerThread繼承自Thread,它與普通Thread的差別在于内部有個Looper成員變量,這個Looper對象就是對消息隊列以及消息隊列邏輯處理的封裝。當我們需要一個工作線程,且不是用過就廢棄的話,就可以使用它。
private Handler mHandler;
private HandlerThread mHandlerThread;
private void setRunnableToWorker(Runnable r){
if(null == mHandlerThread){
mHandlerThread = new HandlerThread("WorkThread");
mHandlerThread.setPriority(Thread.MIN_PRIORITY);
mHandlerThread.start();
}
if(null == mHandler){
mHandler = new Handler(mHandlerThread.getLooper());
mHandler.post(r);
}
}
(4)主線程與子線程通過Handler資訊互動
//方式一:
private Handler mThreadHandler;//建立工作線程handler
private HandlerThread mHandlerThread;//工作線程
private void setRunnableToWorker(Runnable r){
if(null == mHandlerThread){
mHandlerThread = new HandlerThread("WorkThread");
mHandlerThread.setPriority(Thread.MIN_PRIORITY);
mHandlerThread.start();
}
if(null == mThreadHandler){
mThreadHandler = new Handler(mHandlerThread.getLooper());
mThreadHandler.post(r);
}
}
//建立主線程handler
private Handler m_Handler = new Handler(){
public void handleMessage(Message msg) {
if(msg.what == ){
Toast.makeText(getApplicationContext(), "update UI", Toast.LENGTH_LONG).show();
tv.setText("hi");
}
};
};
//onResume()中調用setRunnableToWorker()處理耗時操作,并通過handler更新UI
setRunnableToWorker(new Runnable() {
@Override
public void run() {
try {
Thread.sleep();
m_Handler.sendEmptyMessage();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Toast.makeText(getApplicationContext(), "update UI", Toast.LENGTH_LONG).show();
}
});
//方式二:
private WorkHandler mWorkHandler;//建立工作線程的Handler
private class WorkHandler extends Handler{
public WorkHandler(Looper l){
super(l);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what == ){
try {
Thread.sleep();
m_Handler.sendEmptyMessage();//耗時操作處理完畢,發送消息給主線程更新UI
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//建立主線程handler
private Handler m_Handler = new Handler(){
public void handleMessage(Message msg) {
if(msg.what == ){
Toast.makeText(getApplicationContext(), "update UI", Toast.LENGTH_LONG).show();
tv.setText("hi");
}
};
};
//建立工作線程
private HandlerThread mWorkThread;
//onResume()中
mWorkThread = new HandlerThread("workThread");
mWorkThread.start();
mWorkHandler = new WorkHandler(mWorkThread.getLooper());
mWorkHandler.sendEmptyMessage();//發送消息給工作線程,讓其處理耗時操作
(5)Android中更新UI的方式
1.Handler的post()方法
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), );
}
//調用了sendMessageDelayed()方法去發送一條消息,并且還使用了getPostMessage()方法将Runnable對象轉換成了一條消息
private final Message getPostMessage(Runnable r) {
Message m = Message.obtain();//可以避免重複建立Message對象
m.callback = r;
return m;
}
//在這個方法中将消息的callback字段的值指定為傳入的Runnable對象。咦?這個callback字段看起來有些眼熟啊,喔!在Handler的dispatchMessage()方法中原來有做一個檢查,如果Message的callback等于null才會去調用handleMessage()方法,否則就調用handleCallback()方法。
private final void handleCallback(Message message) {
message.callback.run();
}
//直接調用了一開始傳入的Runnable對象的run()方法。是以在子線程中通過Handler的post()方法進行UI操作就可以這麼寫:
public class MainActivity extends Activity {
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
handler.post(new Runnable() {
@Override
public void run() {
// 在這裡進行UI操作
}
});
}
}).start();
}
}
2.Activity的runOnUiThread()方法
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
//如果目前的線程不等于UI線程(主線程),就去調用Handler的post()方法,否則就直接調用Runnable對象的run()方法。
3.View的post()方法
public boolean post(Runnable action) {
Handler handler;
if (mAttachInfo != null) {
handler = mAttachInfo.mHandler;
} else {
ViewRoot.getRunQueue().post(action);
return true;
}
return handler.post(action);
}
//最終調用的還是handler的post方法
4.Handler的sendMessage方法
見上述分析