Android進階 布局繪制流程 一 setContentView源碼解讀
本文承接上文 view 繪制流程 一。上文跟隨源碼分析了 setContentVIew() 后的部分 關(guān)于 PhoneWindow 和 Layoutinfate 部分的源碼解析。該篇文章本人將繼續(xù)學習和總結(jié) ,并簡單提及到經(jīng)典面試題。本文大概閱讀時間:30分鐘 Andorid SDK :28.0 。
上文 分析道 PhoneWindow 的 setContentView 進行了分部解讀
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
// view繪制流程 一 第一節(jié)
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
// view 繪制流程 一 第四節(jié)
mLayoutInflater.inflate(layoutResID, mContentParent);
}
// 該篇我們順序從該方法繼續(xù)研究學習。!!!!!!
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
我們正式開始!
ViewRootImpl
// view 類
public void requestApplyInsets() {
requestFitSystemWindows();
}
public void requestFitSystemWindows() {
if (mParent != null) {
mParent.requestFitSystemWindows();
}
}
這里的mParent是ViewParent的具體實現(xiàn)ViewRootImpl,所以調(diào)用的是ViewRootImpl里的requestFitSystemWindows方法。
// ViewRootImpl 類
@Override
public void requestFitSystemWindows() {
// 檢查線程 是否是在主線程
checkThread();
mApplyInsetsRequested = true;
// 視圖繪制
scheduleTraversals();
}
ViewRootImpl.scheduleTraversals()
void scheduleTraversals() {
// 沒有正在繪制的 任務(wù)才能進入
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 發(fā)送一個消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
...
}
}
// Runnable實現(xiàn)
final class TraversalRunnable implements Runnable {
@Override
public void run() {
// 繪制方法
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// 最重要的地方 執(zhí)行view 的繪制流程
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
performTraversals()
private void performTraversals() {
...
// 第一次加載View,需要調(diào)整窗口大小,需要適應(yīng)系統(tǒng)窗口,視圖顯示狀態(tài)改變,
// 視圖布局參數(shù)不為空,強制窗口重新布局。才可能執(zhí)行測量
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
...
// 窗口沒有停止,或者通知需要繪制
if (!mStopped || mReportNextDraw) {
...
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
...
// 1.第一步:測量
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
}
} else {
...
}
...
if (didLayout) {// 執(zhí)行布局
// 2.第二步:布局
performLayout(lp, mWidth, mHeight);
...
}
...
// 如果沒有取消繪制,并且不是新的Surface,那么執(zhí)行繪制
if (!cancelDraw && !newSurface) {
...
// 3.第三步:繪制
performDraw();
} else {// 如果取消了繪制或者是新的Surface,那么要重新測量、布局和繪制
...
}
mIsInTraversal = false;
}
重要的方法就是在上述的代碼中 通過 performMeasure(), performLayout() ,performDraw() 三個方法,方法內(nèi)也分別調(diào)用了 View的 measure layout 和 draw 方法。這也印證了我們 在編寫自定義View的時候 需要默認實現(xiàn)的三個方法的由來,也說明了三個方法的順序。
View 繪制相關(guān)面試題
事件分發(fā)
前邊的文章已經(jīng)提到過,這里就放兩張圖片,一圖勝千言
觸摸事件分發(fā) 流程圖參考文章
Dialog 和 Activity 點擊事件問題
案例:當Dialog 現(xiàn)實的時候點擊 Activity 部分按鈕是沒有反映的,為什么呢?
我們先看下 Dialog的初始化源碼
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
if (createContextThemeWrapper) {
if (themeResId == ResourceId.ID_NULL) {
final TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
themeResId = outValue.resourceId;
}
mContext = new ContextThemeWrapper(context, themeResId);
} else {
mContext = context;
}
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//新建PhoneWindow
final Window w = new PhoneWindow(mContext);
mWindow = w;
//將回調(diào)注入到window當中
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
這里創(chuàng)建好 phoneWindow 后setContentView() 設(shè)置布局,這里就繞到了 上篇文章的 源碼順序中,通過 installDecor() ,創(chuàng)建 DecorView 將我們傳入的布局id 帶入,通過 xml pull 方式迭代讀取 view 最后返回一個view 對象,并將其添加到 DecorView中。
public void show() {
...
mWindowManager.addView(mDecor, l);
...
}
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
//創(chuàng)建ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
// setView
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
...
}
}
}
Dialog 通過show() 的方式,將DecorView 傳給 WindowManager實現(xiàn)類 。 經(jīng)過底層會調(diào)用到ViewRootImpl的ViewPostImeInputStage里面來。但是因為ViewRootImpl存在著多個,InputManagerService 會根據(jù)Z軸的高度,獲取最近一個窗口,然后執(zhí)行對應(yīng)ViewRootImpl里面ViewPostImeInputStage的監(jiān)聽方法->執(zhí)行mView.dispatchPointerEvent(event)。所以點擊事件便在最上層 的Dialog中消費了。
繪制卡頓源碼追蹤
由于這里 源碼層級更多,不是一篇兩篇能夠說清楚的,推薦一個大佬的 博文