Brief analysis of View system

View framework in application

The View frame in the application is shown in the figure.
Insert image description here

View和ViewRoot

If you use an xml file to describe the layout of the UI interface, you can find that all the elements inside actually form a tree-like structure relationship, such as:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/top"...>
 <LinearLayout android:id="@+id/digits_container"...>
  <com.android.contacts.dialpad.DigitsEditText android:id="@+id/digits".../>
  <ImageButton  android:id="@+id/deleteButton".../>
 </LinearLayout>
 <View android:id="@+id/viewEle".../>
</LinearLayout>

The relationship between the elements in this xml file is as shown in the figure.
Insert image description here

From the name, "ViewRoot" seems to be the "root of the View tree". This is easily misleading because ViewRoot is not part of the View tree. From the perspective of source code implementation, there is no "blood" relationship between ViewRoot and View objects . It is neither a subclass of View nor the parent class of View. To be more precise, ViewRoot can be understood as the "manager of the View tree" - it has an mView member variable that points to the root of the View tree it manages, that is, the element with the id "top" in the figure.

The core task of ViewRoot is to communicate with WindowManagerService.

The relationship between Activity and Window

Activity supports UI display, so does it directly manage the View tree or ViewRoot? the answer is negative. Activity is not directly related to the two. There is an object called "Window" in between.

Specifically, there is a mWindow member variable inside Activity. As follows:

private Window mWindow;

Window literally means "window", which explains its existence well. Window is the base class, and different subclasses can be derived according to different products - specifically, it is determined by the system calling PolicyManager.make NewWindow in Activity.attach. The current version of the Android system generates PhoneWindow by default.

The relationship between Window and WindowManagerImpl

Let’s look at Window first. It is Activity-oriented and represents the “outer frame of the UI interface”; the specific things “inside the frame” include layout and content, etc., which are planned by specific Window subclasses, such as PhoneWindow. But no matter what the final generated window is, the Activity does not need to be modified.

Another meaning of Window is to communicate with WindowManagerService, but it does not directly implement this function on itself. The reason is: there are likely to be multiple Windows in an application. If they all communicate with WMS separately, it will waste resources and cause management confusion. In other words, they require unified management. So there is WindowManager, which exists as the member variable mWindowManager of Window. This WindowManager is an interface class, and its real implementation is WindowManagerImpl, which is also the manager of all Windows in the entire application.

The relationship between ViewRoot and WindowManagerImpl

Within WindowManagerImpl, the ViewRoot and View tree are managed uniformly through the WindowManagerGlobal class. There are three global variables in WindowManagerGlobal:

private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();

They are used to represent the root node of the View tree, ViewRoot and Window properties respectively. It can also be seen from this that there is more than one ViewRoot in a process; Activity and ViewRoot have a one-to-one relationship.

The relationship between ViewRoot and WindowManagerService

Inside every ViewRootImpl, there is a global variable:

static IWindowSession sWindowSession;

This variable is used for the connection between ViewRoot and WMS. It is created by ViewRoot using the opneSession() interface of WMS. On this basis, ViewRoot will also provide an IWindow object through the IWindowSession.add() method - so that WMS can also conduct two-way communication with ViewRoot through this Binder object.

The relationship between Activity, WindowManagerGlobal and WMS is as follows:
Insert image description here

Each Application has an ActivityThread main thread and the mActivities global variable, which records all Activity objects running in the application. An Activity corresponds to a unique WindowManager and ViewRootImpl. WindowManagerGlobal serves as a global manager, and its internal mRoots and mViews record the ViewRootImpl of each Activity and the top-level elements of the View tree. Another important role of ViewRootImpl is to communicate with WMS. The communication from ViewRootImpl to WMS uses IWindowSession, and the reverse direction is completed by IWindow.

The creation process of View Tree in Activity

The biggest difference between Activity and other components is that it has a complete interface display mechanism internally, which involves ViewRootImpl, Window and the View Tree managed by them.
Insert image description hereThere are several subjects involved in the creation of View Tree, namely ActivityThread, Activity, PhoneWindow, ViewRootImpl and WM (it is not strictly distinguished here whether it is the local WindowManager or the server-side WindowManagerService).

  • Step1. As the main thread of the application, ActivityThread is responsible for processing various core events. For example, the task "AMS notifies the application process to start an Activity" will eventually be converted into a LAUNCH_ ACTIVITY message managed by ActivityThread, and then handleLaunchActivity is called, which is the starting point of the entire ViewTree creation process.

  • Step2. Inside handleLaunchActivity, it can be subdivided into two sub-processes ( performLaunchActivityand handleResumeActivity):

/*frameworks/base/core/java/android/app/ActivityThread.java*/
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    …
    Activity a = performLaunchActivity(r, customIntent);//启动(加载)Activity
    if (a != null) {
 handleResumeActivity(r.token, false, r.isForward);//Resume这个Activity
        …
    }…

performLaunchActivity

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { 
        …
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();//类加载器
            activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);  
            /*加载这个Activity对象*/
            …
        } catch (Exception e) {
           …
        }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
            …
            if (activity != null) {
                …
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config);
                …
                mInstrumentation.callActivityOnCreate(activity, r.state);/*最终会调用
                                                                 Activity.onCreate()*/
                …
        } catch (SuperNotCalledException e) {
            …
            return activity;
        }

The main task of this function is to generate an Activity object, call its attach method, and then indirectly call Activity.onCreate through Instrumentation.callActivityOnCreate. Among them, attach will assign values ​​to many global variables within the Activity - the most important one is mWindow. The source code is as follows:

mWindow = PolicyManager.makeNewWindow(this);

What you get here is a PhoneWindow object, which has only one instance in each Activity. We know that "Window" in Activity can be regarded as the "framework abstraction of the interface", so after having Window, the next step must be to generate specific View content, that is, mDecor in Activity. The original meaning of Decor is "decoration". In other words, in addition to containing the content that is actually wanted to be displayed in the Activity, it must also have "decoration" parts common to all applications, such as Title, ActionBar, etc. (Whether these "decorations" are ultimately displayed depends on the application. own needs).

The process of generating DecorView is initiated by setContentView, which is why developers need to call this function during onCreate. OnCreate itself is called indirectly by mInstrumentation.callActivityOnCreate(activity, r.state).

setContentView in Activity is just an intermediary, which will complete the construction of DecorView through the corresponding Window object:

/*frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java*/
      public void setContentView(int layoutResID) {
        if (mContentParent == null) {//如果是第一次调用这个函数的情况下
 installDecor();//需要首先生成mDecor对象
        } else {
            mContentParent.removeAllViews();//不是第一次调用此函数,先移除掉旧的
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);//根据ResId来创建View对象
      …
    }

The variable mContentParent is a ViewGroup object, which is used to hold "ContentView". When mContentParent is empty, it means that setContentView is called for the first time. At this time, mDecor must also be empty, so installDecor is called to create a DecorView; otherwise, all existing View objects in mContentParent are cleared first. Finally, inflate new content through layoutResID (mContentParent is the root of the View tree generated by layoutResID).

The function installDecor has two tasks, namely generating mDecor and mContentParent.

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            …
        }
		...
		 if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            …
        }
}
 protected ViewGroup generateLayout(DecorView decor) {
       TypedArray a = getWindowStyle();//获取窗口样式
       mIsFloating =a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloa  
       ting, false);
       …
       int layoutResource;
       int features = getLocalFeatures();
       if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
           …//根据具体的样式为layoutResource挑选匹配的资源
       } else if ((features & ((1 << FEATURE_PROGRESS) | (1 <<
                   FEATURE_INDETERMINATE_PROGRESS))) != 0
                   && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
           …
       } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
          …
       } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
          …
       } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
           …
       } else {
          …
            //默认加载该布局
            layoutResource = R.layout.screen_simple;
       }
       …
       View in = mLayoutInflater.inflate(layoutResource, null);//将资源inflate出来
       // 根据feature确定DecorView子布局,并添加填充满DecorView
       decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
       ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
       …
       return contentParent;
    }
  • Take out Window styles, such as windowIsFloating, windowNoTitle, windowFullscreen, etc.

  • Select the layout resource that meets the requirements based on the style obtained in the previous step, and represent it by layoutResource.
    For example, through the following statement:
    (features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0)
    you can know whether the application's UI interface requires two left and right icons; these default layout files provided by the system framework are stored in frameworks/base/core/res/res/layout.

    It is important to note that no matter which layout is used, it must contain a View object with an id value of "content", otherwise an exception will occur.

    Inflate the corresponding View object according to the layout (xml) file specified by layoutResource. Then add this new object to mDecor (DecorView is a FrameLayout); finally, the return value of the entire generateLayout function is an object with the id ID_ANDROID_CONTENT= com.android.internal.R.id.content, which is mContentParent.
    Insert image description hereThis mContentParent is a View with the id com.android.internal.R.id.content. Its function is to load the resource file passed in setContentView.

It can be seen that what setContentView actually does is to add the view (ContentView) that the application wants to display plus other elements in the system strategy (such as Title, Action) to synthesize the final application interface seen by the user ( As shown in FIG). It should be noted that setContentView is not responsible for actually displaying this view . There is an experiment that can prove this point. Readers can try not to call setContentView in the Activity to see if the final application interface can still be displayed as usual - only the "content" part in the middle is empty.

handleResumeActivity

Through performLaunchActivity, the creation process of Window and DecorView has been completed inside the Activity. It can be said that the entire View Tree has actually been generated, but it is not known to the outside world yet. In other words, neither WMS nor SurfaceFlinger knows it exists yet. So next you need to add it to the local WindowManagerGlobal (remember? There are three arrays mViews, mRoots and mParams in WindowManagerGlobal), and then register it in WMS.

final void handleResumeActivity(…) {…
        ActivityClientRecord r = performResumeActivity(token, clearHide);/*这将导致  
        Activity. onResume最终被调用*/
        if (r != null) {
            final Activity a = r.activity;
            …
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();//Activity对应的Window对象
                View decor = r.window.getDecorView();//最外围的mDecor
                decor.setVisibility(View.INVISIBLE);//先设置为不可见
                ViewManager wm = a.getWindowManager();//即WindowManager
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;//窗口类型
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);//首先添加decor到本地的全局记录中,再注册到WMS中
                }
            } else if (!willBeVisible) {
                …
            }
…

The declared type of variable wm is ViewManager. This is because WindowManager inherits from ViewManager, and getWindowManager actually returns a WindowManagerImpl object. The latter's addView indirectly calls the implementation in WindowManager Global:

/*frameworks/base/core/java/android/view/WindowManagerGlobal.java*/
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {…        
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {…
            int index = findViewLocked(view, false);//是不是添加过此View?是的话函数直接返回
            … 
            root = new ViewRootImpl(view.getContext(), display);/*为这个View生成一个配套的
                                                          ViewRootImpl*/
            view.setLayoutParams(wparams);
            if (mViews == null) {//第一次添加元素到mViews中
                index = 1;
                mViews = new View[1];
                mRoots = new ViewRootImpl[1];
                mParams = new WindowManager.LayoutParams[1];
            } else {//不是第一次操作
                …//动态分配数组容量,代码省略
            }
            index--;
            mViews[index] = view;
            mRoots[index] = root;
            mParams[index] = wparams;
        }
        try {
            root.setView(view, wparams, panelParentView);//将View注册到WMS中的关键调用
        } catch (RuntimeException e) {…
        }
    }

If the index in the above code snippet is less than 0, it means that this View object has not been added before, so the program can continue to execute; otherwise, it means that the caller has added the same View object multiple times, so the function returns directly.

addView needs to add a new ViewRootImpl to the mRoots array of WindowManagerGlobal. mViews records DecorView, and mParams records layout attributes. The elements in these three arrays are in one-to-one correspondence.

The function synchronously records DecorView into the mView variable inside ViewRootImpl through root.setView. Because ViewRootImpl will frequently access this View Tree later - for example, when receiving a key event or touch event, it needs to be passed to the latter for processing.

From this, a View Tree in an Activity is completely established and included in the local global management. However, we have not seen any substantive interactions with WMS and SurfaceFlinger, such as applying for a window for display from WMS (note that it is different from the concept of PhoneWindow); nor have we analyzed how each object in the View Tree works. Use this Window to draw the final UI content.

Register window in WMS

First of all, we need to emphasize the concept of "window" again. PhoneWindow inherits from the Window class, which expresses a constraint mechanism for windows; while Window in WMS is an abstract concept, and it has a WindowState to describe the state.

It can also be simply understood: PhoneWindow is the description of the "window" on the application process side, and WindowState is the description of the "window" in WMS.

When ViewRootImpl is constructed, it needs to establish a two-way channel to communicate with WMS.

  • ViewRootImplWMS: IwindowSession;
  • WMSViewRootImpl: Window。

Because WMS is a real-name Binder Server registered in ServiceManager (see the description in the Binder chapter for details), any program can obtain WMS services at any time by initiating a query to Service Manager. IWindowSession and IWindow are two anonymous Binder Servers, which require certain methods to provide services.
Insert image description here

  • Step1. In the constructor, ViewRootImpl will first use the openSession interface provided by WMS to open a Session channel and store it in the internal mWindowSession variable:
public ViewRootImpl(Context context, Display display) {…
       mWindowSession = WindowManagerGlobal.getWindowSession();//IWindowSession
       …
       mWindow = new W(this);//IWindow
       …
    }

The function getWindowSession is responsible for establishing the Session connection between the application and WMS:

public static IWindowSession getWindowSession() {
       synchronized (WindowManagerGlobal.class) {
           if (sWindowSession == null) {
               try {
                   InputMethodManager imm = InputMethodManager.getInstance();
                   IWindowManager windowManager = getWindowManagerService();
                   sWindowSession = windowManager.openSession(imm.getClient(),
                                                             imm.getInputContext());
                   …
               } catch (RemoteException e) {
                   Log.e(TAG, "Failed to open window session", e);
               }
           }
           return sWindowSession;
       }
}

If sWindowSession is not empty, then there is no need to repeatedly open the Session connection; otherwise, you need to obtain the WMS service through ServiceManager first, and then use the openSession interface it provides to establish a "channel" with WMS.

The windowManager variable in the above code snippet is different from the WindowManager object seen in handleResumeActivity in the previous section.

We can understand these two WindowManagers this way: the former is the agent of WindowManagerService in the local process; the latter is completely local and is stored within the application process for window management-related transactions. The former is ultimately implemented on the remote end by WindowManagerService, while the latter is implemented by WindowManagerImpl.

  • Step2. In the previous section, we saw that the function addView will call ViewRootImpl.setView at the end - on the one hand, this function will set the DecorView, which is the root of the View tree, to VierRootImpl; on the other hand, it will apply to WMS to register a window, while passing the W (subclass of IWindow) object in ViewRootImpl as a parameter to WMS.
/*frameworks/base/core/java/android/view/ViewRootImpl.java*/
    public void setView(View view, WindowManager.LayoutParams attrs, 
                                View panelParent View) {
        synchronized (this) {
            if (mView == null) {
 mView = view;//ViewRoot内部记录了它所管理的View树的根
                …
 requestLayout();//执行Layout
                …
                try {…
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mInputChannel);
                } catch (RemoteException e) {…
                } finally {…
                }…
            }… 
    }

The most critical step in the above code snippet is to apply to WMS to register a window through addToDisplay provided by IWindowSession (this function will call WMS's addWindow).

How ViewRoot basically works

Each View Tree only corresponds to one ViewRoot, which will conduct a series of communications with WindowManagerService, including window registration, size adjustment, etc. (see the interface methods provided by IWindowSession). So, under what circumstances does ViewRoot perform these operations?

  • Requests inside View Tree

For example, when a View object needs to update the UI, it will initiate a request through invalidate or other methods. Then these requests will be passed up the View Tree layer by layer, and finally reach the ViewRoot - the manager of this View Tree will take corresponding measures based on a series of actual situations (such as whether to initiate a traversal, whether to notify WMS, etc.).

  • External status updates

In addition to internal changes, ViewRoot can also receive various requests from the outside. For example, WMS will call back ViewRoot to notify interface size changes, touch events, key events, etc.

Whether it is an internal or external request, usually ViewRoot will not handle them directly, but will first enqueue the message and then process it in sequence. The ViewRootHandler class is defined internally in ViewRoot to uniformly process these messages. Interestingly, this Handler is actually hooked to the MessageQueue of the main thread, which also verifies that ViewRoot-related operations are indeed performed in the main thread. Because of this, when we perform specific event processing in ViewRootHandler, we must pay special attention not to have time-consuming operations, otherwise it is likely to block the main thread and cause ANR.

Various internal and external requests and status updates are first queued into the MessageQueue of the program's main thread, and then specifically processed by ViewRoot. Doing so avoids slowing down the response time of the application due to processing an event for a long time.

Insert image description here

View Tree traversal timing

The so-called "traversal" refers to the process in which the program sequentially accesses all elements in a collection (such as View Tree) one and only once according to a certain algorithm path.

  • 1. When the application is first started
    , according to the analysis in the previous sections, after the application is started, it will gradually construct its entire View Tree, and then conduct a comprehensive traversal:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {…
        // Schedule the first layout -before- adding to the window
        // manager, to make sure we do the relayout before receiving
        // any other events from the system.//从注释中也可以看出这是第一次执行遍历的地方
 requestLayout();

The requestLayout called in setView is the trigger source for executing the first traversal. This function will indirectly drive the execution of Layout by registering a CALLBACK_TRAVERSAL callback event with Choreographer. The final "traversal" work is done by performTraversals.

  • 2. External events
    For applications, external events are the main trigger source that drives ViewRoot's work. For example, touch, key press and other events generated by the user are passed through layers and finally distributed to the application process. In addition to changing the internal state of the application, these events may also affect the display of the UI interface - if necessary, ViewRoot will traverse to determine the specific impact of the event on each View object.

  • 3.Internal events
    In addition to external trigger sources, the program sometimes needs to actively initiate some trigger events during its own operation. For example, when we write a clock application, the interface needs to be refreshed at least every second; for example, when the visibility of a View changes from GONE to VISIBLE, it involves the adjustment and redrawing of the interface. Therefore, under these circumstances, the program must actively request the system to refresh the interface, and may trigger the execution of traversal.

Whether it is an external or internal event, as long as ViewRoot finds that it may cause changes in the size, position and other properties of the UI interface during processing, it is likely to perform a "traversal" operation. The leader of traversal is naturally ViewRootImpl, because only it can manage the entire View Tree from top to bottom.

The entrance to the traversal process is as follows:

 /*frameworks/base/core/java/android/view/ViewRootImpl.java*/
      void scheduleTraversals() {
        if (!mTraversalScheduled) {//当前是否已经在做遍历了
            mTraversalScheduled = true;
            …
 mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            …
        }
    }

The variable mTraversalScheduled is used to indicate whether "Traversal" is currently being done to avoid multiple entries. The focus of the entire function is mChoreographer.postCallback. Once the VSYNC signal comes, the run function in mTraversalRunnable will be called to ensure that the UI interface updates are organized in an orderly manner in the shortest possible time. The implementation of the function run is also very simple. It directly calls doTraversal:

 void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;//变量在这里就复位了
            …
            try {
 performTraversals();//执行遍历
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
            …
        }
    }

It can be seen that this function is not the place where the traversal is ultimately executed, and performTraversals needs to be further called.

View Tree traversal process

The three elements displayed by the UI are size, position and content, which respectively correspond to the following three functions during the traversal process:

  • performMeasure (size)
    is used to calculate the size of the drawing area of ​​the View object on the UI interface.

  • performLayout (position)
    is used to calculate the drawing position of the View object on the UI interface.

  • performDraw (drawing)
    After the above two properties are determined, the View object can draw UI content on this basis.

The body of the traversal is performTraversals.

Insert image description here

 private void performTraversals() {…
       if (mFirst || windowShouldResize || insetsChanged ||viewVisibilityChanged || params !=  
       null) {//层级1
           …
           if (!mStopped) {//层级2
              boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
               (relayoutResult&WindowManagerImpl.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
              if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                     || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {//层级3
                 int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                 int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); 
                 // Ask host how big it wants to be
 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

Conditions for executing performMeasure:
Insert image description here

If mLayoutRequested is true and is not currently stopped, then layoutRequested is true. The variable windowSizeMayChange, as its name suggests, indicates that the size of the window may change - for example, the current width and height do not match the expected value. Assuming that there is currently a layout requirement and the window size does need to be changed, then windowShouldResize is true.

performMeasure

Once the above three levels of conditions are met, the program starts executing performMeasure. In fact, this function does nothing, it simply calls the measure function of the top element of the View Tree:

mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);

In this way, ViewRootImpl transfers control to the root element of the View tree, and the real Traversal has just begun.
The measure() function of the View class will call back onMeasure(). The real measurement work is also performed in onMeasure, as follows:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
             getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

Here we take the measurement process of DecorView as an example. For example, mDecor is a FrameLayout, and its onMeasure source code is as follows:

 /*frameworks/base/core/java/android/widget/FrameLayout.java*/
    @Override //这是一个重载函数
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();//子对象个数
        /*Step1. 判断父对象的mode要求*/
        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        mMatchParentChildren.clear();
        int maxHeight = 0;//所有子对象中测量到的最大高度
        int maxWidth = 0;//所有子对象中测量到的最大宽度
        int childState = 0;

        for (int i = 0; i < count; i++) {//循环处理所有子对象
            final View child = getChildAt(i);//获取一个子对象
            if (mMeasureAllChildren || child.getVisibility() != GONE) {//需要测量吗?
 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec,
  0);//Step2.
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                /*Step3. 取得最大值*/
 maxWidth = Math.max(maxWidth, child.getMeasuredWidth() +
  lp.leftMargin + lp.rightMargin);
 maxHeight = Math.max(maxHeight,child.getMeasuredHeight() +
  lp.topMargin + lp.bottomMargin);
               childState = combineMeasuredStates(childState, child.getMeasuredState());
                …
            }
        }

        /*Step4. 综合考虑其他因素 */
        // 检查padding
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

        // 检查建议的最小宽高值
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        // 检查foreground背景宽高值
        final Drawable drawable = getForeground();
        if (drawable != null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
        }
        /*记录结果*/
 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec,
  childState),resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));//将结果保存下来
        …
    }

For the specific measurement process, you can also see:
Measurement process

performLayout

After the above performMeasure, the size of each element in the View Tree has been basically determined and stored in its own internal member variables. Next, ViewRootImpl will enter another "traversal" process, that is, position measurement. The meaning of the word Layout in the design field is similar to "composition" and "layout", so it requires both "size" and "position" information. What the function performMeasure gets is the size of the object, and performLayout is more precise, it further improves the "position" information on this basis, and then combines it into a real "layout".

The function performLayout is called in only one place in performTraversals:

    private void performTraversals() {…
        final boolean didLayout = layoutRequested && !mStopped;
        …
        if (didLayout) {
 performLayout();
           …

performLayout execution conditions

The variable didLayout depends on two factors, namely layoutRequested and mStopped - the latter has already been analyzed and will not be repeated here. The layoutRequested is mainly assigned by the following statement:

boolean layoutRequested = mLayoutRequested && !mStopped

In short, once ViewRootImpl finds that layout needs to be executed, it calls performLayout for position measurement. The specific implementation is basically the same as performMeasure, except that the layout of the top-level root element (mView) of the View Tree is indirectly called:

private void performLayout() {…
        final View host = mView;
        …
        try {
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        } …
    }

Let's still take FrameLayout as an example to see how the View object calculates layout:

/*frameworks/base/core/java/android/view/View.java*/
public void layout(int l, int t, int r, int b) {…
       boolean changed = isLayoutModeOptical(mParent) ?
              setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);//将4个边距记录到成员变量中
       if (changed||(mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED){
 onLayout(changed, l, t, r, b);//执行layout
            …
        }
        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    }

The l, t, r, and b of the above function represent the distances between the left, top, right, and bottom borders of this View object and the parent object respectively. The big difference from measure is that layout directly records (setFrame) these values ​​into member variables, namely mLeft, mTop, mRight and mBottom. Next, if changed is true, it means that the margin set this time has changed compared with the last time; or layout is mandatory in flags, then onLayout will be called. Now that the layout of the View object itself has been determined, you can guess that this function should be the process of adjusting the layout of its sub-objects. Because of this, the onLayout function implementation body in the View class is empty - this requires the extension classes of each ViewGroup, such as FrameLayout, to be overloaded and specifically implement their required functions:

/*frameworks/base/core/java/android/widget/FrameLayout.java*/
    @Override//这是个重载函数
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        final int count = getChildCount();//子对象的个数

        final int parentLeft = getPaddingLeftWithForeground();//这些变量的含义可参见后面的图示
        final int parentRight = right - left - getPaddingRightWithForeground();
        final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();
        …
        for (int i = 0; i < count; i++) {//循环处理所有子对象
            final View child = getChildAt(i);//当前子对象
            if (child.getVisibility() != GONE) {//如果为GONE的话,表示不需要在界面上显示,因而略过
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();//child   
                设置的layout属性
                final int width = child.getMeasuredWidth();//child在measure中测量到的宽度
                final int height = child.getMeasuredHeight();//child在measure中测量到的高度

                int childLeft;//最终计算出的child的左边距
                int childTop;//最终计算出的child上边距

                int gravity = lp.gravity;//这个属性值是后面计算的依据
                …
                final int layoutDirection = getResolvedLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity,layout  
                Direction);
                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.LEFT:
                        childLeft = parentLeft + lp.leftMargin;
                        break;
                    case Gravity.CENTER_HORIZONTAL://后面以此为例做详细分析
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                        break;
                    case Gravity.RIGHT:
                        childLeft = parentRight - width - lp.rightMargin;
                        break;
                    default://default情况下的处理,应用开发人员要特别留意下
                        childLeft = parentLeft + lp.leftMargin;
                }
                …//省略childTop的计算过程,和childLeft是类似的
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }

Similar to onMeasure, it first calculates the number of contained sub-objects, and then processes them one by one through a for loop. The last line of each loop calls child.layout, and the four parameters passed in are the layout information of the child.

First of all, you need to know that a cuboid layout only needs left, top, width, and height to determine - the latter two already have exact results in measure, so the final problem is transformed into the calculation of left and top. .

The following uses mLeft's processing process in the case of Gravity.CENTER_HORIZONTAL as an example to explain in detail. In order to allow readers to see it more clearly, it is also assumed that lp.leftMargin and lp.rightMargin are 0:

childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +lp.leftMargin - lp.rightMargin;
        =parentLeft + (parentRight - parentLeft - width) / 2;

Insert image description here
According to the above figure, parentRight-parentLeft gets box 2, which is the width of the FrameLayout content area. Because the sub-object is to be placed here, the center of its "center" is also based on the central axis in the figure. So (parentRight-parentLeft-width) / 2 gets the distance between the left edge of box 3 and the corresponding edge of box 2. In the end, childLeft will add parentLeft on this basis.

After calculating childLeft, the next step of the program will follow a similar method to calculate childTop. We said that for a rectangle, left+top+width+height is enough to confirm its layout. thus calling

child.layout(childLeft, childTop, childLeft + width, childTop + height);

To set the layout area of ​​the sub-object. This cycle repeats until all elements in the View Tree are processed.

For the specific layout process, you can see:
Layout process

performDraw

After the layout of an object is determined, it can execute "Draw" based on this. The function performDraw is the last one called in the traversal process. It will generate UI data on the "drawing board", which will then be integrated by SurfaceFlinger at the appropriate time and finally displayed on the screen. There are two ways to draw graphics, namely hardware and software.

Take software rendering as an example:

 /*frameworks/base/core/java/android/view/ViewRootImpl.java*/
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff,
            boolean scalingRequired, Rect dirty) {…//dirty表示需要重绘的区域
        Canvas canvas;//后续小节有详细介绍
        try {…
            canvas = mSurface.lockCanvas(dirty);//先取得一个Canvas对象,在此基础上作图
            …
        } catch (Surface.OutOfResourcesException e) {
            …
        } catch (IllegalArgumentException e) {
            …
        }

        try {…
            try {
                canvas.translate(0, -yoff);//坐标变换
                if (mTranslator != null) {
                    mTranslator.translateCanvas(canvas);
                }
                …
                mView.draw(canvas);//由顶层元素开始遍历绘制
            } finally {…
            }
        } finally {
            try {
                surface.unlockCanvasAndPost(canvas);//绘制完毕,释放Canvas并“提交结果”
            } catch (IllegalArgumentException e) {…
            }
        }
        return true;//true表示成功
    }

Focus on this sentence: mView.draw(canvas);

draw和onDraw

What is the general process for drawing graphics with View objects?

Once ViewRootImpl successfully locks to Canvas, it can gradually transfer this "drawboard tool" down through the root element of ViewTree. Therefore, the first element to be processed is the outermost DecorView (in the case of PhoneWindow), as shown below (assuming it is in the case of software rendering):

 private boolean drawSoftware(…) {…
 mView.draw(canvas);
        …
    }

The variable mView is a member variable used internally by ViewRootImpl to record the root element of the ViewTree. Its draw function is the starting point for the entire ViewTree drawing traversal. In addition, although Decor View is a ViewGroup, it does not overload the draw method, so View.draw is still called in the above code snippet.

Before analyzing the source code, let's think about it: If you were the designer of View, how would you arrange this draw function? There are at least two general directions that require special attention:

  • The separation of draw and onDraw
    is because subsequent View subclasses hope to overload only onDraw, not the entire draw function. This imposes a mandatory requirement on us, that is, the design of View's draw function must be common - because we have no way to know the behavior of all extended subclasses in advance.

Insert image description here

  • The drawing order in draw
    Insert image description herehas the following UI elements in the View class:

  • The background
    view usually needs to set a background, such as an image.

  • The content
    area is the picture that this View really wants to express, so it is the top priority. According to previous analysis, there is usually a certain distance between this area and the outer border, that is, padding.

  • decorations
    mainly refers to scrollbar. There are two types of scroll bars: vertical and horizontal, and their positions can also be adjusted.


  • In order to present a better UI effect with fading , we can also choose to add fading special effects to the View.

Let’s take a look at the source code implementation of the draw function in the View class:

 public void draw(Canvas canvas) {…
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == 
          PFLAG_DIRTY_ OPAQUE&& (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
        // Step 1.绘制背景:
        int saveCount;
        if (!dirtyOpaque) {
            …//具体代码稍后分析
        }
        /*接下来分为两种情况:要么完整执行Step2-6;要么跳过其中的Step2和Step5(稍后会有各个Step
        的详细说明,请结合起来阅读)*/
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {//情况1. 没有fading edges的情况
            if (!dirtyOpaque) onDraw(canvas);// Step 3, 绘制内容
            dispatchDraw(canvas);// Step 4, 绘制子对象
            onDrawScrollBars(canvas);// Step 6, 绘制decoration
            …
            return;//直接返回
        }

        /*情况2. 如果程序走到这里,说明我们要完整执行Step2-Step6(uncommon case)*/
        …//具体代码略
    }

draw order

(1) Draw the background.

Obviously the background is at the bottom and will be covered by other elements, so it needs to be drawn first.

(2) Save the canvas layers for subsequent fading needs.

(3) Draw the content area.

(4) Draw sub-objects (if any).

(5) Draw fading (if any) and restore the layers saved in step (2).

(6) Draw decorations (mainly scrollbars).

The above 6 steps are not all executed in every draw process. For example, step 2 and step 5 are optional for many applications and do not need to be considered. Therefore, the entire draw function is divided into two types of Cases.

Case1 (Common Case): If both horizontalEdges and verticalEdges are empty, you can skip steps 2 and 5 - this will greatly simplify the entire function process.

View.onDraw()

  • The size of each View will be restricted by other Views
  • The "potential" display content of a View may exceed its visible area
  • The visual area of ​​the View remains unchanged.
  • When the scroll bar is operated, the displayed content changes.

Next, we take ImageView as an example to analyze the source code implementation of onDraw:

  /*frameworks/base/core/java/android/widget/ImageView.java*/
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mDrawable == null) {//如果Drawable为空,直接返回
            return; // couldn't resolve the URI
        }
        if (mDrawableWidth == 0 || mDrawableHeight == 0) {//且Drawable的大小是合法的
            return;     // nothing to draw (empty bounds)
        }
        if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {//最简单的情况
            mDrawable.draw(canvas);
        } else {
            int saveCount = canvas.getSaveCount();
            canvas.save();//保存这一场景,稍后还要恢复
            if (mCropToPadding) {
                final int scrollX = mScrollX;
                final int scrollY = mScrollY;
                canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
                        scrollX + mRight - mLeft - mPaddingRight,
                        scrollY + mBottom - mTop - mPaddingBottom);
            }

            canvas.translate(mPaddingLeft, mPaddingTop);//坐标变换
            if (mDrawMatrix != null) {
                canvas.concat(mDrawMatrix);
            }
            mDrawable.draw(canvas);
            canvas.restoreToCount(saveCount);//恢复Canvas
        }
    }

For the specific drawing process, you can also view:
onDraw process

Touch analysis for View

View event delivery

Guess you like

Origin blog.csdn.net/jxq1994/article/details/132717377