Handler -- Ranger

Handler原理分析

在android開發(fā)中耍休,經(jīng)常會在子線程中進行一些操作顽分,當操作完畢后會通過handler發(fā)送一些數(shù)據(jù)給主線程,通知主線程做相應的操作含友。 探索其背后的原理:子線程 handler 主線程 其實構成了線程模型中的經(jīng)典問題 生產(chǎn)者-消費者模型。 生產(chǎn)者-消費者模型:生產(chǎn)者和消費者在同一時間段內共用同一個存儲空間校辩,生產(chǎn)者往存儲空間中添加數(shù)據(jù)窘问,消費者從存儲空間中取走數(shù)據(jù)。

image

好處: - 保證數(shù)據(jù)生產(chǎn)消費的順序(通過MessageQueue,先進先出) - 不管是生產(chǎn)者(子線程)還是消費者(主線程)都只依賴緩沖區(qū)(handler)宜咒,生產(chǎn)者消費者之間不會相互持有惠赫,使他們之間沒有任何耦合

Handler機制的相關類

Hanlder:發(fā)送和接收消息 Looper:用于輪詢消息隊列,一個線程只能有一個Looper Message: 消息實體MessageQueue: 消息隊列用于存儲消息和管理消息

創(chuàng)建Looper

創(chuàng)建Looper的方法是調用Looper.prepare() 方法

在ActivityThread中的main方法中為我們prepare了

public static void main(String[] args) {
  Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
  //其他代碼省略...
  Looper.prepareMainLooper(); //初始化Looper以及MessageQueue
  ActivityThread thread = new ActivityThread();
  thread.attach(false);

  if (sMainThreadHandler == null) {
    sMainThreadHandler = thread.getHandler();
  }

  if (false) {
    Looper.myLooper().setMessageLogging(new
    LogPrinter(Log.DEBUG, "ActivityThread"));
  }

  // End of event ActivityThreadMain.
  Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
  Looper.loop(); //開始輪循操作
  throw new RuntimeException("Main thread loop unexpectedly exited");
}

Looper.prepareMainLooper();

public static void prepareMainLooper() {
  prepare(false);//消息隊列不可以quit
  synchronized (Looper.class) {
    if (sMainLooper != null) {
      throw new IllegalStateException("The main Looper has already been prepared.");
    }
    sMainLooper = myLooper();
  }
}

prepare有兩個重載的方法故黑,主要看 prepare(boolean quitAllowed) quitAllowed的作用是在創(chuàng)建MessageQueue時標識消息隊列是否可以銷毀儿咱, 主線程不可被銷毀 下面有介紹

public static void prepare() {
   prepare(true);//消息隊列可以quit
}
private static void prepare(boolean quitAllowed) {
  if (sThreadLocal.get() != null) {//不為空表示當前線程已經(jīng)創(chuàng)建了Looper
    throw new RuntimeException("Only one Looper may be created per thread");
    //每個線程只能創(chuàng)建一個Looper
  }
  sThreadLocal.set(new Looper(quitAllowed));//創(chuàng)建Looper并設置給sThreadLocal,這樣get的時候就不會為null了
}

創(chuàng)建MessageQueue以及Looper與當前線程的綁定

private Looper(boolean quitAllowed) {
  mQueue = new MessageQueue(quitAllowed);//創(chuàng)建了MessageQueue
  mThread = Thread.currentThread(); //當前線程的綁定
}

MessageQueue的構造方法

MessageQueue(boolean quitAllowed) {
  //mQuitAllowed決定隊列是否可以銷毀 主線程的隊列不可以被銷毀需要傳入false, 在MessageQueue的quit()方法就不貼源碼了
  mQuitAllowed = quitAllowed;
  mPtr = nativeInit();
}

Looper.loop()

同時是在main方法中 Looper.prepareMainLooper() 后Looper.loop(); 開始輪詢

public static void loop() {
  final Looper me = myLooper();//里面調用了sThreadLocal.get()獲得剛才創(chuàng)建的Looper對象
  if (me == null) {
    throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
  }//如果Looper為空則會拋出異常
  final MessageQueue queue = me.mQueue;
  // Make sure the identity of this thread is that of the local process,
  // and keep track of what that identity token actually is.
  Binder.clearCallingIdentity();
  final long ident = Binder.clearCallingIdentity();

  for (;;) {
  //這是一個死循環(huán)场晶,從消息隊列不斷的取消息
  Message msg = queue.next(); // might block
  if (msg == null) {
  //由于剛創(chuàng)建MessageQueue就開始輪詢混埠,隊列里是沒有消息的,等到Handler sendMessageenqueueMessage后
  //隊列里才有消息
  // No message indicates that the message queue is quitting.
  return;
  }

  // This must be in a local variable, in case a UI event sets the logger
  Printer logging = me.mLogging;
  if (logging != null) {
    logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);
  }

  msg.target.dispatchMessage(msg);//msg.target就是綁定的Handler,詳見后面Message的部分峰搪,Handler開始
  //后面代碼省略.....
  
  msg.recycleUnchecked();
  } 
}

創(chuàng)建Handler

最常見的創(chuàng)建handler

Handler handler=new Handler(){
  @Override
  public void handleMessage(Message msg) {
    super.handleMessage(msg);
  }
};

在內部調用 this(null, false);

public Handler(Callback callback, boolean async) {
  //前面省略
  mLooper = Looper.myLooper();//獲取Looper岔冀,**注意不是創(chuàng)建Looper**!
  if (mLooper == null) {
    throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
  }
  mQueue = mLooper.mQueue;//創(chuàng)建消息隊列MessageQueue
  mCallback = callback; //初始化了回調接口
  mAsynchronous = async;
}

Looper.myLooper()概耻;

   //這是Handler中定義的ThreadLocal ThreadLocal主要解多線程并發(fā)的問題
   // sThreadLocal.get() will return null unless you've called prepare().
  static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
  public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
  }

sThreadLocal.get() will return null unless you’ve called prepare(). 這句話告訴我們get可能返回null 除非先調用prepare()方法創(chuàng)建Looper使套。在前面已經(jīng)介紹了

創(chuàng)建Message

可以直接new Message 但是有更好的方式 Message.obtain。因為可以檢查是否有可以復用的Message,用過復用避免過多的創(chuàng)建鞠柄、銷毀Message對象達到優(yōu)化內存和性能的目地

public static Message obtain(Handler h) {
    Message m = obtain();//調用重載的obtain方法
    m.target = h;//并綁定的創(chuàng)建Message對象的handler

    return m;
}

public static Message obtain() {
    synchronized (sPoolSync) {//sPoolSync是一個Object對象侦高,用來同步保證線程安全
        if (sPool != null) {//sPool是就是handler dispatchMessage 后 通過recycleUnchecked 回收用以復用的Message
        Message m = sPool;
        sPool = m.next;
        m.next = null;
        m.flags = 0; // clear in-use flag
        sPoolSize--;
        return m;
        }
    }
    return new Message();
}

Message和Handler的綁定

創(chuàng)建Message的時候可以通過 Message.obtain(Handler h) 這個構造方法綁定。當然可以在 在Handler 中的enqueueMessage()也綁定了厌杜,所有發(fā)送Message的方法都會調用此方法入隊奉呛,所以在創(chuàng)建Message的時候是可以不綁定的

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this; //綁定
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

Handler發(fā)送消息

Handler發(fā)送消息的重載方法很多,但是主要只有2種夯尽。 sendMessage(Message) sendMessage方法通過一系列重載方法的調用瞧壮,sendMessage調用sendMessageDelayed,繼續(xù)調用sendMessageAtTime匙握,繼續(xù)調用enqueueMessage咆槽,繼續(xù)調用messageQueue的enqueueMessage方法,將消息保存在了消息隊列中圈纺,而最終由Looper取出秦忿,交給Handler的dispatchMessage進行處理
我們可以看到在dispatchMessage方法中麦射,message中callback是一個Runnable對象,如果callback不為空灯谣,則直接調用callback的run方法潜秋,否則判斷mCallback是否為空,mCallback在Handler構造方法中初始化胎许,在主線程通直接通過無參的構造方法new出來的為null,所以會直接執(zhí)行后面的handleMessage()方法峻呛。

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {//callback在message的構造方法中初始化或者使用handler.post(Runnable)時候才不為空
    handleCallback(msg);
    } else {
        if (mCallback != null) {//mCallback是一個Callback對象,通過無參的構造方法創(chuàng)建出來的handler呐萨,該屬性為null杀饵,此段不執(zhí)行
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);//最終執(zhí)行handleMessage方法
    }
 }

Handler處理消息

在handleMessage(Message)方法中,我們可以拿到message對象谬擦,根據(jù)不同的需求進行處理,整個Handler機制的流程就結束了朽缎。

小結

handler.sendMessage 發(fā)送消息到消息隊列MessageQueue惨远,然后looper調用自己的loop()函數(shù)帶動MessageQueue從而輪詢messageQueue里面的每個Message,當Message達到了可以執(zhí)行的時間的時候開始執(zhí)行话肖,執(zhí)行后就會調用message綁定的Handler來處理消息北秽。大致的過程如下圖所示


image.png

handler機制就是一個傳送帶的運轉機制。
1)MessageQueue就像履帶最筒。
2)Thread就像背后的動力贺氓,就是我們通信都是基于線程而來的。
3)傳送帶的滾動需要一個開關給電機通電床蜘,那么就相當于我們的loop函數(shù)辙培,而這個loop里面的for循環(huán)就會帶著不斷的滾動,去輪詢messageQueue
4)Message就是 我們的貨物了邢锯。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末扬蕊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子丹擎,更是在濱河造成了極大的恐慌尾抑,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒂培,死亡現(xiàn)場離奇詭異再愈,居然都是意外死亡,警方通過查閱死者的電腦和手機护戳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門翎冲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人灸异,你說我怎么就攤上這事府适「岱桑” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵檐春,是天一觀的道長逻淌。 經(jīng)常有香客問我,道長疟暖,這世上最難降的妖魔是什么卡儒? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮俐巴,結果婚禮上骨望,老公的妹妹穿的比我還像新娘。我一直安慰自己欣舵,他們只是感情好擎鸠,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著缘圈,像睡著了一般劣光。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上糟把,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天绢涡,我揣著相機與錄音,去河邊找鬼遣疯。 笑死雄可,一個胖子當著我的面吹牛,可吹牛的內容都是我干的缠犀。 我是一名探鬼主播数苫,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼夭坪!你這毒婦竟也來了文判?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤室梅,失蹤者是張志新(化名)和其女友劉穎戏仓,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亡鼠,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡赏殃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了间涵。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仁热。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖勾哩,靈堂內的尸體忽然破棺而出抗蠢,到底是詐尸還是另有隱情举哟,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布迅矛,位于F島的核電站妨猩,受9級特大地震影響,放射性物質發(fā)生泄漏秽褒。R本人自食惡果不足惜壶硅,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望销斟。 院中可真熱鬧庐椒,春花似錦、人聲如沸蚂踊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悴势。三九已至窗宇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間特纤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工侥加, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捧存,地道東北人。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓担败,卻偏偏與公主長得像昔穴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子提前,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內容