轉(zhuǎn)載請注明文章出處LooperJing洒扎!
1脓恕、UI不能在子線程中更新是個偽命題
我們常說UI需要在主線程中進(jìn)行更新磺送,子線程就不能更新UI嗎?不是谋减,我們并不是說不能在子線程中更新UI牡彻,而是說UI必須要在它的創(chuàng)建線程中進(jìn)行更新,比如下面一段代碼在子線程更新UI就不會報錯出爹。
new Thread(new Runnable() {
@Override
public void run() {
TextView textView=new TextView(MainActivity.this);
textView.setText("Hello");
}
}).start();
OK庄吼,那我偏偏不在創(chuàng)建線程中進(jìn)行更新,如下严就!view在主線程中創(chuàng)建总寻,在子線程中add一個Button。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final LinearLayout view = (LinearLayout) LayoutInflater.from(this).inflate(R.layout.activity_main, null, false);
setContentView(view);
new Thread(new Runnable() {
@Override
public void run() {
Button btn=new Button(MainActivity.this);
btn.setText("Hello");
view.addView(btn);
}
}).start();
}
運行后梢为,Button被順利的加到了根布局中渐行。尼瑪,“UI必須要在它的創(chuàng)建線程中進(jìn)行更新”這種說法也不對爸K钣 !粟害!不是不對蕴忆,而是欠妥,我修改一下代碼悲幅!
new Thread(new Runnable() {
@Override
public void run() {
Button btn=new Button(MainActivity.this);
btn.setText("Hello");
SystemClock.sleep(2000);
view.addView(btn);
}
}).start();
就是在addView之前套鹅,睡眠了一會,結(jié)果還是拋出了 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.這個錯誤汰具!通常這種錯誤卓鹿,就要用Handler向UI線程發(fā)送消息來更新!這個后面會對源碼進(jìn)行分析留荔。說了半天吟孙,進(jìn)入正題,我今天要討論的是Message存谎。要掌握Handler拔疚,先對Handler發(fā)送的Message有所了解。
2既荚、Message的初步認(rèn)識
Message就是一個實現(xiàn)了Parcelable的java類,我的第一感覺是它可以可以用在進(jìn)程間通信(IPC)栋艳,可以用在網(wǎng)絡(luò)中傳輸恰聘,看一下它比較重要的幾個字段。先來幾個熟悉的。
//當(dāng)有多個Handler發(fā)消息的時候晴叨,或者一個Handler發(fā)多個消息的時候凿宾,可以用what標(biāo)識一個唯一的消息
public int what;
//當(dāng)傳遞整形數(shù)據(jù)的時候,不需要使用setData(Bundle)去設(shè)置數(shù)據(jù)兼蕊,使用arg1初厚,arg2開銷更小。
public int arg1;
public int arg2;
//Message所攜帶的數(shù)據(jù)對象
public Object obj;
這幾個都是很常用的孙技,要深入了解Message,不得不了解下面幾個产禾。
//flags表示這個Message有沒有在使用,1表示在池中牵啦,等待復(fù)用亚情,0表示正在被使用,
int flags;
//Message發(fā)送之后哈雏,何時才能被處理
long when;
// Message所攜帶的數(shù)據(jù)
Bundle data;
//表示這個消息被哪個Handler處理
Handler target;
//我們用Handler.post一個Runnable,這個Runnable也是被包裝成Message對象的
Runnable callback;
// 作為消息鏈表所用的一個成員
Message next;
//sPoolSync是對象鎖楞件,因為Message.obtain方法會在任意線程調(diào)用
private static final Object sPoolSync = new Object();
//sPool代表接下來要被重用的Message對象
private static Message sPool;
//sPoolSize表示有多少個可以被重用的對象
private static int sPoolSize = 0;
//MAX_POOL_SIZE是pool的上限,這里hardcode是50
private static final int MAX_POOL_SIZE = 50;
對于上面列舉的裳瘪,挑幾個重點分析一下土浸。
Runable是怎么被封裝到Message中的?我們常為會延遲做某個操作寫出下面的代碼
handler.postDelayed(new Runnable() {
@Override
public void run() {
}
},1000);
跟蹤進(jìn)去發(fā)現(xiàn)在postDelayed中調(diào)用了getPostMessage
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
所以這個run方法只是一個普通的回調(diào)而已彭羹,千萬不要認(rèn)為他是在另外一個線程中黄伊。剛說了Message中的target表示了這個消息要被哪一個Handler所處理,對于Message是怎么被處理的呢皆怕?盡管后面要分析Handler源碼毅舆,這里還是提一下。
Message的處理順序
Looper.loop()方法中Message被從MessageQueue取出來后會調(diào)用msg.target.dispatchMessage(msg)
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
在dispatchMessage方法中愈腾,檢查是否有由Runnable封裝的消息憋活,如果有,首先處理虱黄;其次處理的是mCallback悦即,mCallback是什么?mCallback是Callback的對象橱乱,Callback是Handler中的一個內(nèi)部接口辜梳,這個接口的作用是,當(dāng)你實例化一個Handler對象的時候泳叠,可以避免不去實現(xiàn)Handler的handleMessage方法作瞄。
/**
* Callback interface you can use when instantiating a Handler to avoid
* having to implement your own subclass of Handler.
*
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public interface Callback {
public boolean handleMessage(Message msg);
}
從消息的分發(fā)可以看到,如果返回了true,那么handler中的handleMessge方法是不會被執(zhí)行的,如果返回false或者mCallback沒有被賦值危纫,那么就會回調(diào)Handlerd的handleMessge宗挥,這就是一個Message的分發(fā)流程乌庶。了解了Message的分發(fā)之后,那么Message是怎么被創(chuàng)建的呢契耿。
3瞒大、Message全局池
ANDROID系統(tǒng)中很多操作都是靠發(fā)消息完成的,比如按下返回鍵就是一個消息搪桂,這樣系統(tǒng)會不會構(gòu)建大量的Message對象呢透敌?上面看到getPostMessage方法中,以 Message.obtain()的方式獲取一個消息踢械,而不是通過它的構(gòu)造函數(shù)酗电,獲取消息的代碼很簡單。
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
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();
}
有一個關(guān)鍵詞是sPool裸燎,初步感覺是池的意思顾瞻,別忘記了,在第一節(jié)Message的初步認(rèn)識中德绿,sPool是一個Message對象 荷荤,這里有點蒙逼,一個對象起個名字跟池有什么關(guān)系移稳≡棠桑回顧一下,Message還有一個成員是 next个粱。
// sometimes we store linked lists of these things
Message next;
這樣每一個Message對象都有一個next指針指向下一個可用的Message古毛,這不就是大學(xué)數(shù)據(jù)結(jié)構(gòu)中的鏈表嘛!所以一個消息池的結(jié)構(gòu)是這樣的都许。
既然知道Message有一個消息池(我們通常稱這個消息池為全局池)的機(jī)制稻薇,那么它設(shè)計初衷肯定是要做到復(fù)用的,那么還有一個問題,Message何時被入池胶征,何時出池塞椎?(MessageQueue雖然是存儲消息的,但要弄清楚睛低,這里說的消息池跟MessageQueue并沒有什么關(guān)系案狠。)
消息要入池,也就是這個消息被回收到池中钱雷,等待復(fù)用骂铁,所以我們大膽猜測這個消息肯定不在使用之中,如果這個消息正在使用之中罩抗,是肯定不會把它放到全局池里面的拉庵,也就是說只有這個消息完成了它的使命,系統(tǒng)才能把它回收到全局池中套蒂。通過這樣的分析名段,消息入池不是在它的創(chuàng)建階段阱扬,而是在回收階段泣懊。直接看代碼吧伸辟。Message中有一個recycle的方法。
/**
* Return a Message instance to the global pool.
* <p>
* You MUST NOT touch the Message after calling this function because it has
* effectively been freed. It is an error to recycle a message that is currently
* enqueued or that is in the process of being delivered to a Handler.
* </p>
*/
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
首先判斷消息是否在使用之中
boolean isInUse() {
return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
}
如果在使用之中馍刮,繼續(xù)判斷gCheckRecycle信夫,gCheckRecycle的默認(rèn)值是true,這個一個與版本相關(guān)的常量,在5.0之后的版本這個值是false卡啰,不知道作什么使用静稻,有誰知道可以告訴我一下。在此不糾結(jié)了匈辱。如果不在使用之中振湾,最后會走進(jìn)recycleUnchecked。
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
這段代碼也不是很長亡脸,很好理解押搪,一開始,把這個消息所有成員賦值成最初的狀態(tài)浅碾,F(xiàn)LAG_IN_USE的值是1一開始說了Message的flags表示這個Message有沒有在使用大州,1表示在池中,等待復(fù)用垂谢,0表示正在被使用厦画。重點看同步鎖中的代碼。
假設(shè)全局池沒有元素時滥朱,我們將第一個消息放到池中根暑,sPool一開始是NULL,next指向了sPool,所以此時的消息的sPool和next都是NULL徙邻,然后sPool指向當(dāng)前的Message對象排嫌,最后池的數(shù)量加1。大致如下圖鹃栽。
假設(shè)有來個消息m2躏率,在走一遍同步鎖中的代碼,此時全局池的狀態(tài)如下圖所示民鼓。
同理薇芝,三個消息的時候,是這樣
看到這丰嘉,相信你已經(jīng)知道了消息是怎么加到全局池中的夯到,那么何時出池呢?再看消息的獲取代碼饮亏,嘗試找出消息何時出池的耍贾。
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
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();
}
obtain經(jīng)常使用在多線程之中阅爽,所以用sPoolSync作為同步鎖,第一次sPool為空荐开,會new一個消息返回付翁,這new的消息會在回收的時候,會被加到全局池中晃听。如果sPool不為空百侧,sPool是什么?sPool是指向全局池的頭指針能扒,sPool不為空佣渴,說明了全局池中有元素。把sPool賦值給一個Message對象m,同時全局池的頭指針向后移初斑,指向下一個被復(fù)用的消息辛润,然后把m的flags賦值為0,表示這個消息被復(fù)用了见秤,池中元素數(shù)量減1砂竖。經(jīng)過這個邏輯,上圖中的消息m3是不是就被出池了呢秦叛?
OK晦溪,本文分析到這里,一個Message大部分內(nèi)容都被分析了挣跋,我們知道了Message內(nèi)部有一個全局池三圆,保證了開發(fā)者可以不構(gòu)建大量的消息,提高性能避咆。我們也知道了一個消息何時入池舟肉,何時出池。OK查库,Message復(fù)用機(jī)制到此結(jié)束路媚。
Please accept mybest wishes for your happiness andsuccess