Handler源碼講解+手寫機(jī)制

摘自BAT面試寶典視頻

問題引入:點擊后更新TextView
重點:
1 惋鸥、不能在子線程更新UI
2、OOM:HAndler使用不當(dāng)可能引起內(nèi)存泄漏
3悍缠、Message的優(yōu)化:要用Handler卦绣。obtainMessage()而不是new,會消耗內(nèi)存飞蚓。
4滤港、在子線程創(chuàng)建Handler,要準(zhǔn)備Looper:Looper.prepare()趴拧。
5蜗搔、空指針異常:Handler消息處理完了單頁面銷毀了劲藐,就會拋出異常。

Handler整體架構(gòu) (4個關(guān)鍵類基本關(guān)系幕后類Thread)

handler能做什么樟凄?
1聘芜、處理延時任務(wù):推送將來的Message或Runnable到消息隊列;
2缝龄、線程間通信:在子線程把需要在另一個線程執(zhí)行的操作加入到消息隊列汰现;

1.png

源碼分析(線程如何跨越、生產(chǎn)者消費者設(shè)計模式叔壤、ThreadLocal原理)

從“handler.sendMessage()”發(fā)送消息出發(fā)
sendMessage(msg)
---->sendMessageDelay(msg,0)
---->sendMessageAtTime(msg,SysMillis()+delay)
---->enqueueMessage(queue,msg,uptimeMillis);
---->queue.enqueueMessage(msg,uptimeMillis);
enqueueMessage()往MessageQueue發(fā)送消息

其他流程圖

2.png

所有的send和post都是MessageQueue.enqueueMessage()瞎饲!

MessageQueue.java

3.png
4.png

用一個for循環(huán)不斷地.next找消息

是一個鏈表隊列,新消息來時比較時間后插入相應(yīng)位置

5.png

消息的處理

Handler mHandler = new Handler(){
  public void handlerMessage(Message msg){
    super.handlerMessage(msg);
  }
}

如何handlerMessage炼绘?從MessageQueue里找next()方法

6.png

又有一個for循環(huán)

7.png

messageQueue.next()返還嗅战、銷毀隊列里的消息
.next()是誰調(diào)用的?--->Looper.java

8.png
9.png

for循環(huán)一直在運行
Loop在被誰調(diào)用俺亮?---》ActivityThread.java

10.png

*如上圖主線程里不需要準(zhǔn)備Loop

ActivityThread讓Loop跑起來的篡腌。

Handler框架手寫

定義4個工具類

11.png

Handler

public class Handler{
 final Looper mLooper;
 final MessageQueue mQueue;

 public Handler(){
    mLooper = Looper.myLooper();//1
    mQueue = mLooper.mQueue;//1為什么不是new
  }
//發(fā)送消息
 public void sendMessage(Message msg){
  enqueueMessage(msg);
 }
 public void enqueueMessage(Message msg){
    msg.target = this;//人跟著箱子去了傳送帶
   mQueue.enqueueMessage(msg);
 }
//分發(fā)消息
  public void dispachMessage(Message msg){
    handlerMessage(msg);
}
//處理消息
  public void handlerMessage(Message 
 msg){
    
  }
}
public class Looper{
  public MessageQueue mQueue;
  public static ThreadLoca<Looper> sThreadLocal= new ThreadLoca<>();

  private Looper(){
    mQueue = new MessageQueue();
  }
 
  public static prepare(){
    //看下面ThreadLocal解釋
    if(sThreadLocal.get()!=null){
      throw new RuntimeException("Only one Looper may ...")
    }
    sThreadLocal.set(new Looper(.));
  }

  public static Looper myLooper(){
    return ThreadLocal.get();
 }
//啟動looper 讓MQ run
  public void loop(){
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    for(;;){
      Message msg = queue .next();
      if(msg!=null){
        msg.target.dispachMessage(msg);
      }
    }
  }

}
public class  MessageQueue{

BlockingQueue <Message> queue ;//實現(xiàn)倉庫的阻塞隊列
private static final int MAXCOUNT = 10;//倉庫大小
public MessageQueue(){
  queue= new ArrayBlockingQueue<>(MAXCOUNT );
}
//往隊列里添加消息
 public void enqueueMessage(Message msg){
    try{
      queue.put(msg)
    }catch(){
      
    }
 }
//往隊列里取消息
 public Message next(){
    Message msg = null;
    try{
      msg =queue.take();
    }catch(InterruptException e){
      
    }
   return msg;
 }
}
public class  Message{
  Handler target;
  Object obj;

  public Message(){
  }
  public String toString(){
    return obj.toString();
  }
}

測試代碼

public calss HandlerMain{
  public static void main(String [] args){
    Looper.prepare();
    Handler handler = new Handler(){
      public void handleMessage(Message msg){
        System.out.println("Thread ID "+Thread.currentThread().getName()+" received msg: "+msg.toSting())
      };
    };
  
  new Thread(new Runnable(){
     public void run(){
        while(true){
          Message msg = new Message();
          msg.obj = UUID.randomUUID().toString();
          System.out.println(Thread.currentThread().);
          handler.sendMessage(msg);
          try{
            Thread.sleep(500);
          }catch(InterruptedException e){
            e.printStackTrace();
          }
        }
      } 
   })
   Looper.loop();
  }
}

一個線程只有一個Looper弟蚀!
*ThreadLocal線程隔離工具類
類似HashMap<Key,Value>
Key---線程ID
Value---Looper對象

通過ThreadLocal確保Looper唯一悍及,通過Looper確保MessageQueue唯一

子線程和主線程的通信(MQ由子線程寫入在主線程讀雀钜浴)是借助內(nèi)存實現(xiàn)的

12.png
13.png

Message.obtion()運用了享原設(shè)計模式 ,復(fù)用了Message本讥。

子線程中真的不能更新UI嗎珊泳?
可以
1、

//在oncreate里
new Thread(newRunnable(){
  public void run (){
    button.setText("可以正常更新不報異常");
  }
}).run();

原理在ActivityManagerService.java里
在onCreate沒到onResume時拷沸,是不會檢測是在子線程還是在主線程的

ViewRootImpl的創(chuàng)建在onResume方法回調(diào)之后色查,而我們是在onCreate方法中創(chuàng)建了子線程并訪問UI,在那個時刻撞芍,ViewRootImpl是沒有創(chuàng)建的秧了,無法檢測當(dāng)前線程是否是UI線程,所以程序沒有崩潰一樣能跑起來勤庐,而之后修改了程序示惊,讓線程休眠了200毫秒后,程序就崩了愉镰。很明顯200毫秒后ViewRootImpl已經(jīng)創(chuàng)建了米罚,可以執(zhí)行checkThread方法檢查當(dāng)前線程。

2丈探、Surface可以在子線程中更新UI
SurfaceView與View的刷新方法都是一樣的录择,通過lockCanvas和unlockCanvasAndPost方法來進(jìn)行畫的,但SurfaceView能在UI線程中刷新,也能在其它線程中刷新隘竭,而View只能在UI線程中刷新塘秦,View的刷新有一個checkThread(在ViewRootImp.java中)的判斷,如果不是在UI線程中就會拋異常动看, 這是google人為這樣設(shè)計的尊剔,不讓其它線程刷新View,SurfaceView就不會進(jìn)行判斷菱皆,這樣它就可以在其它線程中進(jìn)行刷新须误。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市仇轻,隨后出現(xiàn)的幾起案子京痢,更是在濱河造成了極大的恐慌,老刑警劉巖篷店,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件祭椰,死亡現(xiàn)場離奇詭異,居然都是意外死亡疲陕,警方通過查閱死者的電腦和手機(jī)方淤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鸭轮,“玉大人臣淤,你說我怎么就攤上這事橄霉∏砸” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵姓蜂,是天一觀的道長按厘。 經(jīng)常有香客問我,道長钱慢,這世上最難降的妖魔是什么逮京? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮束莫,結(jié)果婚禮上懒棉,老公的妹妹穿的比我還像新娘。我一直安慰自己览绿,他們只是感情好策严,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著饿敲,像睡著了一般妻导。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天倔韭,我揣著相機(jī)與錄音术浪,去河邊找鬼。 笑死寿酌,一個胖子當(dāng)著我的面吹牛胰苏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播醇疼,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼碟联,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了僵腺?” 一聲冷哼從身側(cè)響起鲤孵,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎辰如,沒想到半個月后普监,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡琉兜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年凯正,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片豌蟋。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡廊散,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梧疲,到底是詐尸還是另有隱情允睹,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布幌氮,位于F島的核電站缭受,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏该互。R本人自食惡果不足惜米者,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宇智。 院中可真熱鬧蔓搞,春花似錦、人聲如沸随橘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽太防。三九已至妻顶,卻和暖如春酸员,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背讳嘱。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工幔嗦, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人沥潭。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓邀泉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親钝鸽。 傳聞我的和親對象是個殘疾皇子汇恤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容