1.Handler簡介
2.Handler的用法
3.Android為啥要設計只能通過Handler機制去更新UI
4.Handler的原理
5.創(chuàng)建一個和線程相關的Handler
6.HandlerThread
7.Android中更新UI的幾種方式
8.子線程中真的不能更新UI嗎?
Handler
- handler是Android給我們提供的用來更新UI的一套機制,也是一套消息處理的機制,我們可以通過它發(fā)送消息和處理消息
- 為什么要使用handler母剥?
- Android在設計的時候,就封裝了一套消息創(chuàng)建这刷,傳遞和處理的機制,如果不遵守這樣的機制就沒有辦法跟新UI娩井,就會拋出異常信息CalledFromWrongThreadException
Handler的用法
post(Runable r):發(fā)送一個任務暇屋,任務是運行在主線程中的
-
postDelayed(Runnable r, int delayMillis):延遲發(fā)送一個任務,任務是運行在主線程中的
new Thread(){ public void run() { //在子線程中通過handler直接post一個任務洞辣,該任務是運行在主線程中的咐刨,所以可以更新UI Handler.post(new Runnable() { public void run() { mTextView.setText("fdfd"); } }); }; }.start();
sendMessage(Message msg):發(fā)送消息
handler.hasMessages(int what);檢查消息中是否存在what這個消息
handler.hasMessages(int what,Object object):檢查是否存在what和object
-
mHandler.sendMessageDelayed(Message msg, int delayMillis):延遲發(fā)送消息
new Thread(){ public void run() { Message msg = new Message(); /** * msg.what:用于區(qū)分消息,不同的消息執(zhí)行不同的操作 * msg.arg1,arg2 :用于攜帶簡單的整形數(shù)據(jù) * msg.obj:攜帶任何的對象 * msg.setData(Bundle bundle):通過bundle攜帶任何的數(shù)據(jù) * 在handleMessage中通過getData()去獲取Bundle */ msg.what = 1; //在子線程中發(fā)送消息 mHandler.sendMessage(msg); }; }.start();
可以使用handler實現(xiàn)循環(huán)展示圖片等類似的循環(huán)操作
private MyRunable runable = new MyRunable();
class MyRunable implements Runnable{
int index = 0;
@Override
public void run() {
index++;
index = index % 3;
mImageView.setImageResource(image[index]);
//每隔一秒執(zhí)行當前的任務扬霜,會循環(huán)調用
//所以會一直執(zhí)行下去定鸟,是在主線程中執(zhí)行的
mHandler.postDelayed(runable, 1000);
}
}-
攔截handler發(fā)送的消息
private Handler mHandler = new Handler(new Callback() { @Override public boolean handleMessage(Message msg) { Toast.makeText(getApplicationContext(), "1", 0).show(); //設置為true,就會攔截消息著瓶,就不會執(zhí)行下面的內容了 //設置為false時联予,就先執(zhí)行該方法中的內容再執(zhí)行下面的方法 return false; } }){ public void handleMessage(Message msg) { Toast.makeText(getApplicationContext(), "2", 0).show(); }; };
執(zhí)行結果會先顯示1 再顯示2
- 取消發(fā)送消息
mHandler.removeCallbacks(runable);//取消執(zhí)行之前發(fā)送的任務 - handler發(fā)送消息的另一種方法
new Thread(){
public void run() {
Message msg = Message.obtain(mHandler);
msg.arg1 = 10;
//使用Message去啟動消息,跟handler發(fā)送的結果一樣
msg.sendToTarget();
};
}.start();
Android為啥要設計只能通過Handler機制去更新UI?
- 最根本的目的是解決多線程并發(fā)的問題
- 假設在一個Activity鐘有多個線程去更新UI材原,而且都沒有加鎖機制沸久,那會產(chǎn)生什么結果呢?
- 界面混亂
- 如果更新UI使用加鎖機制會怎樣呢华糖?
- 性能下降
- 處于以上目的的考慮麦向,Android為我們設計一套更新UI的機制,我們只要遵守就行客叉,不用考慮多線程的問題,都是在主線程的消息隊列中去輪詢處理的
Handler的原理
- Looper(輪詢)
- 內部包含一個消息隊列也就是MessageQueue,所有的Handler發(fā)送的消息都會存儲到這個隊列
- Looper.looper方法是一個死循環(huán)兼搏,不斷的從MessageQueue中取消息卵慰,如果有消息就處理消息,沒有就阻塞
- Handler封裝了消息的發(fā)送(主要把消息發(fā)送給誰)
- 內部會跟Looper關聯(lián)佛呻,也就是Handler內部可以找到Looper裳朋, 找到了Looper也就是找到了MessageQueue,Handler發(fā)送消息就是向消息隊列MessageQueue中發(fā)送消息
- 總結:Handler負責發(fā)送消息吓著,Looper負責接收Handler發(fā)送的消息鲤嫡,并直接把消息回傳給Handler自己;MessageQueue就是一個消息隊列绑莺,存儲所有Handler發(fā)送的消息
創(chuàng)建一個和線程相關的Handler
- 如果不給Handler指定一個Looper就會出現(xiàn)異常
- "Can't create handler inside thread that has not called Looper.prepare():沒有指定Looper
class MyThread extends Thread{
public Handler handler;
@Override
public void run() {
Looper.prepare(); //創(chuàng)建一個Looper對象
handler = new Handler(){
//此方法運行在子線程中
@Override
public void handleMessage(Message msg) {
System.out.println("當前的線程-->" + Thread.currentThread());
}
};
Looper.loop(); //開始輪詢
}
}
MyThread thread = new MyThread();
thread.start(); //啟動線程
//發(fā)送消息就會執(zhí)行該handler的handleMessage方法(在子線程中)
thread.handler.sendEmptyMessage(1);
說明:在主線程中創(chuàng)建Handler時暖眼,主線程會自動創(chuàng)建Looper的,而自己在子線程中自定義handler機制時纺裁,需要手動創(chuàng)建Looper诫肠,并調用Looper的looper方法
HandlerThread
- 本身是一個子線程
- 可以實現(xiàn)異步操作
private HandlerThread thread;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//創(chuàng)建一個線程,名字為:"HandlerThread"
thread = new HandlerThread("HandlerThread");
thread.start();//啟動線程
//創(chuàng)建一個Handler欺缘,并制定此handlerde的Looper
//此處該Looper是HandlerThread線程中的栋豫,所以消息的處理實在HandlerThread線程中的(子線程)
handler = new Handler(thread.getLooper()){
//該方法運行在子線程中
@Override
public void handleMessage(Message msg) {
System.out.println("當前線程--->" + Thread.currentThread());
}
};
handler.sendEmptyMessage(1);//發(fā)送一個空的消息
}
因為handleMessage方法運行在子線程,所以可以處理耗時的操作谚殊,使用HandlerThread丧鸯,可以實現(xiàn)異步的操作,而不用考慮什么時候創(chuàng)建任務嫩絮,取消任務等
Demo:主線程向子線程發(fā)送消息
private HandlerThread thread;
private Handler threadHandler; //子線程中的handler
//主線程中的handler
private Handler handler = new Handler(){
public void handleMessage(Message msg) {
//在主線程中向子線程發(fā)送消息
threadHandler.sendEmptyMessageDelayed(1, 1000);
System.out.println("主線程向子線程發(fā)送消息");
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
thread = new HandlerThread("異步子線程");
thread.start();
threadHandler = new Handler(thread.getLooper()){
@Override
public void handleMessage(Message msg) {
//在子線程中向主線程發(fā)送消息
handler.sendEmptyMessageDelayed(1, 1000);
System.out.println("子線程向主線程發(fā)送消息");
}
};
}
//按鈕點擊響應處理
public void click(View view){
handler.sendEmptyMessage(1);
}
說明:按鈕點擊時丛肢,handler就會發(fā)送消息,就會執(zhí)行主線程中的handleMessage方法絮记,在該方法中threadHandler就會向子線程發(fā)送消息摔踱,在子線程中handler又會發(fā)送消息,因此一直循環(huán)下去怨愤,可以在添加一個按鈕派敷,調用handler的removeCallbacks方法取消發(fā)送消息,從而結束循環(huán)
Android中更新UI的幾種方式
不管是哪種方式內部都是通過handler發(fā)送消息來實現(xiàn)更新UI的
- handler post
new Thread(){
public void run() {
//在子線程中通過handler直接post一個任務撰洗,該任務是運行在主線程中的篮愉,所以可以更新UI
mHandler.post(new Runnable() {
public void run() {
mTextView.setText("fdfd");
}
});
};
}.start();
說明post里面封裝的還是sendMessage
- handler sendMessage
new Thread(){
public void run() {
mHandler.sendEmptyMessage(1);
};
}.start();
/* 在主線程中運行,由哪個Handler發(fā)送的消息就由那個Handler來處理 */
private Handler mHandler = new Handler(){
public void handleMessage(Message msg) {
mTextView.setText("Update");
};
}; - runOnUiThread
new Thread(){
public void run() {
//運行在主線程中
runOnUiThread(new Runnable() {
public void run() {
mTextView.setText("Update");
}
});
};
}.start();
說明:runOnUiThread內部先判斷當前線程是不是主線程差导,如果不是就通過handler發(fā)送一個消息
- view post
new Thread(){
public void run() {
mTextView.post(new Runable(){
mTextView.setText("Update");
});
};
}.start();
通過view自己post一個任務试躏,該任務也是運行在主線程中的,內部也是通過handler發(fā)送消息來實現(xiàn)的
子線程中真的不能更新UI嗎设褐?
- 更新UI時颠蕴,會判斷當前的線程是不是主線程泣刹,是通過viewRootImpl判斷的
- viewRootImpl是在Activity的onResume()方法中初始化的
- 所以只要在子線程更新UI時,只要viewRootImpl沒有初始化犀被,就不會進行判斷椅您,就可以在子線程中更新UI
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTv = (TextView) findViewById(R.id.text);
//這樣是可以在子線程中更新UI的,因為此時viewRootImpl還沒有初始化
new Thread(){
public void run() {
mTv.setText("子線程中跟新UI");
};
}.start();
}
////////////////////////////////////////////////////////////////////////////////////////
new Thread(){
public void run() {
try {
//這樣是不能跟新的寡键,會報異常的掀泳,因為此時已經(jīng)初始化了
//android.view.ViewRootImpl$CalledFromWrongThreadException
Thread.sleep(2000);
mTv.setText("子線程中跟新UI");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();