Handler的理解和使用

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ā)送的消息
s.png
創(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();
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市西轩,隨后出現(xiàn)的幾起案子员舵,更是在濱河造成了極大的恐慌,老刑警劉巖藕畔,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件马僻,死亡現(xiàn)場離奇詭異,居然都是意外死亡劫流,警方通過查閱死者的電腦和手機巫玻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來祠汇,“玉大人仍秤,你說我怎么就攤上這事】珊埽” “怎么了诗力?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長我抠。 經(jīng)常有香客問我苇本,道長,這世上最難降的妖魔是什么菜拓? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任瓣窄,我火速辦了婚禮,結果婚禮上纳鼎,老公的妹妹穿的比我還像新娘俺夕。我一直安慰自己,他們只是感情好贱鄙,可當我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布劝贸。 她就那樣靜靜地躺著,像睡著了一般逗宁。 火紅的嫁衣襯著肌膚如雪映九。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天瞎颗,我揣著相機與錄音件甥,去河邊找鬼捌议。 笑死,一個胖子當著我的面吹牛嚼蚀,可吹牛的內容都是我干的禁灼。 我是一名探鬼主播管挟,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼轿曙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了僻孝?” 一聲冷哼從身側響起导帝,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎穿铆,沒想到半個月后您单,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡荞雏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年虐秦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凤优。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡悦陋,死狀恐怖,靈堂內的尸體忽然破棺而出筑辨,到底是詐尸還是另有隱情俺驶,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布棍辕,位于F島的核電站暮现,受9級特大地震影響,放射性物質發(fā)生泄漏楚昭。R本人自食惡果不足惜栖袋,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抚太。 院中可真熱鬧塘幅,春花似錦、人聲如沸凭舶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帅霜。三九已至匆背,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間身冀,已是汗流浹背钝尸。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工括享, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人珍促。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓铃辖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親猪叙。 傳聞我的和親對象是個殘疾皇子娇斩,可洞房花燭夜當晚...
    茶點故事閱讀 44,974評論 2 355

推薦閱讀更多精彩內容