參考資料
- 官方介紹文檔
- Android 異步消息處理機(jī)制 讓你深入理解 Looper、Handler、Message三者關(guān)系
- Android異步消息處理機(jī)制完全解析,帶你從源碼的角度徹底理解
- 慕課網(wǎng)課程-Android面試??虷andler詳解
如果在非UI線程中更新UI會(huì)出現(xiàn)問(wèn)題嗎?
實(shí)踐:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.id_tv)
TextView idTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000*5);
idTv.setText("Javen205測(cè)試非UI線程更新UI會(huì)出現(xiàn)什么異常呢?");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
我們運(yùn)行項(xiàng)目就會(huì)以下異常

為了更直觀的看到報(bào)錯(cuò)原因,我們找到源碼ViewRootImpl的checkThread方法,看它做了些什么。

但是Android為什么要這樣搞呢?
我們仔細(xì)看這句話,只有創(chuàng)建了View的線程才能對(duì)這個(gè)View進(jìn)行操作。而我們一般View都是為了顯式在UI上的。Android正是為了防止我們?cè)诜荱I線程去操作這些UI上的控件,才加了限制的。因?yàn)閁I體驗(yàn)對(duì)用戶來(lái)說(shuō)是最直觀的,如果誰(shuí)都有權(quán)限去操作一下,那UI要么很亂,要么控制很復(fù)雜。
竟然Android是不允許我們?cè)诜荱I線程中去執(zhí)行更新UI,那我們要怎么解決這個(gè)問(wèn)題呢?那我們就要使用Android 提供的Hander機(jī)制去更新UI了
一、什么是Handler
Handler是Android提供的用來(lái)更新UI的一套機(jī)制,也是一套消息處理機(jī)制,我們可以通過(guò)它發(fā)送消息,也可以通過(guò)它處理消息。
二、為什么要使用Handler
Android在設(shè)計(jì)的時(shí)候,就封裝了一套消息創(chuàng)建、傳遞、處理機(jī)制,如果不遵循這樣的機(jī)制就沒(méi)有辦法更新UI信息,就會(huì)拋出異常。
三、Handler怎么用呢?
- 在非UI線程借助Handler.post(Runnable)更新UI
首先在Activity中實(shí)例化一個(gè)Hander
Handler handler = new Handler();
然后在子線程中調(diào)用Handler.post(Runnable)更新UI
詳細(xì)的代碼如下:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.id_tv)
TextView idTv;
Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000*5);
// idTv.setText("Javen205測(cè)試非UI線程更新UI會(huì)出現(xiàn)什么異常呢?");
handler.post(new Runnable() {
@Override
public void run() {
idTv.setText("Javen205測(cè)試非UI線程更新UI會(huì)出現(xiàn)什么異常呢?");
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
- 在非UI線程借助Handler.postDelayed(Runnable, DelayTime)定時(shí)執(zhí)行相關(guān)動(dòng)作
栗子:實(shí)現(xiàn)三張圖片自動(dòng)切換

private int images[]= {R.drawable.image4,R.drawable.image6,R.drawable.image7};
private int index=0;
Handler handler = new Handler();
Thread myThread = new Thread(){
@Override
public void run() {
index++;
index = index%3;
System.out.println(index);
idImg.setImageResource(images[index]);
handler.postDelayed(myThread,1000);
}
};
handler.postDelayed(myThread,1000);
要是我們想停止圖片的切換,那要如何操作呢?
- Handler移除一個(gè)消息
handler.removeCallbacks(myThread);
- 自定義Handler和Message
自定義Hander(customHander)
Handler customHander = new Handler() {
@Override
public void handleMessage(Message msg) {
idTv.setText("msg.arg1>"+msg.arg1+ "\nmsg.arg2>" +msg.arg2 +"\nmsg.obj>"+((Dog)msg.obj).toString());
}
};
實(shí)體類
package com.javen205.entity;
import java.io.Serializable;
public class Dog implements Serializable{
private String name;
private int age;
public Dog() {
}
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
使用只定義Hander(customHander)發(fā)送消息
Dog dog=new Dog("薩摩耶",1);
Message message = new Message();
message.arg1 = 1;
message.arg2 = 2;
message.obj = dog;
customHander.sendMessage(message);
復(fù)用Message
Dog dog=new Dog("薩摩耶",1);
// Message message = new Message();
Message message= customHander.obtainMessage();
message.arg1 = 1;
message.arg2 = 2;
message.obj = dog;
customHander.sendMessage(message);
原理:obtainMessage()方法做了哪些操作呢?
/**
* Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
* creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
* If you don't want that facility, just call Message.obtain() instead.
*/
public final Message obtainMessage()
{
return Message.obtain(this);//this 就是Hander本身
}
我們看看Message.obtain(Handler h) 又做了哪些操作呢?
/**
* Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.
* @param h Handler to assign to the returned Message object's <em>target</em> member.
* @return A Message object from the global pool.
*/
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;
return m;
}
target就是Hander自己,指消息要發(fā)送給誰(shuí)
再來(lái)看看obtain()方法
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
這個(gè)方法就是跟我們平常new Message 對(duì)象一樣 ,只不過(guò)是在之前添加了一判斷,判斷系統(tǒng)中是否存在空的Message,如果存在就直接返回否則就創(chuàng)建一個(gè)Message對(duì)象。
其實(shí)發(fā)送消息也可以這樣玩,不使用直接使用Hander而是使用Message的sendToTarget()方法,代碼如下。
Dog dog=new Dog("薩摩耶",1);
// Message message = new Message();
Message message= customHander.obtainMessage();
message.arg1 = 1;
message.arg2 = 2;
message.obj = dog;
// customHander.sendMessage(message);
message.sendToTarget();
我們來(lái)看看Message.sendToTarget()方法源碼
/**
* Sends this Message to the Handler specified by {@link #getTarget}.
* Throws a null pointer exception if this field has not been set.
*/
public void sendToTarget() {
target.sendMessage(this);
}
上面提到過(guò)target就是Hander自己,其實(shí)就是調(diào)用Hander自己的一個(gè)sendMessage,跟我們普通發(fā)送Message沒(méi)有什么區(qū)別只是里面封裝了一個(gè)target,本質(zhì)還是調(diào)用了Hander.sendMessage(...)
- Handler 消息攔截使其接收者接收不到消息
自定義Handler(interceptHander)攔截消息
Handler interceptHander = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
System.out.println("is intercept Handler>"+msg.what);
// 設(shè)置true攔截消息
return true;
}
}){
@Override
public void handleMessage(Message msg) {
System.out.println("is intercept Handler");
}
};
interceptHander發(fā)送一個(gè)消息Message
interceptHander.sendEmptyMessage(1);
四、Handler幾種發(fā)送消息方式之間區(qū)別
可以參考:Handler發(fā)送sendMessage和postRunnable的區(qū)別
五、Handler的原理是什么?

Handler封裝了消息的發(fā)送:內(nèi)部會(huì)跟Looper關(guān)聯(lián)
Looper(消息封裝的載體):內(nèi)部包含一個(gè)消息隊(duì)列(MessageQueue),所有Handler發(fā)送的消息都會(huì)走向這個(gè)消息隊(duì)列;
Looper.Looper方法是一個(gè)死循環(huán),不斷的從MessageQueue取消息,如果有消息就處理消息,沒(méi)有消息就阻塞。MessageQueue(消息隊(duì)列):可以添加消息,并處理消息
總結(jié):Handler負(fù)責(zé)發(fā)送消息,Looper負(fù)責(zé)接收Handler發(fā)送的消息,并直接把消息回傳給Handler自己(handleMessage),MessageQueue就是一個(gè)存儲(chǔ)消息的容器。
六、自定義一個(gè)與線程相關(guān)的Handler
1、線程中創(chuàng)建一個(gè)Looper 可以使用 Looper.prepare();方法
2、實(shí)例化一個(gè)Handler
3、調(diào)用Looper.loop();方法循環(huán)處理消息
public class HandlerActivity extends AppCompatActivity {
private MyThread myThread;
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
System.out.println("UI :"+Thread.currentThread());
}
};
class MyThread extends Thread{
public Handler handler;
@Override
public void run() {
Looper.prepare();
handler= new Handler(){
@Override
public void handleMessage(Message msg) {
System.out.println("currentThread:"+Thread.currentThread());
}
};
Looper.loop();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
myThread = new MyThread();
myThread.start();
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
myThread.handler.sendEmptyMessage(1);
handler.sendEmptyMessage(1);
}
}
輸出結(jié)果:
System.out: currentThread:Thread[Thread-151,5,main]
System.out: UI :Thread[main,5,main]
七、非UI線程真的不能更新UI嗎?
我們運(yùn)行下面的代碼測(cè)試:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
new Thread() {
@Override
public void run() {
idTv.setText("Javen205測(cè)試非UI線程更新UI會(huì)出現(xiàn)什么異常呢?");
}
}.start();
}
最開(kāi)始的案例中我們?nèi)サ鬞hread.sleep(...)會(huì)正常運(yùn)行嗎?答案:可正常運(yùn)行。是不是感覺(jué)有點(diǎn)奇怪呢?那為什么直接在Activity的onCreate中添加子線程可以直接更新UI呢?
詳細(xì)解答: 為什么我們可以在非UI線程中更新UI
八、Handler異步消息處理(HandlerThread)
Android異步消息處理機(jī)制完全解析,帶你從源碼的角度徹底理解
一個(gè)簡(jiǎn)單的例子
public class HandlerThreadActivity extends AppCompatActivity {
private Handler handler;
private HandlerThread thread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_thread);
thread = new HandlerThread("Handler Thread");
thread.start();
handler = new Handler(thread.getLooper()){
@Override
public void handleMessage(Message msg) {
// 處理耗時(shí)操作
System.out.println("current thread>"+Thread.currentThread());
}
};
handler.sendEmptyMessage(1);
}
}
測(cè)試輸出結(jié)果
I/System.out: current thread>Thread[Handler Thread,5,main]
九、如何在主線程給子線程發(fā)送消息
public class HandlerThreadActivity extends AppCompatActivity {
@BindView(R.id.id_btn1)
Button idBtn1;
@BindView(R.id.id_btn2)
Button idBtn2;
private Handler threadhandler;
private HandlerThread thread;
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
System.out.println("UI thread>" + Thread.currentThread());
// 給主線程發(fā)送消息
Message message = new Message();
message.what =1;
threadhandler.sendMessageDelayed(message, 1000);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_thread);
ButterKnife.bind(this);
thread = new HandlerThread("Handler Thread");
thread.start();
threadhandler = new Handler(thread.getLooper()) {
@Override
public void handleMessage(Message msg) {
// 處理耗時(shí)操作
System.out.println("current thread>" + Thread.currentThread());
// 給主線程發(fā)送消息
Message message = new Message();
message.what =1;
handler.sendMessageDelayed(message, 1000);
}
};
// threadhandler.sendEmptyMessage(1);
}
@OnClick({R.id.id_btn1, R.id.id_btn2})
public void onClick(View view) {
switch (view.getId()) {
case R.id.id_btn1:
handler.sendEmptyMessage(1);
break;
case R.id.id_btn2:
handler.removeMessages(1);
threadhandler.removeMessages(1);
break;
}
}
}
十、Android中更新UI的幾種方式
- handler sendMessage
- runOnUIThread
- handler post
- view post
項(xiàng)目源碼地址:https://github.com/Javen205/Hander
推薦閱讀
AndroidStudio多渠道打包
Android依賴管理與私服搭建
Android Studio 上傳aar(Library)到JCenter
Android版-支付寶APP支付
Android版-微信APP支付
支付寶Wap支付你了解多少?
一張二維碼集成微信、支付寶支付
安利時(shí)間:
JPay是對(duì)微信App支付、支付寶App支付的二次封裝,對(duì)外提供一個(gè)相對(duì)簡(jiǎn)單的接口以及支付結(jié)果的回調(diào)
極速開(kāi)發(fā)微信公眾號(hào)是對(duì)微信公眾平臺(tái)接口的二次封裝。包括開(kāi)發(fā)者模式、事件回調(diào)監(jiān)聽(tīng)、微信模板消息、微信客服消息、自定義菜單、微信支付、素材管理等
如遇到問(wèn)題歡迎留言交流