本文主要講解view.post() 的四大常見疑問
- 為什么view.post()能保證獲取到view的寬高?
- 為什么onCreate()使用view.post()無法立刻執(zhí)行任務(wù)(如獲取寬高)
- 若只是創(chuàng)建一個(gè) View & 調(diào)用view.post()傳入要執(zhí)行的任務(wù)鹿霸,為什么該任務(wù)不會被執(zhí)行排吴?
- view.pos()傳入的任務(wù)被執(zhí)行的有效期是什么時(shí)間節(jié)點(diǎn)?
常見疑問1
a. 描述
為什么view.post()能保證獲取到view的寬高懦鼠?
.b 原因
View.post()的原理:以Handler為基礎(chǔ)钻哩,View.post() 將傳入任務(wù)添加到 View繪制任務(wù)所在的消息隊(duì)列尾部,從而保證View.post() 任務(wù)的執(zhí)行時(shí)機(jī)是在View 繪制任務(wù)完成之后的肛冶。 其中街氢,幾個(gè)關(guān)鍵點(diǎn):
View.post()實(shí)際操作:將view.post()傳入的任務(wù)保存到一個(gè)數(shù)組里
View.post()添加的任務(wù) 添加到 View繪制任務(wù)所在的消息隊(duì)列尾部的時(shí)機(jī):View 繪制流程的開始階段,即 ViewRootImpl.performTraversals()
View.post()添加的任務(wù)執(zhí)行時(shí)機(jī):在View繪制任務(wù)之后
所以:
通過View.post()添加的任務(wù)是在View繪制任務(wù)里 - 開始繪制階段時(shí)添加到消息隊(duì)列尾部的淑趾;
所以,View.post() 添加的任務(wù)的執(zhí)行是在View繪制任務(wù)后才執(zhí)行忧陪,即在View繪制流程結(jié)束之后執(zhí)行扣泊。
即View.post() 添加的任務(wù)能夠保證在所有 View繪制流程結(jié)束之后才被執(zhí)行,所以 執(zhí)行View.post() 添加的任務(wù)時(shí)可以正確獲取到 View 的寬高嘶摊。
具體源碼分析請看:Android:為什么view.post()能保證獲取到view的寬高延蟹?
常見疑問2
a. 描述
為什么onCreate()使用view.post()無法立刻執(zhí)行任務(wù)(如獲取寬高),需要在onResume()后才可獲纫抖选阱飘?
.b 原因
在onCreate()時(shí),AttachInfo還沒被賦值(為null)(是在view.dispatchAttachedToWindow()才被賦值)虱颗,所以會走下述源碼的過程2沥匈;通過上面分析,此過程的作用僅是:保存了通過post()添加的任務(wù)忘渔,并沒執(zhí)行高帖。
public boolean post(Runnable action) {
// ...
// 判斷AttachInfo是否為null
final AttachInfo attachInfo = mAttachInfo;
// 過程1:若不為null,直接調(diào)用其內(nèi)部Handler的post ->>分析1
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// 過程2:若為null,則加入當(dāng)前View的等待隊(duì)列
getRunQueue().post(action);
return true;
}
c. 實(shí)例代碼演示
@Override
public void onCreate(Bundle savedInstanceState) {
// 執(zhí)行日志1:carsonhe oncreate()
view.post(new Runnable() {
@Override
public void run() {
// 執(zhí)行日志2:carsonhe view.post() do something
}
});
}
@Override
protected void onResume() {
// 執(zhí)行日志3:carsonhe onresume()
}
// 輸出日志展示
日志1:carsonhe oncreate()
日志3:carsonhe onresume()
日志2:carsonhe view.post() do something
常見疑問3
a. 問題描述
若只是創(chuàng)建一個(gè) View & 調(diào)用它的post()畦粮,那么post的任務(wù)會不會被執(zhí)行散址?
final View view = new View(this);
view.post(new Runnable() {
@Override
public void run() {
// ...
}
});
b. 答案
不會乖阵。主要原因是:
每個(gè)View中post() 需執(zhí)行的任務(wù),必須得添加到窗口視圖-執(zhí)行繪制流程 - 任務(wù)才會被post到消息隊(duì)列里去等待執(zhí)行预麸,即依賴于dispatchAttachedToWindow ()瞪浸;
若View未添加到窗口視圖,那么就不會走繪制流程吏祸,post() 添加的任務(wù)最終不會被post到消息隊(duì)列里对蒲,即得不到執(zhí)行。(但會保存到HandlerAction數(shù)組里)
上述例子犁罩,因?yàn)樗鼪]有被添加到窗口視圖齐蔽,所以不會走繪制流程,所以該任務(wù)最終不會被post到消息隊(duì)列里 & 執(zhí)行
c. 解決方案
此時(shí)只需要添加將View添加到窗口床估,那么post()的任務(wù)即可被執(zhí)行
// 因?yàn)榇藭r(shí)會重新發(fā)起繪制流程含滴,post的任務(wù)會被放到消息隊(duì)列里,所以會被執(zhí)行
contentView.addView(view);
常見疑問4
a. 描述
view.pos()傳入的任務(wù)被執(zhí)行的有效期是多久丐巫?
b. 結(jié)論
在整個(gè) Activity 的生命周期內(nèi)都可以正常使用 View.post() 任務(wù)
c.原因
任務(wù)被執(zhí)行是構(gòu)造AttachInfo谈况,所以任務(wù)釋放即時(shí)釋放AttachInfo (置為null)。而AttachInfo 的釋放操作(置為null)是在 Activity 生命周期 onDestory 方法之后
.d 原因分析
目標(biāo)
跟蹤 AttachInfo 的釋放過程(即何時(shí)置為null)方向
AttachInfo的賦值依賴于DecorView.dispatchAttachedToWindow()递胧,那么釋放過程碑韵,容易聯(lián)想到是對應(yīng)的:DecorView.dispatchDetachedFromWindow()
- 具體源碼分析
/**
* 入口分析:DecorView.dispatchDetachedFromWindow()
* 實(shí)際上是調(diào)用父類ViewGroup.dispatchDetachedFromWindow()
*/
void dispatchDetachedFromWindow() {
// ...
final int count = mChildrenCount;
final View[] children = mChildren;
// 遍歷所有childView
for (int i = 0; i < count; i++) {
// 遍歷所有childView & dispatchDetachedFromWindow()
// 分析1
children[i].dispatchDetachedFromWindow();
}
}
/**
* 分析1:childView.dispatchDetachedFromWindow()
*/
void dispatchDetachedFromWindow() {
// ...
AttachInfo info = mAttachInfo;
// 1. 回調(diào)View.onDetachedFromWindow()
onDetachedFromWindow();
// 2. 通知所有監(jiān)聽View.onAttachToWindow的監(jiān)聽者回調(diào)onViewDetachedFromWindow()
ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners = li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
for (OnAttachStateChangeListener listener : listeners) {
listener.onViewDetachedFromWindow(this);
}
}
// 3. 將AttachInfo置為null
mAttachInfo = null;
}
下面,我們將分析缎脾,什么時(shí)候調(diào)用上述入口祝闻,即DecorView.dispatchDetachedFromWindow();
此時(shí)需從 將DecorView從WindowManager中移除 開始講起:移除 Window 窗口任務(wù)是通過 ActivityThread.handleDestoryActivity()完成遗菠。
/**
* 入口
*/
private void handleDestroyActivity(IBinder token, boolean finishing,
int configChanges, boolean getNonConfigInstance) {
// 關(guān)注1:回調(diào) Activity.onDestory()
ActivityClientRecord r = performDestroyActivity(token, finishing,
configChanges, getNonConfigInstance);
// 獲取當(dāng)前Window的WindowManager
WindowManager wm = r.activity.getWindowManager();
// 當(dāng)前Window的DecorView
View v = r.activity.mDecor;
// 關(guān)注2:通知WindowManager,移除當(dāng)前 Window窗口
wm.removeViewImmediate(v);
// 此處即會釋放AttachInfo
// 因?yàn)樵陉P(guān)注1處是在回調(diào) Activity.onDestory()后联喘,故在整個(gè)Activity的生命周期內(nèi)都可以正常使用 View.post() 任務(wù)
// 下面繼續(xù)分析如何移除 ->> 分析1
}
/**
* 分析1:WindowManager.removeViewImmediate()
*/
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
// 調(diào)用WindowManagerGlobal的removeView()
// ->> 分析2
}
/**
* 分析2:WindowManagerGlobal.removeView()
*/
public void removeView(View view, boolean immediate) {
// ...
// 找到保存該DecorView的下標(biāo)
int index = findViewLocked(view, true);
// 找到對應(yīng)的ViewRootImpl,內(nèi)部的DecorView
View curView = mRoots.get(index).getView();
// 從WindowManager中移除該DecorView
// immediate 表示是否立即移除
removeViewLocked(index, immediate);
// ->> 分析3
}
/**
* 分析3
*/
private void removeViewLocked(int index, boolean immediate) {
// 找到對應(yīng)的ViewRootImpl
ViewRootImpl root = mRoots.get(index);
// 該View是DecorView
View view = root.getView();
// ...
// 調(diào)用ViewRootImpl的die
// 并且將當(dāng)前ViewRootImpl在WindowManagerGlobal中移除
boolean deferred = root.die(immediate);
// ->> 分析4
}
/**
* 分析4
*/
boolean die(boolean immediate) {
// immediate 表示立即執(zhí)行
// mIsInTraversal 表示是否正在執(zhí)行繪制任務(wù)
if (immediate && !mIsInTraversal) {
doDie();
// ->> 分析5
}
// ...
}
/**
* 分析5
*/
void doDie() {
// ...
if (mAdded) {
dispatchDetachedFromWindow();
// 回調(diào)View的dispatchDetachedFromWindow
// ->> 即一開始分析的DecorView.dispatchAttachedToWindow()
}
// 將其從WindowManagerGlobal中移除DecorView
WindowManagerGlobal.getInstance().doRemoveView(this);
}
.d 最終原因 & 結(jié)論
View.post() 任務(wù)被執(zhí)行的有效期是在 Activity 生命周期 onDestory()后辙纬。本質(zhì)是追蹤AttachInfo的釋放過程(置為null)
AttachInfo的釋放過程是在 將DecorView從WindowManager中移除時(shí):回調(diào)DecorView.dispatchDetachedFromWindow()豁遭,其具體行為是:
- 回調(diào)View.onDetachedFromWindow()
- 通知所有監(jiān)聽View.onAttachToWindow的監(jiān)聽者回調(diào)onViewDetachedFromWindow()
- 將AttachInfo置為null
而上述過程是在ActivityThread.handleDestoryActivity()中回調(diào) Activity.onDestory()之后。
至此贺拣,關(guān)于view.post()的四大常見疑問 (坑)內(nèi)容講解完畢蓖谢。
總結(jié)
- 本文主要總結(jié)了常用的view.post() 的四大常見疑問
- 接下來推出的文章,我將繼續(xù)講解
Android
的相關(guān)知識譬涡,感興趣的讀者可以繼續(xù)關(guān)注我的博客哦:Carson_Ho的Android博客
相關(guān)系列文章閱讀
Carson帶你學(xué)Android:學(xué)習(xí)方法
Carson帶你學(xué)Android:四大組件
Carson帶你學(xué)Android:自定義View
Carson帶你學(xué)Android:異步-多線程
Carson帶你學(xué)Android:性能優(yōu)化
Carson帶你學(xué)Android:動(dòng)畫
歡迎關(guān)注Carson_Ho的簡書
不定期分享關(guān)于安卓開發(fā)的干貨闪幽,追求短、平涡匀、快沟使,但卻不缺深度。