1. 程式人生 > >基於Android 8.0的原始碼分析(View的繪製流程)

基於Android 8.0的原始碼分析(View的繪製流程)

640?wx_fmt=png

今日科技快訊

據CNBC報道,社交網路巨頭Facebook已承認向61家科技公司提供了其使用者資料的特殊訪問許可權,此前該公司曾在2015年公開表示限制此類訪問。Facebook在上週五晚些時候提交給美國國會的747頁檔案中承認,該公司在2015年5月宣佈限制上述做法後,繼續與61家硬體和軟體製造商分享使用者資訊。

作者簡介

本篇來自 豌豆射手_BiuBiu的投稿,分享了Android原始碼分析(View的繪製流程),一起來看看!希望大家喜歡。

豌豆射手_BiuBiu的部落格地址:

https://www.jianshu.com/u/a58eb984bda4

正文

原始碼基於安卓8.0分析結果

View是何時開始繪製的?Activity走了onCreate方法嗎?這篇文章就是從程式的入口ActivityThread入口程式,去解釋View中的measure()方法、View中的layout、View中的draw怎麼開始呼叫的,非常有意思!雖然好多的技術文件,在半個月前已經做好了,這篇文章,對我自己來講的話,是個很好的複習~~

為了更好地闡述著這篇文章,我這裡就直接丟擲結論了,為啥會這樣的,在下篇文章會講到,這裡就記住一點,在Activity onResume後,呼叫了View onAttachedToWindow 才會開始View measure

640?wx_fmt=jpeg

Activity的生命週期和View的生命週期.jpg

為什麼會這樣子?先看ActivityThread類裡面有個內部private class H extends Handler這就是系統的Handler,具體分析請看Android原始碼分析(Handler機制)

https://www.jianshu.com/p/a2c53e96cae6

裡面有個case RESUME_ACTIVITY,獲取焦點

case RESUME_ACTIVITY:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
                    SomeArgs args = (SomeArgs) msg.obj;
                    handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0true,
                            args.argi3, "RESUME_ACTIVITY"
);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
       break;

handleResumeActivity()方法,這裡只截取了關鍵的程式碼

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ActivityClientRecord r = mActivities.get(token);
        if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
            return;
        }
        mSomeActivitiesChanged = true;
        //在這裡執行performResumeActivity的方法中會執行Activity的onResume()方法
        r = performResumeActivity(token, clearHide, reason);
        if (r != null) {
            final Activity a = r.activity;

            if (localLOGV) Slog.v(
                    TAG, "Resume " + r + " started activity: " +
                            a.mStartedActivity + ", hideForNow: " + r.hideForNow
                            + ", finished: " + a.mFinished);
            final int forwardBit = isForward ?
                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
            boolean willBeVisible = !a.mStartedActivity;
            if (!willBeVisible) {
                try {
                    willBeVisible = ActivityManager.getService().willActivityBeVisible(
                            a.getActivityToken());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            //PhoneWindow在這裡獲取到
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                //DecorView在這裡獲取到
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                //獲取ViewManager物件,在這裡getWindowManager()實質上獲取的是ViewManager的子類物件WindowManager
                // TODO: 2018/5/24 WindowManager
                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;
                    // Normally the ViewRoot sets up callbacks with the Activity
                    // in addView->ViewRootImpl#setView. If we are instead reusing
                    // the decor view we have to notify the view root that the
                    // callbacks may have changed.
                    //獲取ViewRootImpl物件
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
        }
}

第一點分析得出performResumeActivity()肯定先於wm.addView(decor, l);執行的~這也是為啥我們 Activity先獲取焦點了,才去繪製View。

performResumeActivity(),可以得出呼叫的是r.activity.performResume();

640?wx_fmt=png

關於r.activity.performResume();;這裡也可以,看出,在activity 中的fragment獲取焦點要晚於activity,雖然這是常識。注意這個方法mInstrumentation.callActivityOnResume(this);;然後才會執行onPostResume;這也就是為什麼,Activity先獲取焦點,後執行onPostResume();

final void performResume() {
        performRestart();
        mInstrumentation.callActivityOnResume(this);
        mCalled = false;
       //這裡也可以,看出,在activity 中的fragment獲取焦點要晚於activity,雖然這是常識
        mFragments.dispatchResume();
        mFragments.execPendingActions();
        onPostResume();
    }

關注這個方法mInstrumentation.callActivityOnResume(this);果然不出所料,這裡執行了activity.onResume();

640?wx_fmt=png

既然在上面知道了,activity 獲取焦點,會在上面執行,那麼View的繪製就會在下面的函式中進行。

  1. 獲取PhoneWindow;  activity.getWindow(),Window類的唯一子類

  2. 獲取window.getDecorView();DecorView,PhoneWindow的內部類,private final class DecorView extends FrameLayout ,安卓的事件分發和它密切相關Android原始碼分析(事件傳遞),也就是從Activity 傳遞到 ViewGroup的過程~~

  3. 獲取ViewManager wm = a.getWindowManager();,其實也就是activity.getWindowManager(),也就是獲取的是ViewManager的子類物件WindowManager,這裡的知道WindowManager其實也是一個介面.

640?wx_fmt=png

wm.addView(decor, l);,也就是到這裡來了,WindowManager.addView(decor,l).

//PhoneWindow在這裡獲取到
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                //DecorView在這裡獲取到
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                //獲取ViewManager物件,在這裡getWindowManager()實質上獲取的是ViewManager的子類物件WindowManager
                // TODO: 2018/5/24 WindowManager
                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;
                    // Normally the ViewRoot sets up callbacks with the Activity
                    // in addView->ViewRootImpl#setView. If we are instead reusing
                    // the decor view we have to notify the view root that the
                    // callbacks may have changed.
                    //獲取ViewRootImpl物件
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        //在這裡WindowManager將DecorView新增到PhoneWindow中
                        wm.addView(decor, l);
                    }
                }

分析到這裡來了,會通過WindowManager.addView(decor,l).我們需要去找WindowManager的實現。WindowManagerImpl;

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
   @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
}

去尋找WindowManagerGlobal的addView()方法。這裡有個單利模式,在原始碼好多地方使用的單利模式都是這樣,並沒有進行雙重判斷,在老牌的圖片載入框架ImageLoader也是這樣獲取單利物件,如果想了解更多設計模式的姿勢,可以看這片文章二十三種設計模式.

https://www.jianshu.com/p/4e01479b6a2c

public static WindowManagerGlobal getInstance() {
        synchronized (WindowManagerGlobal.class) {
            if (sDefaultWindowManager == null) {
                sDefaultWindowManager = new WindowManagerGlobal();
            }
            return sDefaultWindowManager;
        }
    }

在這裡!就是WindowManagerGlobal.addView()的關鍵的方法,我做了兩個註釋,一個是view.setLayoutParams(wparams);,這個方法非常有意思,最近在研究ViewGroup的原始碼,發現不論什麼情況下,View或者是ViewGroup都會有兩次測量,這裡是根本的原因,我先給結論。

api26:執行2次onMeasure、1次onLayout、1次onDraw。
api25-24:執行2次onMeasure、2次onLayout、1次onDraw,
api23-21:執行3次onMeasure、2次onLayout、1次onDraw,
api19-16:執行2次onMeasure、2次onLayout、1次onDraw,
API等級24:Android 7.0 Nougat
API等級25:Android 7.1 Nougat
API等級26:Android 8.0 Oreo
API等級27:Android 8.1 Oreo

後續我會做一篇文章詳細解釋下,為什麼會這樣,這裡不過多的解釋了,自提一句,非常有意思的程式碼!以前還會有兩次的layout,說明谷歌也在優化安卓 framework。todo

public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
     ...
      root = new ViewRootImpl(view.getContext(), display);
            //view setLLayoutParams()在這裡
      view.setLayoutParams(wparams);
      try {
                // TODO: 2018/6/4  這裡呢?就是ViewRootImpl 呼叫的setView的方法,就在這裡
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }

ok,現在繼續的關注這個方法ViewRootImpl.setView(view, wparams, panelParentView)

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {   try {
                 // TODO: 2018/6/4 這裡傳入的attrs 決定了View 或者是ViewGroup是否會onMeasure 兩次
                mWindowAttributes.copyFrom(attrs);
                } catch (RemoteException e) {

                    // TODO: 2018/5/24 就會調動這裡的來
                    unscheduleTraversals();
                } finally {
                    if (restore) {
                        attrs.restore();
                    } if (res < WindowManagerGlobal.ADD_OKAY) {

                    // TODO: 2018/5/24 就會調動這裡的來
                    unscheduleTraversals();}
                }

unscheduleTraversals(),沒有Activity獲取焦點的時候,這個方法肯定會執行

void unscheduleTraversals() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            mChoreographer.removeCallbacks(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }

關注mTraversalRunnable物件

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

doTraversal()方法,Traversal翻譯過來就是遍歷的意思~~

// TODO: 2018/5/24  到這裡來了 ---->     Traversal 遍歷
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            performTraversals();
            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

performTraversals這裡就是整個View繪製的開始,所有的繪製,都會從這裡開始,雖然這個方法程式碼有點多,但是關鍵的地方我都做了註釋,下面一步一步的分析

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

如果視窗的型別是有狀態列的,那麼頂層檢視DecorView所需要的視窗的寬度和高度

// 如果視窗的型別是有狀態列的,那麼頂層檢視DecorView所需要的視窗的寬度和高度
            //就是除了狀態列
            if (shouldUseDisplaySize(lp)) {
                // NOTE -- system code, won't try to do compat mode.
                Point size = new Point();
                mDisplay.getRealSize(size);
                desiredWindowWidth = size.x;
                desiredWindowHeight = size.y;
            } else {
                //否者頂層檢視DecorView所需要的視窗的寬度和高度就是整個螢幕的寬度
                desiredWindowWidth = dipToPx(config.screenWidthDp);
                desiredWindowHeight = dipToPx(config.screenHeightDp);
            }

獲得view寬高的測量規格

// TODO: 2018/5/25 //獲得view寬高的測量規格,
                    // TODO: 2018/5/25 mWidth和mHeight表示視窗的寬高,lp.widthhe和lp.height表示DecorView根佈局寬和高
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

注意這個物件WindowManager.LayoutParams lp ,如果說lp.horizontalWeight > 0.0f或者是lp.verticalWeight > 0.0f,那麼measureAgain =true;horizontalWeight這個標記大概是這個意思指示額外空間的多少將被水平分配。如果檢視指定0不應被拉伸。否則額外畫素將被優先評估。在所有重量大於0的檢視中。一般都指示出還有多少的水平的空間將要被分配。

/**
         * 這個WindowMananger 這裡標記了 是
         */

        // TODO: 2018/6/4
        WindowManager.LayoutParams lp = mWindowAttributes;
              // TODO: 2018/5/25  這裡是第一步的  執行測量的操作
           performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
          if (lp.horizontalWeight > 0.0f) {
                        width += (int) ((mWidth - width) * lp.horizontalWeight);
                        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
                    if (lp.verticalWeight > 0.0f) {
                        height += (int) ((mHeight - height) * lp.verticalWeight);
                        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }

如果這個measureAgain=true的話,就會再次呼叫performMeasure(),通過程式碼可以發現這就呼叫了兩次performMeasure;

其實我這裡犯了一個錯誤,不是這樣的子,這個標記不一定是為true。

if (measureAgain) {
                        if (DEBUG_LAYOUT) Log.v(mTag,
                                "And hey let's measure once more: width=" + width
                                        + " height=" + height);
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
   }

關於performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);方法,其實就是呼叫的是mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);,也就是View第一步是測量。

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

第一次繪製的時候,這個標記一定是didLayout一定是true,一定會走到這個方法裡面去performLayout(lp, mWidth, mHeight);

final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {
            // TODO: 2018/5/25  執行佈局操作
            performLayout(lp, mWidth, mHeight);
        }
}

關於performLayout這個方法,直接會呼叫host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());,也就是View的layout的方法。

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                               int desiredWindowHeight)
 
{
final View host = mView;
  try {
            host.layout(00, host.getMeasuredWidth(), host.getMeasuredHeight());
           //測試層級
                    measureHierarchy(host, lp, mView.getContext().getResources(),
                            desiredWindowWidth, desiredWindowHeight);
 } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

這兩個標記也是!cancelDraw && !newSurface為true,那麼就會走到performDraw();

if (!cancelDraw && !newSurface) {
            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).startChangingAnimations();
                }
                mPendingTransitions.clear();
            }
            // TODO: 2018/5/25 執行繪製的操作
            performDraw();
        }

關於performDraw();方法,直接呼叫的是draw(fullRedrawNeeded);

private void performDraw() {
        mIsDrawing = true;
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
        try {
            draw(fullRedrawNeeded);
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
}

關於draw(fullRedrawNeeded);,會呼叫到這裡來drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)

private void draw(boolean fullRedrawNeeded) {
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                    return;
                }
    }