Android進階 (布局繪制流程 二 setContentView源碼解讀) v1.0

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ā) 流程圖參考文章

觸摸事件分發(fā) 流程圖

硬件到Android 觸摸事件

硬件到Android 觸摸事件 參考文章

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中消費了。

繪制卡頓源碼追蹤

由于這里 源碼層級更多,不是一篇兩篇能夠說清楚的,推薦一個大佬的 博文

app卡頓 16ms vsync 的由來

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 在中國的傳統(tǒng)文化中,提起商人,首先想到的是無利不起早,無商不奸的這類詞語。而江蘇德鵬空調(diào)設(shè)備有限公司的朱炳江總經(jīng)理...
    健康就是太陽閱讀 955評論 2 2
  • 用了好幾次了,每次用到還是要查找一下,決定自己記錄一下當作筆記
    EmptyWalker閱讀 3,669評論 7 1
  • 加入簡書已經(jīng)快三年了,可是現(xiàn)在自己的簡書鉆卻寥寥無幾,關(guān)注我的人也屈指可數(shù)。別說收到點贊,就連閱讀量也少得可憐。 ...
    小清慧閱讀 1,676評論 5 8
  • 不知為何想去隨筆 也許是因為思念你 人漫無目的的聚散 漸漸地吞噬了世界 亦如互相追...
    順子叔叔閱讀 200評論 0 0