天天看點

Handler的了解和使用

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

  • 如果不給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();

關注微信公衆号擷取更多相關資源

Handler的了解和使用