《代碼里的世界》
用文字札記描繪自己 android學(xué)習(xí)之路
轉(zhuǎn)載請(qǐng)保留出處 by Qiao http://blog.csdn.net/qiaoidea/article/details/45115047
[Android更新Ui進(jìn)階精解(一)][4] android ui線程檢查機(jī)制
[Android更新Ui進(jìn)階精解(二)][5] android 線程更新UI機(jī)制
1.回顧
[前面一篇][1]簡(jiǎn)單講了如何快速使用handler更新ui。稍微補(bǔ)充一些:
- 更新ui時(shí)可以直接使用這種方法启盛,你不須非要再new一個(gè)子線程才使用潘拨,比如:
viewPostBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
viewPostBtn.post(new Runnable() {
@Override
public void run() {
titleView.setText("viewPost——Result");
}
});
}
});
-
這幾種方法可以同樣延伸到很多類似的反不同意義的用法暂衡,比如
sendMessage()可以延伸的方法 - sendMessageAtTime(Message msg, long uptimeMillis)在指定時(shí)間uptimeMillis時(shí)發(fā)送消息msg加匈;
- sendMessageDelayed(Message msg, long delayMillis)延遲delayMillis時(shí)間后發(fā)送消息msg
- sendEmptyMessage(int what) 發(fā)送一個(gè)指定類型what的空消息攘轩;
- sendEmptyMessageAtTime(int what, long uptimeMillis)在指定時(shí)間uptimeMillis時(shí)發(fā)送一條指定類型what的空消息;
- sendEmptyMessageDelayed(int what, long delayMillis)延遲delayMillis時(shí)間后發(fā)送一條指定類型what的空消息率碾;
- sendMessageAtFrontOfQueue(Message msg)在消息隊(duì)列頭(優(yōu)先)發(fā)送這條消息msg叔营;
同樣,post()可以延伸的方法
- postAtTime(Runnable r, long uptimeMillis)
- postAtTime(Runnable r, Object token, long uptimeMillis)
- postDelayed(Runnable r, long delayMillis)
- postAtFrontOfQueue(Runnable r)
請(qǐng)自行查閱相應(yīng)方法所宰,這里不予一一列出。
2.原理--源碼分析
首先說(shuō)[上篇][1]的第一個(gè)問(wèn)題畜挥,android在生成頁(yè)面的同時(shí)生成一個(gè)ViewRootImpl的對(duì)象仔粥,這個(gè)對(duì)象負(fù)責(zé)檢查checkThread線程是否是在主ui線程,當(dāng)我們嘗試使用非ui線程更新視圖時(shí)蟹但,checkThread則拋出異常躯泰。
1. 先看看負(fù)責(zé)檢查線程的ViewRootImpl這段邏輯
@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
// other code..
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
// other code..
}
好吧,為什么要這樣华糖? 引用一段比較合理解釋:
“那么為什么Android要求只能在UI主線程中更改View呢麦向?這就要說(shuō)到Android的單線程模型了,因?yàn)槿绻С侄嗑€程修改View的話客叉,由此產(chǎn)生的線程同步和線程安全問(wèn)題將是非常繁瑣的诵竭,所以Android直接就定死了兼搏,View的操作必須在UI線程卵慰,從而簡(jiǎn)化了系統(tǒng)設(shè)計(jì)》鹕耄”
2. 再看看視圖創(chuàng)建時(shí)候是何時(shí)添加了這個(gè)檢查對(duì)象的
我們從activity創(chuàng)建說(shuō)起裳朋,首先獲取一個(gè)窗口管理器WindowManager,然后設(shè)置并初始化其container吓著。接著通過(guò)activity得到根視圖DecorView(FramLayout)鲤嫡,最后將DecorView添加到 activity的ViewManager 中去,而這個(gè)ViewManager 在addView時(shí)候就會(huì)生成一個(gè)ViewRootImpl對(duì)象绑莺。說(shuō)這么多感覺(jué)表述不清楚暖眼,更容易犯糊涂了。 看代碼
/**
* 以下方法是在調(diào)用activity Resume時(shí)候執(zhí)行
* @ActivityClientRecord r 記錄activity相關(guān)狀態(tài)及參數(shù)
*/
if (r.window == null && !a.mFinished && willBeVisible) {
//獲取根視圖DecorView
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//獲取并添加至ViewManager
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
}
然后更新和顯示activity ,調(diào)用了r.activity.makeVisible()方法
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
//判斷配置等是否需要更新view最后調(diào)用wm.updateViewLayout(decor, l); 方法紊撕,略過(guò)罢荡。。。
//最后顯示activity
r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible(); //顯示
}
}
再看activity的makeVisible()方法
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes()); //注意這里区赵。惭缰。
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
這個(gè) ViewManager 的addview方法正是關(guān)鍵,它將添加我們提到的ViewRootImpl笼才,其具體實(shí)現(xiàn)可以看WindowManagerGlobal:
//主要展示添加ViewRootImpl的過(guò)程漱受,其他代碼略
public final class WindowManagerGlobal {
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
ViewRootImpl root;
synchronized (mLock) {
root = new ViewRootImpl(view.getContext(), display);
mRoots.add(root);
}
}
}
至此,我們大略知道了ui線程是在何時(shí)對(duì)更新過(guò)程和加以控制檢查骡送,并了解了檢查的內(nèi)部原理昂羡。
看到這里很多小伙伴們肯定會(huì)不滿了,你他瞄不是說(shuō)講解handler更新Ui的原理嗎摔踱,凈扯這些有屁用呢虐先!額,由于篇幅限制派敷,這段放在下一篇[Android更新Ui進(jìn)階精解(二)][5] 講解蛹批。咱線繼續(xù)上一話題:
那么,我們真的就不能在子線程里更新Ui了嗎篮愉?顯然不是腐芍,基于前面講解的部分,既然是在onResume時(shí)候生成檢查ViewRootImpl對(duì)象试躏,所以我們其實(shí)可以在oncreate里更新Ui猪勇,比如
//onCreate調(diào)用這段
new Thread(new Runnable() {
@Override
public void run() {
titleView.setText("OtherThread");
}
}).start();
當(dāng)然,你是不能這樣用的
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(200); //睡了就再起不來(lái)了
} catch (InterruptedException e) {
e.printStackTrace();
}
titleView.setText("OtherThread");
}
}).start();
但是為什么我我們又能在OnResume里這么用颠蕴?
@Override
protected void onResume() {
super.onResume();
new Thread(new Runnable() {
@Override
public void run() {
titleView.setText("OtherThread");
}
}).start();
}
愛哥說(shuō)是因?yàn)橄㈥?duì)列Message Queue在接收和處理過(guò)程并非立即的泣刹,需要一個(gè)過(guò)程。(這一部分我大愛哥 [愛哥 --非UI線程更新UI][3] 其實(shí)有精講裁替,大家不妨看一下项玛。)其實(shí)我覺(jué)得不妨可以大膽猜想,只要view是在渲染到視圖之前弱判,我們都是可以通過(guò)其他線程來(lái)更改的襟沮。大家有空可以研究下。
--
[1]: http://www.reibang.com/p/4c60506c3ae1
[2]: http://www.cnblogs.com/xirihanlin/archive/2011/04/11/2012746.html
[3]: http://blog.csdn.net/aigestudio/article/details/43449123
[4]: http://www.reibang.com/p/6de0a42a44d6
[5]: http://##