參考
Handler和他的小伙伴們(上)
Android:異步處理之Handler+Thread的應用(一)
Android:異步處理之Handler走贪、Looper抄淑、MessageQueue之間的恩怨(三)
android的消息處理機制(圖+源碼分析)——Looper,Handler,Message
Android中為什么主線程不會因為Looper.loop()里的死循環(huán)卡死
主線程是指進程所擁有的線程衩婚,默認情況下一個進程只有一個線程就是主線程剔应,主線程主要處理界面交互咒林,又叫UI線程熬拒;除了主線程之外都是子線程,又叫工作線程垫竞。
一澎粟、為什么不能直接更新UI
不要在UI主線程中進行耗時操作,你可能會疑問什么是UI主線程欢瞪,UI主線程主要運行的就是Activity活烙、Service等里面的生命周期方法,所以不要在生命周期方法如onCreate()中進行下載這些大事件遣鼓。對于耗時操作啸盏,我們應該新建一個子線程并交給他處理。但是還需要注意一點骑祟,不要在子線程中更新UI界面回懦。
為什么不允許子線程訪問UI呢?這是因為UI控件是線程不安全的次企,如果多線程并發(fā)訪問會導致UI控件處于不可預期狀態(tài)怯晕。那為什么不加鎖機制來解決呢?缺點有兩個:
- 鎖機制會讓UI訪問邏輯變復雜
- 鎖機制會阻塞某些線程的執(zhí)行缸棵,降低UI訪問效率
所以最簡單高效方式就是單線程處理UI操作舟茶。
二、Handler
現在我們需要進行耗時操作(例如下載文件)時不能在主線程執(zhí)行堵第,我們又需要在UI界面通知用戶我們活干完了不能在子線程中執(zhí)行稚晚。這似乎是一個棘手的熱山芋呀,幸好谷歌給我們提供了一個救我們于危難之中的Handler型诚,一個能讓主線程監(jiān)聽子線程發(fā)送來消息的東東客燕。
<pre>
private Button btn;
private TextView text;
private Handler handler = new Handler(){
private int process = 0;
@Override
public void handleMessage(Message msg) {
switch(msg.what){
case 0://更細下載進度
process += 1;
text.setText("下載" + process + "%");//在主線程中更新UI界面
break;
case 1://提示下載完成
text.setText("下載完成");//在主線程中更新UI界面
break;
default:
break;
}
}
};
//onCreate之類的生命周期的方法就是允許在UI主線程中
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.btn);
text = (TextView) findViewById(R.id.text);
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread(){
@Override
public void run() {
//為了不阻塞主線程,將下載任務通過子線程來執(zhí)行
for(int i = 0; i < 100; i++){
try {
Thread.sleep(200);//休眠0.2秒狰贯,模擬耗時操作
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage(0);//發(fā)送消息到handler也搓,通知下載進度
}
handler.sendEmptyMessage(1);//發(fā)送消失到handler,通知主線程下載完成
}
}.start();
}
});
}
</pre>
但是如果你覺得每次都要重寫handlerMessage()比較麻煩涵紊,我們完全可以用更加簡略的方法來解決我們的需求傍妒,就是用handler中的post方法。代碼如下
<pre>
new Thread(){
@Override
public void run() {
//在子線程中進行下載操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.post(new Runnable() {
@Override
public void run() {
text.setText("下載完成");
}
});//發(fā)送消失到handler摸柄,通知主線程下載完成
}
}.start();
</pre>
這樣處理的話我們就可以不用重寫handlerMessage()方法了颤练,適合子線程與主線程進行較為單一的交流。但在這里我們要強調的一點的是驱负,post里面的Runnable還是在UI主線程中運行的嗦玖,而不會另外開啟線程運行患雇,千萬不要在Runnable的run()里面進行耗時任務
如果你有時候連handler都不想搞,還可以這樣寫代碼滴宇挫。我們只需要把handler換成View組件進行post苛吱,更新任務自然會加載到UI主線程中進行處理。
<pre>
text.post(new Runnable() {
@Override
public void run() {
text.setText("下載完成");
}
});//發(fā)送消失到handler器瘪,通知主線程下載完成
</pre>
關于sendMessage和post詳細區(qū)別翠储,可參考承香墨影Android--多線程之Handler
三、Handler looper messengerQueue
這里給大家寫了一個向子線程發(fā)送消息并顯示輸出的例子橡疼,強調一下下哦援所,是向子線程喲。
<pre>
public class MainActivity extends ActionBarActivity {
private Handler handler;
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.sendmsg);
new HandlerThread().start();//啟動子線程
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
handler.sendEmptyMessage(0);//向子線程發(fā)送消息
}
});
}
class HandlerThread extends Thread{
@Override
public void run() {
//開始建立消息循環(huán)
Looper.prepare();//初始化Looper
handler = new Handler(){//默認綁定本線程的Looper
@Override
public void handleMessage(Message msg) {
switch(msg.what){
case 0:
Toast.makeText(MainActivity.this, "子線程收到消息", Toast.LENGTH_SHORT).show();
}
}
};
Looper.loop();//啟動消息循環(huán)
}
}
}
</pre>
說一下消息發(fā)送的過程:
1欣除、啟動一個子線程任斋,并在子線程初始化一個Looper。
<pre>
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
</pre>
在Looper()中耻涛,實例化了一個消息隊列(MessageQueue)废酷!并且如我們所愿的綁定到了mQueue這個局部變量上,在這里我們可以得出這么一個結論:調用Looper. prepare()的線程就建立起一個消息循環(huán)的對象抹缕。
2澈蟆、在HandlerThread中實例化Handler,Handler自動綁定上當前線程的Looper。
<pre>
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
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 = callback;
mAsynchronous = async;
}
</pre>
在代碼中我們通過handler = new Handler() 調用到了Handler(Callback callback, boolean async)這個方法卓研;我們發(fā)現mLooper = Looper.myLooper()把線程中的Looper綁定到了Handler上趴俘,通過mQueue = mLooper.mQueue獲取了線程的消息隊列(單鏈表,方便插入刪除)奏赘,我當然也可以換句話說:Handler已經綁定到了創(chuàng)建此Handler對象的線程的消息隊列上了寥闪,所以咱們可以開始干壞事了。磨淌。疲憋。。
3梁只、重寫Handler里面的消息處理方法缚柳。
<pre>
@Override
public void handleMessage(Message msg) {
switch(msg.what){
case 0:
Toast.makeText(MainActivity.this, "子線程收到消息", Toast.LENGTH_SHORT).show();
}
}
</pre>
4、執(zhí)行Looper.loop()啟動消息循環(huán)搪锣,子線程進入等待消息狀態(tài)秋忙。
<pre>
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;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
return;
}
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycle();
}
}
</pre>
在loop()的這個靜態(tài)方法中,我們可以注意到for (;;)這個方法构舟,這是死胡同死循環(huán)灰追,所以我們將其稱作為“消息循環(huán)”,說起來挺形象滴。在消息循環(huán)中會調用queue.next()來獲取消息隊列中排隊等待處理的消息弹澎,并將其賦值到msg這個變量上朴下;接下來就判斷如果msg != null 就開始分發(fā)消息裁奇,也就是執(zhí)行msg.target.dispatchMessage(msg)桐猬。在分發(fā)消息結束后麦撵,將會回收掉這個消息刽肠,體現在msg.recycle()這個函數上。
msg.target是一個handler對象免胃,表示需要處理這個消息的handler對象音五,所以我們回到Handler看看dispatchMessage()這個方法了:
<pre>
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);//post方法傳遞的Runnable參數
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
</pre>
不知道大家有沒有一眼發(fā)現handleMessage()這個方法,這可不是我們在第三步重寫Handler中的方法么羔沙。真相大白躺涝,當 msg.callback != null 并且 mCallback != null 時將會調用 handleMessage(msg) 來處理其他線程發(fā)送來的消息,我們通過覆蓋這個方法來實現我們具體的消息處理過程扼雏;這也就是Handler消息處理機制的全部內容坚嗜。
通讀全文,我們可以知道消息循環(huán)機制的核心就是Looper诗充,因為Looper持有了MessageQueue的對象苍蔬,并且可以被一個線程設為該線程的一個局部變量,我們可以這么認為這個線程通過Looper擁有了一個消息隊列蝴蜓。而Handler的用處就是封裝了消息發(fā)送和消息處理的方法碟绑,在線程通信中,線程可以通過Handler發(fā)送消息給創(chuàng)建Handler的線程茎匠,通過Looper將消息放入進入消息接收線程的消息隊列格仲,等待Looper取出消息并在最后交給Handler處理具體消息。
我們會發(fā)現在Activity中實例化一個Handler并不需要Looper.prepare()來初始化一個Looper和Looper.loop()來啟動消息循環(huán)诵冒,因為Activity在構造過程中已經對Looper進行了初始化并且建立了消息循環(huán)凯肋,參見ActivityThread.java中的代碼:
<pre>
public final class ActivityThread {
public static final void main(String[] args) {
......
Looper.prepareMainLooper();
......
ActivityThread thread = new ActivityThread();
thread.attach(false);
......
Looper.loop();
......
thread.detach();
......
}
}
</pre>
Android應用程序進程在啟動的時候,會在進程中加載ActivityThread類汽馋,并且執(zhí)行這個類的main函數否过,應用程序的消息循環(huán)過程就是在這個main函數里面實現的。
總結:一個線程只有一個Looper, 而一個Looper持有一個MessageQueue, 當調用Looper.prepare()時惭蟋,Looper就與當前線程關聯起來了(在Activity里沒有顯示調用Looper.prepare()是因為系統自動在主線程里幫我們調用了)苗桂,而Handler是與Looper的線程是綁定的,查看Handler類的源碼可以發(fā)現它幾個構造函數告组,其中有接收一個Looper參數的煤伟,也有不接收Looper參數的,從上面的代碼上看,我們沒有為Handler指定Looper便锨,那么Handler就默認更當前線程(即主線程)的Looper關聯起來了围辙,之所以啰嗦那么多就是因為這決定了Handler.handlerMessage(msg)方法體里的代碼到底在哪個線程里執(zhí)行,我們再梳理一下放案,Looper.prepare調用決定了Looper與哪個線程關聯姚建,間接決定了與這個Looper相關聯的Handler.handlerMessage(msg)方法體里的代碼執(zhí)行的線程。(太啰嗦了)
現在回到上面的代碼吱殉,我們的Handler是在主線程里的定義的掸冤,所以也默認跟主線程的Looper相關聯,即handlerMessage方法的代碼會在UI線程執(zhí)行友雳。