16.原始碼閱讀(View的繪製-androidapi-26)

黑夜路口發表於2018-05-26

今天帶著一個問題來看Android View的繪製流程

View的繪製入口在哪?

很多時候,在進入到一個頁面的時候,會需要動態的獲取到佈局中某一個view的寬度或者高度,但是我們發現如果直接在onCreate方法或者onResume方法中通過這種方式去取高度值得到的是0

int measuredHeight = mTextView.getMeasuredHeight();

而呼叫post方法才可以得到正確的值

 mTextView.post(new Runnable() {
            @Override
            public void run() {
                int measuredHeight1 = mTextView.getMeasuredHeight();
                System.out.println("post measuredHeight:"+measuredHeight1);
            }
        });

所以回到我們的第一個問題,view的繪製入口在哪裡,只有view繪製完成經過測量才能得到寬高值,是否在onCreate和onResume方法中,view還沒有完成繪製測量呢?

在Activity的啟動流程中已經瞭解到,最終要啟動Activity並且開始執行Activity生命週期的位置是在ActivityThread的handleLaunchActivity中,我們直接來到這裡看,對Activity啟動流程不熟悉可以看另一篇https://www.jianshu.com/p/bd5208574430
handleLaunchActivity

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        ......
        //這裡會回撥onCreate方法
        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            reportSizeConfigurations(r);
            Bundle oldState = r.state;
            //這裡會回撥onResume方法
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

            if (!r.activity.mFinished && r.startsNotResumed) {
                // The activity manager actually wants this one to start out paused, because it
                // needs to be visible but isn`t in the foreground. We accomplish this by going
                // through the normal startup (because activities expect to go through onResume()
                // the first time they run, before their window is displayed), and then pausing it.
                // However, in this case we do -not- need to do the full pause cycle (of freezing
                // and such) because the activity manager assumes it can just retain the current
                // state it has.
                performPauseActivityIfNeeded(r, reason);

                // We need to keep around the original state, in case we need to be created again.
                // But we only do this for pre-Honeycomb apps, which always save their state when
                // pausing, so we can not have them save their state when restarting from a paused
                // state. For HC and later, we want to (and can) let the state be saved as the
                // normal part of stopping the activity.
                if (r.isPreHoneycomb()) {
                    r.state = oldState;
                }
            }
        } else {
            //如果啟動Activity失敗,交給ActivityManager處理
            // If there was an error, for any reason, tell the activity manager to stop us.
            try {
                ActivityManager.getService()
                    .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                            Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }
    }

performLaunchActivity
ClassLoader載入出Activity後執行了callActivityOnCreate,會回撥onCreate方法

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ......
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            
        ......

                activity.mCalled = false;
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }

        return activity;
    }

public void callActivityOnCreate(Activity activity, Bundle icicle) {
        prePerformCreate(activity);
        //呼叫activity中的performCreate,onCreate方法就在裡面
        activity.performCreate(icicle);
        postPerformCreate(activity);
    }

可以看到,activity建立出來後就開始回撥onCreate,而onCreate中做了什麼呢,呼叫了setContentView方法載入我們的佈局檔案,setContentView原始碼分析https://www.jianshu.com/p/2f87ebe77f4e

從原始碼中可以看到,setContentView做了以下的操作:
1.new了一個DecorView
2.載入了一個id為android.R.id.content的佈局並把它加入DecorView
3.我們設定的佈局檔案被載入到id為android.R.id.content的ViewGroup中

也就是說,setContentView後只是載入了佈局,但是還沒有進行測量繪製,那麼在onResum方法呼叫時是否完成了測量繪製呢?

performLaunchActivity之後系統開始執行handleResumeActivity

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ....
        //回撥onResum方法
        r = performResumeActivity(token, clearHide, reason);

        if (r != null) {
            ......
            //獲取到WindowManager,將包含了我們我們自己的佈局的
            //DecorView加入到WindowManager中(ViewManager是一個
            //介面,WindowManager實現了它)
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                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;
                    // 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 impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
                //如果Activity已經可見了,就將DecorView加入Window
                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        wm.addView(decor, l);
                    }
                }

            ......

            // The window is now visible if it has been added, we are not
            // simply finishing, and we are not starting another activity.
            if (!r.activity.mFinished && willBeVisible
                ......
                WindowManager.LayoutParams l = r.window.getAttributes();
                if ((l.softInputMode
                        & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
                        != forwardBit) {
                    l.softInputMode = (l.softInputMode
                            & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
                            | forwardBit;
                    if (r.activity.mVisibleFromClient) {
                        ViewManager wm = a.getWindowManager();
                        View decor = r.window.getDecorView();
                        //window可見之後,開始真正的繪製view了,這個方法很關鍵
                        wm.updateViewLayout(decor, l);
                    }
                }

                ......
            r.onlyLocalRequest = false;

            // Tell the activity manager we have resumed.
            if (reallyResume) {
                try {
                    ActivityManager.getService().activityResumed(token);
                } catch (RemoteException ex) {
                    throw ex.rethrowFromSystemServer();
                }
            }

        } 
    }

也就是說在onCreate和onResume方法之心的時候,還沒有完成View的測量,所以在這兩個方法中無法得到實際的高度,具體繪製的程式碼先不看,我們看看為什麼post方法中可以得到高度

public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        //把Runnable加入了佇列
        getRunQueue().post(action);
        return true;
    }

    public void post(Runnable action) {
        postDelayed(action, 0);
    }

    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 static <T> T[] append(T[] array, int currentSize, T element) {
        assert currentSize <= array.length;

        if (currentSize + 1 > array.length) {
            T[] newArray = (T[]) Array.newInstance(array.getClass().getComponentType(),
                    growSize(currentSize));
            System.arraycopy(array, 0, newArray, 0, currentSize);
            array = newArray;
        }
        array[currentSize] = element;
        return array;
    }

dispatchAttachToWindow,這個佇列是在這個方法中執行的,後邊我們會找到這個方法執行的時機,然後就會知道為什麼這個方法中執行就可以得到高度了

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        ......
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();
        ......
    }

我們再回到上邊ViewManager的addView和updateViewLayout方法,ViewManager和WindowManager都是介面,找到它的實現類WindowManagerImpl(可以通過PhoneWindow找到WindowManager的實現類是他)

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }

看一下WindowManagerGlobal這個類中做了什麼,這裡建立了一個ViewRootImpl,並把DecorView加入了這個View中

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ......

        ViewRootImpl root;
        View panelParentView = null;

           ......

            root = new ViewRootImpl(view.getContext(), display);

            ......

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
               
            }
        }
    }

//這個方法是給view設定引數的,所以關鍵還是在addView方法
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

        view.setLayoutParams(wparams);

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
            root.setLayoutParams(wparams, false);
        }
    }

setView

 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
                ......
                requestLayout();
        }
}

這裡又來到了曾經在invalidate方法中看到的原始碼,重新整理view

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    //執行這個Runnable
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
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");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
private void performTraversals() {
      ....省略大量程式碼
      // Ask host how big it wants to be 回撥到measure了
      performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
      ....
      //回撥到layout了
      if (didLayout) {
            performLayout(lp, mWidth, mHeight);
      }
      ......
      //回撥draw了
      performDraw();
}
總結一下:

1.onCreate和onResume方法呼叫時還沒有進行view的測量
2.onResume方法呼叫之後將包含著當前佈局檔案的DecorView交給了WindowManager(實現類WindowManagerImpl)處理
3.WindowManager中將DecorView設定到ViewRootImpl中,在ViewRootImpl中呼叫requestLayout開始重新整理View,執行measure layout 和 draw方法
4.post方法中能得到高度是因為post將runnable加入佇列,在view attachToWindow時後才去獲取高度,此時view已經完成了測量


相關文章