1.Handler簡介
2.Handler的用法
3.Android為啥要設計隻能通過Handler機制去更新UI
4.Handler的原理
5.建立一個和線程相關的Handler
6.HandlerThread
7.Android中更新UI的幾種方式
8.子線程中真的不能更新UI嗎?
Handler
- handler是Android給我們提供的用來更新UI的一套機制,也是一套消息處理的機制,我們可以通過它發送消息和處理消息
- 為什麼要使用handler?
- Android在設計的時候,就封裝了一套消息建立,傳遞和處理的機制,如果不遵守這樣的機制就沒有辦法跟新UI,就會抛出異常資訊CalledFromWrongThreadException
Handler的用法
- post(Runable r):發送一個任務,任務是運作在主線程中的
-
postDelayed(Runnable r, int delayMillis):延遲發送一個任務,任務是運作在主線程中的
new Thread(){
public void run() {
//在子線程中通過handler直接post一個任務,該任務是運作在主線程中的,是以可以更新UI
Handler.post(new Runnable() {
public void run() {
mTextView.setText(“fdfd”);
}
});
};
}.start();
- sendMessage(Message msg):發送消息
- handler.hasMessages(int what);檢查消息中是否存在what這個消息
- handler.hasMessages(int what,Object object):檢查是否存在what和object
-
mHandler.sendMessageDelayed(Message msg, int delayMillis):延遲發送消息
new Thread(){
public void run() {
Message msg = new Message();
msg.what = 1;
//在子線程中發送消息
mHandler.sendMessage(msg);
};
}.start();
-
可以使用handler實作循環展示圖檔等類似的循環操作
private MyRunable runable = new MyRunable();
class MyRunable implements Runnable{
int index = 0;
@Override
public void run() {
index++;
index = index % 3;
mImageView.setImageResource(image[index]);
//每隔一秒執行目前的任務,會循環調用
//是以會一直執行下去,是在主線程中執行的
mHandler.postDelayed(runable, 1000);
}
}
-
攔截handler發送的消息
private Handler mHandler = new Handler(new Callback() {
@Override
public boolean handleMessage(Message msg) {
Toast.makeText(getApplicationContext(), “1”, 0).show();
//設定為true,就會攔截消息,就不會執行下面的内容了
//設定為false時,就先執行該方法中的内容再執行下面的方法
return false;
}
}){
public void handleMessage(Message msg) {
Toast.makeText(getApplicationContext(), “2”, 0).show();
};
};
執行結果會先顯示1 再顯示2
-
取消發送消息
mHandler.removeCallbacks(runable);//取消執行之前發送的任務
-
handler發送消息的另一種方法
new Thread(){
public void run() {
Message msg = Message.obtain(mHandler);
msg.arg1 = 10;
//使用Message去啟動消息,跟handler發送的結果一樣
msg.sendToTarget();
};
}.start();
Android為啥要設計隻能通過Handler機制去更新UI?
- 最根本的目的是解決多線程并發的問題
- 假設在一個Activity鐘有多個線程去更新UI,而且都沒有加鎖機制,那會産生什麼結果呢?
- 界面混亂
- 如果更新UI使用加鎖機制會怎樣呢?
- 性能下降
- 處于以上目的的考慮,Android為我們設計一套更新UI的機制,我們隻要遵守就行,不用考慮多線程的問題,都是在主線程的消息隊列中去輪詢處理的
Handler的原理
- Looper(輪詢)
- 内部包含一個消息隊列也就是MessageQueue,所有的Handler發送的消息都會存儲到這個隊列
- Looper.looper方法是一個死循環,不斷的從MessageQueue中取消息,如果有消息就處理消息,沒有就阻塞
- Handler封裝了消息的發送(主要把消息發送給誰)
- 内部會跟Looper關聯,也就是Handler内部可以找到Looper, 找到了Looper也就是找到了MessageQueue,Handler發送消息就是向消息隊列MessageQueue中發送消息
- 總結:Handler負責發送消息,Looper負責接收Handler發送的消息,并直接把消息回傳給Handler自己;MessageQueue就是一個消息隊列,存儲所有Handler發送的消息
建立一個和線程相關的Handler
- 如果不給Handler指定一個Looper就會出現異常
-
“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(); //建立一個Looper對象
handler = new Handler(){
//此方法運作在子線程中
@Override
public void handleMessage(Message msg) {
System.out.println(“目前的線程–>” + Thread.currentThread());
}
};
Looper.loop(); //開始輪詢
}
}
MyThread thread = new MyThread();
thread.start(); //啟動線程
//發送消息就會執行該handler的handleMessage方法(在子線程中)
thread.handler.sendEmptyMessage(1);
說明:在主線程中建立Handler時,主線程會自動建立Looper的,而自己在子線程中自定義handler機制時,需要手動建立Looper,并調用Looper的looper方法
HandlerThread
- 本身是一個子線程
-
可以實作異步操作
private HandlerThread thread;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//建立一個線程,名字為:”HandlerThread”
thread = new HandlerThread(“HandlerThread”);
thread.start();//啟動線程
//建立一個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);//發送一個空的消息
}
因為handleMessage方法運作在子線程,是以可以處理耗時的操作,使用HandlerThread,可以實作異步的操作,而不用考慮什麼時候建立任務,取消任務等
Demo:主線程向子線程發送消息
private HandlerThread thread;
private Handler threadHandler; //子線程中的handler
//主線程中的handler
private Handler handler = new Handler(){
public void handleMessage(Message msg) {
//在主線程中向子線程發送消息
threadHandler.sendEmptyMessageDelayed(1, 1000);
System.out.println("主線程向子線程發送消息");
};
};
@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) {
//在子線程中向主線程發送消息
handler.sendEmptyMessageDelayed(1, 1000);
System.out.println("子線程向主線程發送消息");
}
};
}
//按鈕點選響應處理
public void click(View view){
handler.sendEmptyMessage(1);
}
說明:按鈕點選時,handler就會發送消息,就會執行主線程中的handleMessage方法,在該方法中threadHandler就會向子線程發送消息,在子線程中handler又會發送消息,是以一直循環下去,可以在添加一個按鈕,調用handler的removeCallbacks方法取消發送消息,進而結束循環
Android中更新UI的幾種方式
不管是哪種方式内部都是通過handler發送消息來實作更新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();
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發送一個消息
* view post
new Thread(){
public void run() {
mTextView.post(new Runable(){
mTextView.setText(“Update”);
});
};
}.start();
通過view自己post一個任務,該任務也是運作在主線程中的,内部也是通過handler發送消息來實作的
子線程中真的不能更新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 {
//這樣是不能跟新的,會報異常的,因為此時已經初始化了
//android.view.ViewRootImpl$CalledFromWrongThreadException
Thread.sleep(2000);
mTv.setText(“子線程中跟新UI”);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();