android source code -- Summary of view creation principle

1, android interface composition

Activity: control model, control Window.
Window: host model, which is responsible for hosting the view.
View: display model, used to display.
ViewRoot(ViewRootImpl): it is the link between WindowManager and DecorView.

2, View create overall process summary

1, in ActivityThread's performLaunchActivity, we call the attach method of Activity and finish the creation of PhoneWindow in attach method.
2. Create a DecorView in the onCreate method, setContentView method and PhoneWindow of the Activity, and fill the layoutId layout passed in from the Activity in the DecorView. The layout file can be parsed through layoutinflator. DecorView is actually a FrameLayout, and different views are loaded through various theme styles to fill the DecorView.
Layoutinflator must be made into a global singleton. The simple reason is to speed up the process of view instantiation and share the cache of reflected constructors.
3. In handleResumeActivity of ActivityThread, addView method of WindowManager will be called to add DecorView to WMS(WindowManagerService). addView finds the one to create ViewRootImpl, and adds the view to WMS through the setView method of ViewRootImpl.
4. When you use requestwindowfeature to set the style, you actually call the requestFeature method of PhoneWindow, which will store the style in the mLocalFeatures variable of Window. When installDecor, these styles will be applied. That is to say, when requestWindowFeature is required to request styles, it should be called before the setContentView method, because the invocation of the setContentView method causes DecorView to create and apply styles, and if it is called later, it will not work because DecorView has been created at this time.

The setView method of ViewRootImpl mainly completes: View rendering (requestLayout), (performTraservals) and receiving touch screen events. A series of input pipes are set to distribute touch events to DecorView.

3, View specific creation process

1. Creation of PhoneWindow

ActivityThread.java

 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);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } 
···
                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }
                appContext.setOuterContext(activity);
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
···
}

Activity.java

    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();

        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
···
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
    }

An Activity creates a Window in its attach method. In fact, it creates a PhoneWindow and establishes a connection with the Activity through various callbacks. What we use getWindow() in the Activity is the created Window.

###2. Setcontentview (created by decorview)
Generally, the interface of an Activity is created by introducing the layout through setContentView.

mWindow = new PhoneWindow(this, window);

public void setContentView(@LayoutRes int layoutResID) {
   getWindow().setContentView(layoutResID);
   initWindowDecorActionBar();
}

The setContentView method of Activity calls the setContentView method of Window, which is an abstract object, and its specific implementation class is PhoneWindow. Locate the setContentView method in the PhoneWindow

@Override
public void setContentView(int layoutResID) {
     // If mContentParent is empty, build it first
   if (mContentParent == null) {
       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 {
            // Parsing layouts through layoutinflator
       mLayoutInflater.inflate(layoutResID, mContentParent);
   }
   mContentParent.requestApplyInsets();
   final Callback cb = getCallback();
   if (cb != null && !isDestroyed()) {
       cb.onContentChanged();
   }
}

At this point, we have created a DecorView in PhoneWindow, and filled the layoutId layout passed in from Activity in DecorView. But it has not been drawn on the screen. This step will not be really executed until the handleResumeActivity of the ActivityThread.

The layout is parsed through the layoutinflator. The layout is mainly analyzed by the inflate method

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) 

The operations performed by the above inflate method mainly include the following steps:

Parsing the root tag of xml
If the root tag is a merge, call rinflete parsing to add all child views under the merge tag directly to the root tag
If it is not a merge, call createViewFromTag to resolve the element
Call rinfflate to resolve the child views in the template and add these child views to the template
Return the root view of the corresponding resolution through attachToRoot

The View is parsed through To determine whether it is a built-in View or a custom View, we can know why a custom View needs a full path when writing a layout file.

public final View createView(String name, String prefix, AttributeSet attrs)
       throws ClassNotFoundException, InflateException {
   // Get the constructor of the view from the cache
   Constructor<? extends View> constructor = sConstructorMap.get(name);
   if (constructor != null && !verifyClassLoader(constructor)) {
       constructor = null;
       sConstructorMap.remove(name);
   }
   Class<? extends View> clazz = null;

   try {
       Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
            // If there is no cache
       if (constructor == null) {
           // If the prefix is not empty, construct a complete View path and load the class
           clazz = mContext.getClassLoader().loadClass(
                   prefix != null ? (prefix + name) : name).asSubclass(View.class);
           // Gets the constructor of the class
           constructor = clazz.getConstructor(mConstructorSignature);
           constructor.setAccessible(true);
           // Add constructor to cache
           sConstructorMap.put(name, constructor);
       } else {
       }

       Object[] args = mConstructorArgs;
       args[1] = attrs;
            // Build View through reflection
       final View view = constructor.newInstance(args);
       if (view instanceof ViewStub) {
           // Use the same context when inflating ViewStub later.
           final ViewStub viewStub = (ViewStub) view;
           viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
       }
       return view;

   }
}

createView is relatively simple. It constructs the complete path of the View by judging the prefix, loads the class into the virtual machine, obtains the constructor and caches it, and then creates the View object through the constructor and returns it. At this time, we get the root View. Then call the rinflatchildren method to parse the child View, and finally call the rinflatchildren method:

void rInflate(XmlPullParser parser, View parent, Context context,
       AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

    // Get the depth of the tree and traverse through the depth first
   final int depth = parser.getDepth();
   int type;

   while (((type = parser.next()) != XmlPullParser.END_TAG ||
           parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

       if (type != XmlPullParser.START_TAG) {
           continue;
       }

       final String name = parser.getName();
       
       if (TAG_REQUEST_FOCUS.equals(name)) {
           parseRequestFocus(parser, parent);
       } else if (TAG_TAG.equals(name)) {// Parse tag tag
           parseViewTag(parser, parent, attrs);
       } else if (TAG_INCLUDE.equals(name)) {// Resolve include tag
           if (parser.getDepth() == 0) {
               throw new InflateException("<include /> cannot be the root element");
           }
           parseInclude(parser, context, parent, attrs);
       } else if (TAG_MERGE.equals(name)) {// Parse to the merge tag and report an error
           throw new InflateException("<merge /> must be the root element");
       } else {
            // Resolve to the normal child View and call createViewFromTag to obtain the View object
           final View view = createViewFromTag(parent, name, context, attrs);
           final ViewGroup viewGroup = (ViewGroup) parent;
           final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
           // recursive resolution 
           rInflateChildren(parser, view, attrs, true);
           // Add View to parent View
           viewGroup.addView(view, params);
       }
   }

   if (finishInflate) {
       parent.onFinishInflate();
   }
}

https://www.jianshu.com/p/c228e65ea1d9
https://www.jianshu.com/p/427c59a70da6
https://blog.csdn.net/qq_28261343/article/details/78817184

3. Display and drawing of View

In handleResumeActivity of ActivityThread, addView method of WindowManager will be called to add DecorView to WMS(WindowManagerService).
ActivityThread.java

    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
        // The onResume method of the activity is called
        r = performResumeActivity(token, clearHide, reason);

            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
//DecorView creation completed in setContentView
                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;
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
                if (a.mVisibleFromClient && !a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }

Add DecorView through the addView method of WindowManager. WindowManager is an interface, which is actually added through WindowManagerImpl.
WindowManagerImpl.java

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

WindowManagerGlobal.java

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ViewRootImpl root;
        View panelParentView = null;
···
            //!!! Create ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        try {
            root.setView(view, wparams, panelParentView);
        }
}

Add the view to WMS through the setView method of ViewRootImpl.

ViewRootImpl.java

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
     //  The scheduleTraversals() method will be called, and then measure layout draw will be executed? Make sure that the View has completed the measurement and drawing operations before it is added to the Window and displayed on the screen.
                  requestLayout();
        ···
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } 
}

requestLayout performs layout refresh and calls the mWindowSession method to add the View to WMS.
Finally, add the View by calling addToDisplay in WMS remotely through Binder.

The setView method of ViewRootImpl mainly completes: View rendering (requestLayout) and receiving touch screen events. A series of input pipes are set up to distribute touch events to DecorView.

ViewRootImpl.java

    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//mChoreographer. The main logic of postcallback is to send an asynchronous message to the Handler and then process it in mtraversalrunnable (which is a Runnable object)          
mChoreographer.postCallback(
                    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);
        ...
            performTraversals();//Execute the real view drawing process measure -- > layout -- > Draw
      ...
        }
    }

4, ViewRootImpl

ViewRoot is the manager of a ViewTree, not the root node of the ViewTree.
Strictly speaking, the root node of ViewTree has only DecorView.
ViewRoot "combines" DecorView and phonewindow (Window instance created by activity).

The three major processes of View drawing (measure, layout and draw) are completed through ViewRootImpl.
In the ActivityThread, after the Activity object is created, the DecorView will be added to the Window, and the ViewRootImpl object will be created. The ViewRootImpl object is used to establish the relationship between the Window object and the DecorView.

The drawing of View starts from the performTraversals of ViewRootImlp and passes through measure layout. The draw three methods finally draw a View. Measure measures the width and height of the View. Layout determines the View position, and draw is responsible for drawing.

The window holds the decorview. When the decorview is added to the window, a ViewRootImlp will be generated. When the ViewRootImlp calls performTraversals, it will call the measurement, layout and drawing of the top-level view, and then the top-level view will call the measurement, layout and drawing of its own sub layout one by one, so that a page will be displayed.

##5, attachWindow and detachWindow
attach process
When in activitythread Call WindowManager. in handleResumeActivity () method The addview () method is finally called

WindowManagerImpl.addView() -->
WindowManagerGlobal.addView()
ViewRootImpl.setView()
ViewRootImpl.performTraversals()
host.dispatchAttachedToWindow(mAttachInfo, 0)
//ViewGroup and View have different implementations, and ViewGroup is distributed to child views in turn

detach process

ActivityThread.handleDestroyActivity() -->
WindowManager.removeViewImmediate() -->
WindowManagerGlobal.removeViewLocked()Method ->
ViewRootImpl.die() --> doDie() -->
ViewRootImpl.dispatchDetachedFromWindow()

1. The onAttachedToWindow method is called when the activity resumes, that is, when the window corresponding to the act is added, and each view will only be called once. The parent view will be called first, regardless of the visibility state of the view. It is suitable for view specific initialization operations;
2. The onDetachedFromWindow method is called when the activity is destroyed, that is, when the window corresponding to the act is deleted, and each view will only be called once. After the parent view is called, it will be called regardless of the visibility status of the view, which is suitable for the final cleaning operation;
3. These conclusions also explain why there is a window in the method name. Some people may wonder why it is not called onAttachedToActivity/onDetachedFromActivity, because it is more than an Activity in Android. What is said here also applies to Dialog/Toast. Window is just a virtual concept, which is abstracted from Android, and the final operation entity is View, This also explains why the previous WindowManager interface is derived from the ViewManager interface, because in the final analysis, the cornerstone of all is the operation of View.

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

##6, Token and other windows
1. What is a Token?
The type is IBinder, which is a Binder object.
There are two types of Token s:
token pointing to Window: it mainly realizes the communication between WmS and the process where the application is located.
token pointing to ActivityRecord: it mainly realizes the communication between WMS and AMS.

2. Usage scenario of Token?
When creating an Activity, AMS needs to find the corresponding ActivityRecord according to the Token.
The first parameter of showAtLocation of Popupwindow needs to be passed into the View, which is used to obtain the Token.

3. What is WindowSession
ViewRootImpl will be created in addView of WindowManager, and WindowSession will be obtained internally through WMS
The type of WindowSession is IWindowSession, which itself is a Binder object, and the real implementation class is Session.

WindowManager. There are three window type s on layoutparams, corresponding to:
**Application window: * * type value in first_ APPLICATION_ WINDOW LAST_ APPLICATION_ Windows must set the token to the token of the Activity.
eg: Activity window, Dialog

Sub window: the type value is in first_ SUB_ WINDOW ~ LAST_ SUB_ Windows subwindows is associated with the top-level window, and the token needs to be set to the token of the host window to which it is attached.
Eg: popupwindow (if you want to attach to an Activity, you need to set the token to the Activity's token)

System window: the type value is in first_ SYSTEM_ WINDOW ~ LAST_ SYSTEM_ Windows systemWindows cannot be used for application programs. Special permissions are required when using it. It can only be used with specific system functions.
eg: Toast, input method, etc.

A token is a token used to represent a window. Only qualified tokens can be added to the application by WMS.
https://www.jianshu.com/p/bac61386d9bf

reference resources

https://www.jianshu.com/p/c228e65ea1d9
https://www.jianshu.com/p/427c59a70da6
https://blog.csdn.net/qq_28261343/article/details/78817184
https://www.jianshu.com/p/9da7bfe18374
https://www.jianshu.com/p/e7b6fa788ae6
https://www.jianshu.com/p/bac61386d9bf

Keywords: Android Design Pattern

Added by MiCR0 on Thu, 23 Dec 2021 02:47:25 +0200