Android 消息機(jī)制之 Message 與消息對象池的深入源碼分析 [ 四 ]

Android 消息機(jī)制深入源碼分析 [ 一 ]
Android 消息機(jī)制之 ThreadLocal 深入源碼分析 [ 二 ]
Android 消息機(jī)制之 Looper 深入源碼分析 [ 三 ]
Android 消息機(jī)制之 Message 與消息對象池的深入源碼分析 [ 四 ]
Android 消息機(jī)制之 MessageQueue 深入源碼分析 [ 五 ]
Android 消息機(jī)制之初識Handler [ 六 ]
Android 消息機(jī)制之 Handler 發(fā)送消息的深入源碼分析 [ 七 ]
Android 消息機(jī)制之 MessageQueue.next() 消息取出的深入源碼分析 [ 八 ]
Android 消息機(jī)制之消息的其他處理深入源碼分析 [ 九 ]
Android 消息機(jī)制總結(jié) [ 十 ]

接上一章的 Looper, 本章開始分析消息機(jī)制中的 Message 與消息對象池.

什么是 Message

  • 一個(gè)可以發(fā)送給 Handler 的描述和任意數(shù)據(jù)對象的消息對象, 這個(gè)對象包含兩個(gè)額外的 int 字段和一個(gè)額外的對象字段. 這樣就可以使用在很多情況下不用做分配工作.
  • 盡管 Message 的構(gòu)造函數(shù)是公開的, 但是獲取 Message 的對象最好的方式是調(diào)用 Message.obtain() 或者 Handler.obtainMessage(), 因?yàn)檫@樣是直接從一個(gè)可回收的消息對象池中獲取 Message 對象.

成員變量 what

  • 官方: 它是用戶定義 Message 的標(biāo)識符, 用以分辨消息的內(nèi)容, Handler 擁有自己消息代碼的命名空間, 因此你不用擔(dān)心與其他的 Handler 沖突.

成員變量 arg1 與 arg2

  • arg1 與 arg2 都是 Message 的可選變量, 可以用來存放兩個(gè)整數(shù)值, 不用訪問 obj 對象就能讀取的變量. 如果只是幾個(gè)整形的數(shù)值, 相對于使用 setData() 方法, 使用 arg1/arg2 是較低成本的替代方案.

成員變量 obj

  • 官方: 將一個(gè)獨(dú)立的對象發(fā)給接收者. 當(dāng)使用 Messenger 去發(fā)送消息, 并且這個(gè)對象包含 Parcelable 類的時(shí)候, 它必須是非空的, 對于其他的數(shù)據(jù)傳輸, 建議使用 setData() 方法.

剩余成員變量

//回復(fù)跨進(jìn)程的 Messenger
public Messenger replyTo;

//Messenger 發(fā)送的 UID
public int sendingUid = -1;

//正在使用的標(biāo)志值,表示當(dāng)前 Messgae 正處于使用狀態(tài),
//當(dāng) Message 處于消息隊(duì)列中,處于消息池中或者 Handler 正在處理 Messgae 的時(shí)候,它就處于使用狀態(tài)
/*package*/ static final int FLAG_IN_USE = 1 << 0;

//異步標(biāo)志值 表示當(dāng)前 Message 是異步的
/*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;

//消息標(biāo)志值 在調(diào)用 copyFrom()方法時(shí),這個(gè)常量就會被設(shè)置,值其實(shí)和 FLAG_IN_USE 一樣.
/*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;

//消息標(biāo)志, 上面 3 個(gè)常量 FLAG 用在這里.
/*package*/ int flags;

//用于存儲發(fā)送消息的時(shí)間點(diǎn), 以毫秒為單位
/*package*/ long when;

//用于存儲比較復(fù)雜的數(shù)據(jù)
/*package*/ Bundle data;

//用于存儲發(fā)送當(dāng)前 Message 的 Hadnler 對象, 說明 Handler 其實(shí)和 Message 是相互持有引用的.
/*package*/ Handler target;

//用于存儲將會執(zhí)行的 Runnable 對象,
//除了 HandlerMessgae(Message msg) 方法,還可以使用 Runnable 執(zhí)行操作.要注意的是這種方法并不會創(chuàng)建新的線程
/*package*/ Runnable callback;

//指向下一個(gè) Messgae, 
/*package*/ Message next;

//這個(gè)靜態(tài)是為了給同步塊提供一個(gè)鎖
private static final Object sPoolSync = new Object();

//這個(gè)靜態(tài)的 Message 是整個(gè)線程池鏈表的頭部, 通過它才能逐個(gè)取出對象池的 Message
private static Message sPool;

//記錄對象池中 Message 的數(shù)量(鏈表的長度)
private static int sPoolSize = 0;

//設(shè)置了對象池中 Message 的最大數(shù)量, 也就是鏈表的最大長度
private static final int MAX_POOL_SIZE = 50;

//該系統(tǒng)是否支持回收的標(biāo)志位
private static boolean gCheckRecycle = true;

1. Message.obtain

在 Message.java 中, 通過 Message.obtain() 方式來獲取 Message 對象的, 一共有 8 個(gè)方法.下面將會針對這些逐個(gè)分析一下.

Message.obtain

1.1 public static Message obtain( )

代碼位于 Message.java 126 行

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

有一個(gè)變量為 sPool, 那么 sPool 到底有什么用呢? 這就涉及到了 Message 的設(shè)計(jì)原理了. 這里就引出了 Message 中的消息對象池了.

消息對象池

private static Message sPool;

乍一看, 好像沒什么, 就是一個(gè) Message 對象而已, sPool 對象默認(rèn)是 null.
這時(shí)候回頭來看上面的無參 obtain 方法, 內(nèi)部直接 new Message() 返回了. 但是官方又不推薦直接 new Message(), 所以推斷 sPool 在大部分情況下, 是不會為 null 的. 那么就找一下, 看在什么地方會對 sPool 進(jìn)行賦值.
經(jīng)過搜索后發(fā)現(xiàn), 整個(gè) Message 就有兩次地方對 sPool 進(jìn)行賦值.

  1. 上面的無參 obtain 方法中.
  2. void recycleUnchecked() 方法中.

首先第一點(diǎn)已經(jīng)排除了. 那么就直接進(jìn)入到第二點(diǎn)方法內(nèi)查看.

Message.recycleUnchecked()
為了更好的理解 我把 recycleUnchecked() obtain() 放在一起., 并省略一些不重要的代碼

void recycleUnchecked() {
                ...
        if (sPoolSize < MAX_POOL_SIZE) {
                // 第一步
                next = sPool;
                 // 第二步
                sPool = this;
                 // 第三步
                sPoolSize++;
                 ...
         }
    }

public static Message obtain() {
    synchronized (sPoolSync) {
        //第一步
        if (sPool != null) {
            // 第二步
            Message m = sPool;
            // 第三步
            sPool = m.next;
            // 第四步
            m.next = null;
            // 第五步
            m.flags = 0; 
            // 第六步
            sPoolSize--;
            return m;
        }
    }
}

recycleUnchecked()
注意: 變量 nextsPool 都是 Message.

  • 第一步: next = sPool, 首先因?yàn)橄ο蟪匾婚_始就為 null, 所以此時(shí)的 next 應(yīng)該也是為 null.
  • 第二步: sPool = this, 將當(dāng)前 Message 對象作為消息池中第一個(gè)可被復(fù)用的對象.
  • 第三步: sPoolSize++, sPoolSize 默認(rèn)為 0, 這個(gè)時(shí)候就變成了 1, 將消息對象池內(nèi)的數(shù)量+1, 這個(gè)數(shù)量是全局共享的.

假設(shè)現(xiàn)在又調(diào)用了一遍 recycleUnchecked 這個(gè)方法, 之前第一遍調(diào)用 recycleUnchecked 中那個(gè)第一個(gè)可被復(fù)用的對象為 Message1, 依舊執(zhí)行上面三步.

  • 第一步: next = sPool, 因?yàn)橄ο蟪刂杏幸粋€(gè) Message1, 所以此時(shí) sPoolMessage1, 同時(shí)賦值給 next.
  • 第二步: sPool = this, 將當(dāng)前 Message 對象作為消息池中第一個(gè)可被復(fù)用的對象. 假設(shè)當(dāng)前這個(gè)為 Message2.
  • 第三步: sPoolSize++, sPoolSize 此時(shí)為 1, ++ 后, 變?yōu)?2.

那么現(xiàn)在, sPool 消息對象池?cái)?shù)量為 2, sPool 對應(yīng) Message2, sPool 內(nèi)持有的另一個(gè) Message 對象 next 對應(yīng) Message1.
依次類推, 直到 sPoolSize = MAX_POOL_SIZE (MAX_POOL_SIZE = 50) .

接著看 obtain(), 假設(shè)消息對象池中已經(jīng)有兩個(gè)對象了. 就是 Message1, Message2.

  • 第一步: 判斷 sPool 是否為 null, 為 null 就直接創(chuàng)建一個(gè) Message 并返回.
  • 第二步: Message m = sPool, 將消息對象池的頭部賦值給 m, 剛才 sPool 對應(yīng)的是 Message2.
  • 第三步: sPool = m.next, 將消息對象池的下一個(gè)可復(fù)用的 Message 取出并賦值給消息對象池的頭部, 那么此時(shí), sPool 對應(yīng) Message1, m 對應(yīng) Message2. Message1.next 是為 null的.
  • 第四步: m.next = null, 因?yàn)橹耙呀?jīng)把 m.next 也就是 Message1 賦值給 sPool 了. 所以就把 m 做為單獨(dú)的一個(gè) Message 來對待.
  • 第五步 m.flags = 0 , 設(shè)置 m 的標(biāo)記位, 標(biāo)記正在被使用.
  • 第六步. sPoolSize-- , 因?yàn)橐呀?jīng)取出了一個(gè),賦值給 m了, 這時(shí)候消息對象池的容量減一.

那么現(xiàn)在, m 就是單獨(dú)的一個(gè) Message 對象. sPool 消息對象池?cái)?shù)量為 1, 對應(yīng)的是 Message1, 并且 sPool.next = null , 因?yàn)閺南ο蟪刂腥〕隽?Message2 給了 m.
那么剩下的是什么操作呢, Message2 取出來了, 就要開始分發(fā)了, 還記得昨天分析的 Looper.loop() 方法嗎, 分發(fā)完 Message2 后最后一步就是調(diào)用了 Message2.recycleUnchecked() 方法, 又將 Message2 放入了消息對象池了, 這樣就完成了一次 Message 的復(fù)用.

(一開始消息對象池就是 null , 當(dāng)我們第一次調(diào)用 Message.obtain() 的時(shí)候, 其實(shí)就是直接 new 了一個(gè) Message 的. 然后在昨天學(xué)習(xí)的 Looper.loop() 方法的最后, 分發(fā)完消息后, 調(diào)用了 msg.recycleUnchecked() 將我們 newMessage 放入了消息對象池.)

  • 可以將sPool看成一個(gè)指針, 通過next來將對象組成一個(gè)鏈表. 因?yàn)槊看沃恍枰獜某刈又心贸鲆粋€(gè)對象, 所以不需要關(guān)心池子里具體有多少個(gè)對象, 而是拿出當(dāng)前這個(gè)sPool所指向的這個(gè)對象就可以了.sPool從思路上理解就是通過左右移動來完成復(fù)用和回收.
    image.png
  • Message m = sPool, 然后 sPool = m.next 因此第一個(gè)m.next就等于第二個(gè) message, 從上圖上看相當(dāng)于指針向后移動了一位, 隨后將第一個(gè) message.next() 的值設(shè)置為 null.如下圖
    image.png
  • 現(xiàn)在這個(gè)鏈表看上去就斷了, 如果in-use這個(gè)message使用完畢了, 怎么回到鏈表中? 這就是 recycleUnchecked()回收了.
    這時(shí)候再看下recycleUnchecked()中的代碼,next = sPool, 將當(dāng)前sPool所指向的message對象賦值給in-usenext. 然后sPool=this, 將sPool指向第一個(gè)message對象.從而達(dá)到回收 . 如下圖
    image.png

這樣鏈表就恢復(fù)了,而且不管是復(fù)用還是回收都是保證線程同步的. 所以始終會形成一條鏈?zhǔn)浇Y(jié)構(gòu).


?

1.2 public static Message obtain(Message orig)

public static Message obtain(Message orig) {
      Message m = obtain();
      m.what = orig.what;
      m.arg1 = orig.arg1;
      m.arg2 = orig.arg2;
      m.obj = orig.obj;
      m.replyTo = orig.replyTo;
      m.sendingUid = orig.sendingUid;
      if (orig.data != null) {
          m.data = new Bundle(orig.data);
      }
      m.target = orig.target;
      m.callback = orig.callback;
      return m;
  }
  • 解析

obtain 差不多, 但是是將 Message 的所有內(nèi)容賦值一份到新的消息中.
代碼中可以看到首先調(diào)用的是無參 obtain, 從消息對象池中獲取一個(gè) Message 對象 m, 然后把 origin 中所有的屬性都賦值給 m .并返回 m.


?

1.2 public static Message obtain(Handler h)

public static Message obtain(Handler h) {
    Message m = obtain();
    m.target = h;
    return m;
}
  • 解析

和無參 obtain 一樣, 但是成員變量中 target 的值可以用以指定的值(入?yún)?來替換.


剩下幾個(gè) obtain 方法都基本類似, 都是先調(diào)用了obtain 無參函數(shù), 在重置一些值. 這里就不再一一說明.

主要就是對無參 obtain 方法的理解, recycleUnchecked() 方法的理解以及消息對象池 sPool 的理解. 這三個(gè)方面, 還是要捋清楚的. 下一章一起學(xué)習(xí)分析 MessageQueue.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末深员,一起剝皮案震驚了整個(gè)濱河市萌朱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌减响,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡竹椒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門米辐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人书释,你說我怎么就攤上這事翘贮。” “怎么了爆惧?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵狸页,是天一觀的道長。 經(jīng)常有香客問我扯再,道長芍耘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任熄阻,我火速辦了婚禮斋竞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘秃殉。我一直安慰自己坝初,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布钾军。 她就那樣靜靜地躺著鳄袍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪吏恭。 梳的紋絲不亂的頭發(fā)上拗小,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天,我揣著相機(jī)與錄音樱哼,去河邊找鬼哀九。 笑死,一個(gè)胖子當(dāng)著我的面吹牛搅幅,可吹牛的內(nèi)容都是我干的勾栗。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼盏筐,長吁一口氣:“原來是場噩夢啊……” “哼围俘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤界牡,失蹤者是張志新(化名)和其女友劉穎簿寂,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宿亡,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡常遂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了挽荠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片克胳。...
    茶點(diǎn)故事閱讀 38,809評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖圈匆,靈堂內(nèi)的尸體忽然破棺而出漠另,到底是詐尸還是另有隱情,我是刑警寧澤跃赚,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布笆搓,位于F島的核電站,受9級特大地震影響纬傲,放射性物質(zhì)發(fā)生泄漏满败。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一叹括、第九天 我趴在偏房一處隱蔽的房頂上張望算墨。 院中可真熱鬧,春花似錦汁雷、人聲如沸米同。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽面粮。三九已至,卻和暖如春继低,著一層夾襖步出監(jiān)牢的瞬間熬苍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工袁翁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留柴底,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓粱胜,卻偏偏與公主長得像柄驻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子焙压,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評論 2 351