起因:之前一群裡的哥們問 Handler.post() Activity onResume() view.post() view.post() view
為什麼會在
的
之後執行,我找了一遍之後并沒有找到原因,後來從這個問題我想起其他的問題
為什麼在
之後為什麼可以準确的擷取到
的寬高。
疑問🤔️:View.post()為什麼會準确的擷取到View的寬高?
public boolean post(Runnable action) {
//注釋1:判斷attachInfo如果不為空 直接調用attachInfo内部Handler.post()方法
//這樣就有一個問題attachInfo在哪裡指派?這個問題先存疑。
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
//注釋2:此時attachInfo是空的
getRunQueue().post(action);
return true;
}
複制代碼
存疑1:attachInfo在哪裡指派?
我們點進去看一下
getRunQueue().post(action);
是何方神聖。
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
複制代碼
上面代碼很簡單,那麼接下來就需要看看
HandlerActionQueue()
是什麼玩意了。
public class HandlerActionQueue {
private HandlerAction[] mActions;
private int mCount;
//注釋1
public void post(Runnable action) {
postDelayed(action, 0);
}
//注釋2
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
public void removeCallbacks(Runnable action) {}
//假裝不知道這個是執行方法
public void executeActions(Handler handler) {}
public int size() {}
public Runnable getRunnable(int index) {}
public long getDelay(int index) {}
private static class HandlerAction {}
}
複制代碼
先看
注釋1
可以看到這個
post()
方法 其實就是
getRunQueue().post(action);
它内部調用了
注釋2
方法 也就是
postDelayed()
注釋2
簡單看了裡面的代碼 發現大概邏輯隻是對 我們
post
runnable
進行緩存起來,那麼我們從哪裡真正執行這個
runnable
呢? 我發現 我們一路跟進來直到
postDelayed()
方法時并沒有看到對應的執行方法那麼我們就放大招了
影·奧義!時光回溯。
我們看看
getRunQueue()
時 傳回的mRunQueue在哪裡調用的就好了。
//不會上傳圖檔...大小不懂得控制 是以用代碼塊
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
//忽略的一些代碼
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
}
複制代碼
通過檢查對
mRunQueue
的引用 可以看到
view
代碼内隻有這兩個方法有所使用,那麼第一個方法我們以及看過了 是以我們重點看下第二個方法。
從上面第二個方法 可以得知
mAttachInfo
由
dispatchAttachedToWindow(AttachInfo info, int visibility)
内的
info
指派 那麼
info
又在哪裡生成?先繼續看下去。
通過對
dispatchAttachedToWindow()
的調用關系可以發現以下方法會調用
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
/.../
//注釋1
host.dispatchAttachedToWindow(mAttachInfo, 0);
/.../
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//執行測量
performLayout(lp, mWidth, mHeight);//執行布局
performDraw();//執行繪制
}
複制代碼
可以看到
host
内會調用這個方法,并且将
mAttachInfo
作為參數傳入,而這個
host
是一個
DecorView
為什麼是
DecorView
? 我們可以反推回去驗證 通過源碼我們得知
host
是
ViewRootImpl
中的
mView
的成員變量 對
mView
檢查指派的地方 可以看到以下代碼:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;//對mView 指派
/.../
}
}
}
複制代碼
那什麼時候能調用到
setView()
呢? 可以看到
setView()
并不是靜态方法,是以要調用并需要引用執行個體才可以。
那麼我們可以看看構造方法
public ViewRootImpl(Context context, Display display) {
mContext = context;
/.../
//注釋1
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context);
}
複制代碼
可以看到注釋1處的代碼我們能夠得知
mAttachInfo
是在
ViewRootImpl
構造器中建立出來的。
我們再對構造器檢視調用 可以發現再
WindowManagerGlobal
addView()
方法 會建立
ViewRootImpl
,
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
/.../
root = new ViewRootImpl(view.getContext(), display);
}
複制代碼
而
WindowManagerGlobal
addView()
方法 又被
WindowManagerImpl
addView()
調用
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
複制代碼
再對這個方法進行調用檢查可以看到
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView(); //注釋1
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l); //注釋2
} else {
a.onWindowAttributesChanged(l);
}
}
} else if (!willBeVisible) {
if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
}
複制代碼
可以在
注釋1
處:會傳回一個
DecorView
并且在注釋2處添加進入。 至此 我們可以得知
ViewRootImpl
處的
mView
就是
DecorView
。
知識點:并且我們可以從上面代碼看出在
onResume
時
view
才會被添加在
window
内并且執行
view
的測量布局繪制 這就是為什麼在
onCreate()
時擷取到
view
寬高會是0的原因,因為那時
view
都沒有添加進
window
呢!!!
時間再次穿梭 回到
ViewRootImpl
performTraversals()
處
既然已知
mView
DecorView
那麼這個
DecorView
是一個繼承于
FrameLayout
ViewGroup
, 我們在
DecorView
和
FrameLayout
内沒有找到對
dispatchAttachedToWindow()
方法的處理,就自然而然的來到了
ViewGroup
處。
@Override
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
//周遊調用 這樣最終會回到View的dispatchAttachedToWindow()
child.dispatchAttachedToWindow(info,
combineVisibility(visibility, child.getVisibility()));
}
}
複制代碼
這時候我們又回到了
View
dispatchAttachedToWindow()
方法内的
mRunQueue.executeActions(info.mHandler)
;并點選去看源碼
public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;//注釋1
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
handler.postDelayed(handlerAction.action, handlerAction.delay);//注釋2
}
mActions = null;
mCount = 0;
}
}
複制代碼
從
注釋1
處 我們可以看到
mActions
其實是我們用
view.post
方法時 傳入的
runnable
的存儲數組。 而
注釋2
處 就會将
runnable
交給
handler.post()
方法并且添加進這個
Handler
所持有的
Looper
内部的
MessageQueue
中。
至此我們可以大概的進行一次總結了。
總結:
1:
View
mAttachInfo
會在
View
dispatchAttachedToWindow()
方法内指派 在
dispatchDetachedFromWindow()
指派為
null
,并且
mAttachInfo
的根本是在
ViewRootImpl
的構造器内建立的,是以我們就可以知道當
view
attchInfo
不為空時 這個
view
是已經被添加進視窗内的,如果為
null
就說明
view
沒有在
window
内。
2: 我們能通過
view.post()
正确的擷取
View
的寬高主要得益于
Android
内的生命周期是被
Handler
所驅動的,是以當
ViewRootImpl
在
Activity
onResume()
生命周期内被建立時,其實主線程的
Handler
是在執行處理一個
Message
的流程中,雖然我們從上面
ViewRootImpl
performTraversals()
源碼中看到
view
緩存的
runnable
performMeasure(), performLayout(),performDraw()
這些方法前先被
post
出去并且添加到
MessageQueue
連結清單中,但是這些
runnable
是屬于下一個
Message
的,而
performMeasure(), performLayout(),performDraw()
這三個方法是屬于本次
Message
的邏輯,隻有本次消息處理完成
Handler
Looper
才會進行下一次消息的處理,最終保證了
View.post()
能夠正确的拿到
View
作者:FeanCheng
連結:
https://juejin.cn/post/6916024212696236040來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。