Third source code analysis, in View of the drawing process since the start of the Activity source code analysis, because the View drawing involved too much content, therefore this article a little long, maybe a lot of friend see some will lose patience, but I believe that if you can read all, also be a bit more or less comprehension, if you can see are the complete and the wrong place, Please leave a message, thank you!! (Note: Please correct me if there is anything wrong.)

View drawing basic concepts

When it comes to View drawing, we need to understand a few concepts first:

  • Window: is an abstract class with the function of Window management. The implementation class is PhoneWindow. There are three types of Windows: application layer Window, child Window and system Window. In the application layer, a Window corresponds to an Activity, for example, while a child Window must be attached to a parent Window, such as Dialog or PopupWindow. System Windows include Toast, System Alert, etc. The corresponding interval of its level is as follows:

    Application layer Window: 1-99 Sub-Window: 1000-1999 System Window: 2000-2999Copy the code

    Of course, the higher the hierarchy, the higher the ranking.

  • PhoneWindow class: The PhoneWindow class is a concrete implementation of the Android Windows provided by the Framework. When we call the setContentView() method to set up the Activity’s user interface, we are actually setting up the associated PhoneWindow ViewTree(the control tree that the window hosts). We can also customize the appearance of an Activity associated with a PhoneWindow using the requestWindowFeature() method of the Activity class, What this method actually does is store the requested window appearance features into the mFeatures member of the PhoneWindow, which draws the specific appearance based on the value of mFeatures when the appearance template is generated during the window drawing phase. This class inherits from the Window class and is a concrete implementation of the Window class, that is, we can draw the Window concretely through this class. Furthermore, the class internally references a DecorView object, which is the root View of all application Windows (Activity interfaces). In short, the PhoneWindow class wraps a FrameLayout class, a DecorView object, as the root View of the application window and provides a common set of interfaces for manipulating Windows. It is the most basic window system in Android. Each Activity creates a PhoneWindow object, which is the interface between the Activity and the View system.

  • DecorView class: the root container of an application window, which is essentially a FrameLayout. DecorView has a single child, which is a vertical LinearLayout containing two child elements, the TitleView (container for ActionBar) and the ContentView (container for window content). For the ContentView, it is a FrameLayout (Android.r.D.C. Tent), and the setContentView we normally use is the child View that sets it.

  • ViewRootImpl class: ViewRootImpl is the class that actually manages all views in the Window. The number of ViewRootImpl classes in each Activity depends on the number of calls to mWindowManager.addView.

Note: The Activity is managed by the central controller ActivityManagerService. Similar to activities, the content of the UI layer is managed by another controller, WindowManagerService (WMS).

The relationship between Windows, WindowManager, and WindowManagerService

Window

Window is the concept of a Window in Android development. It is an abstract class. In PhoneWindow, there is a top-level view-decorView, inherited from FrameLayout, which can be obtained by getDecorView(). When we call the Activity’s setContentView, we’re actually going to call the Window’s setContentView, and when we call the Activity’s findViewById, we’re actually going to call the Window’s findViewById, This also indirectly indicates that Window is the direct manager of the View. But Window does not really exist, it represents more of an abstract set of functions. View is the presentation form of View in Android. What is drawn on the screen is a View, not Window, but a View cannot exist alone, it must be attached to the abstract concept of Window. Android relies on Windows to provide views for activities, Dialog, Toast, PopupWindow, StatusBarWindow (system status bar), input Window, etc. A view like a Dialog corresponds to a Window.

WindowManager(actually WindowManagerImpl)

WindowManager is an interface, in which the common methods are: Add View, update View and delete View, WindowManager inherits from ViewManager, these three methods are defined in ViewManager. These methods pass in a View argument, not a Window, which means that Windows Manager manages the View in the Window, so if we use Windows Manager to manipulate the View in the Window, we are manipulating the View in the Window. WindowManager controls Window Window objects, which are containers used to hold view objects. Window objects are always supported by Surface objects. WindowManager oversees the life cycle, input and focus events, screen orientation, transitions, animation, positioning, deformation, Z-axis order, and many other aspects of Windows. WindowManager sends all window metadata to SurfaceFlinger so that SurfaceFlinger can use this data to compose the Surface on the screen.

WindowManager#addView initializes ViewRootImpl, and ViewRootImpl setView initiates the View drawing process with requestLayout(). Then mWindowssession #addToDisplay communicates with WMS across the process with Binder, requesting the View on the window, which is displayed on the screen. This mWindowSession is an IWindowSession.AIDL interface type used for inter-process communication. Within WMS, a separate Session is reserved for each application request, also implementing the IWindowSession interface. The application communicates with the WMS through this Session.

MWindowSession is the local proxy for WMS first obtained via the WindowManagerGlobal singleton class getWindowSession() : IWindowManager windowManager = getWindowManagerService(); — > at last, by local agent openSession WMS for the Session: windowManager. OpenSession

WindowManagerService

WindowManagerService is a system-level service started by SystemService. It implements the iWindowManager. AIDL interface. Its main functions are divided into window management and input event relay. The above mwindowssession #addToDisplay actually calls mservice. addWindow, i.e., WMS#addWindow to add the display. The main thing addWindow does is to check the window permission first, because the system window needs to declare the permission, and then proofread the window according to the relevant Display information and window information, and then obtain the corresponding WindowToken, and then check the validity of the window according to different window types. If all the above steps are followed, a WindowState object is created for the window to maintain the WindowState and adjust the WindowState as appropriate, and finally communicate with SurfaceFlinger via the WindowState attach method. So SurfaceFlinger can use these Window information to synthesize surfaces and render output to the display device.

// Note: the mWindow in this method is not really Window, but iWindow.stub, which is the callback used for Bindler cross-process communication. By events, I mean buttons, touch screens, etc. mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel);Copy the code

SetContentView initializes the layout

View drawing starts after the Activity is created by executing the setContentView method:

import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main); }}Copy the code

The MainActivity inherits from the Activity class, which is a parent of activities such as AppCompatActivity and FragmentActivity, so directly inheriting from the Activity makes it easier to analyze.

  1. The Activity class:
    public void setContentView(@LayoutRes int layoutResID) {
        // getWindow()获取的是Window
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
    
    public Window getWindow() {
        return mWindow;
    }
Copy the code

GetWindow () returns mWindow directly, so when is mWindow assigned? The handleLaunchActivity method in the ActivityThread class is called at the end of an Activity start. The handleLaunchActivity method calls the performLaunchActivity method to create and return an Activity.

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { handleConfigurationChanged(null, null); / / initialization WindowManagerService, mainly access to WindowManagerService proxy objects WindowManagerGlobal. The initialize (); Activity a = performLaunchActivity(r, customIntent);if(a ! = null) { r.createdConfig = new Configuration(mConfiguration); // Call onResume() handleResumeActivity(r.token,false, r.isForward, ! r.activity.mFinished && ! r.startsNotResumed); . }... } private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ... ContextImpl appContext = createBaseContextForActivity(r); Activity activity = null; try { java.lang.ClassLoader cl = appContext.getClassLoader(); / / create the Activity Activity = mInstrumentation. NewActivity (cl, component getClassName (), r.i ntent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl); r.intent.prepareToEnterProcess();if(r.state ! = null) { r.state.setClassLoader(cl); } } catch (Exception e) {if(! mInstrumentation.onException(activity, e)) { throw new RuntimeException("Unable to instantiate activity " + component+ ":"+ e.toString(), e); Try {}} / / create the Application object Application app = r.p ackageInfo. MakeApplication (false, mInstrumentation); .if(activity ! = null) { ... Attach (appContext, this, getInstrumentation(), r.toy, R.i.dent, app, R.I.ntent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.configCallback); . // Call back the Activity's onCreate() methodif (r.isPersistable()) {
                   mInstrumentation.callActivityOnCreate(activity, 
                       r.state, r.persistentState);
               } else{ mInstrumentation.callActivityOnCreate(activity, r.state); }... } } catch (SuperNotCalledException e) { throw e; .return activity;
   }
Copy the code

The performLaunchActivity method not only creates the Activity but also calls the attahc method, where mWindow is assigned.

// Attach method of the Activity class:  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); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); . // Set WindowManagerImpl object 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()); } // Get WindowManagerImpl object mWindowManager = mwindow.getwinDowManager (); mCurrentConfig = config; mWindow.setColorMode(info.colorMode); }Copy the code

Back to the setContentView method, where getWindow() also calls the setContentView method, and the real class returned by getWindow() is PhoneWindow, So we’re calling the setContentView method in the PhoneWindow class.

  1. PhoneWindow setContentView method:
/** * What is Transition? * The Activity and Fragment Transitions for Android 5.0 build on a new Android feature called Transitions. * The Transition framework, introduced in 4.4, provides a handy API for animating between UI states. * The framework is based primarily on two concepts: Scenes and Transitions. Scenes define the current STATE of the UI, while Transitions define the progression of animations from scene to scene. */ @Override public voidsetContentView(int layoutResID) {// contentParent is the ContentView part of mDecor(DecorView)if(mContentParent == null) {// Initialize DecoView installDecor(); }else if(! hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } // If there is a feature, it needs to be set dynamically, default no (animation effect)ifNewScene = scene.getSceneForLayout (mContentParent, FEATURE_CONTENT_TRANSITIONS) {  layoutResID, getContext()); transitionTo(newScene); }else{// mLayOutinflater.inflate (layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback();if(cb ! = null && ! isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet =true;
    }
    
    private void installDecor() {
        mForceDecorInstall = false;
        if(mDecor == null) {// mDecor = generateDecor(-1); . }else {
            mDecor.setWindow(this);
        }
        if(mContentParent == null) { mContentParent = generateLayout(mDecor); . } } protected DecorView generateDecor(int featureId) { Context context;if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext().getResources());
                if(mTheme ! = -1) { context.setTheme(mTheme); }}}else {
            context = getContext();
        }
        // new DecorView
        returnnew DecorView(context, featureId, this, getAttributes()); } protected ViewGroup generateLayout(DecorView decor) { ... // android.r.i content ContentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); int layoutResource; . mDecor.startChanging(); // Set different layout files according to different features mDecor. OnResourcesLoaded (mLayoutInflater, layoutResource); .return contentParent;
    }
Copy the code

HasFeature (FEATURE_CONTENT_TRANSITIONS) is used to determine whether to enable the Transition Api. Transition is described above. To learn more about Transition and how to use it, click the link in the description.

www.jcodecraeer.com/a/anzhuokai…

The DecoView(root layout) is initialized and the layout is filled with a call to the mlayoutinflater.inflate () method, which uses an Xml parser to parse the Xml file we pass in and save it to the mContentParent. Xml parsing of the specific source code will not be analyzed, interested partners to view it. At this point, we have analyzed the entire execution flow of the setContentView(), so we have completed the creation and setting of the Activity’s ContentView.

The onResume interface shows the drawn association Window and View wrootimpl

  1. The drawing of the View is related to the launching of the Activity, at which point the Activity has gone through the onCreate callback and the performLaunchActivity method in the Activity flow, Further down you go to the handleResumeActivity method and call performResumeActivity
/ / ActivityThread class:  final void handleResumeActivity(IBinder token,boolean clearHide , boolean isForward, boolean reallyResume, int seq, String reason) { ... // Call onStart and onResume methods r = performResumeActivity(token, clearHide, reason);if (r != null) {  
            final Activity a = r.activity;  
            boolean willBeVisible = !a.mStartedActivity;  
            ...  
            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(a.mVisibleFromClient) { // 1. The main variable is a.mwindowAdded =true; // 2.WindowManager and DecorView associate wm.addView(decor, L); }...if(! r.activity.mFinished && willBeVisible && r.activity.mDecor ! = null && ! r.hideForNow) { ... mNumVisibleActivities++;if(r.activity.mVisibleFromClient) { // 3. Display DecorView state Richard armitage ctivity. MakeVisible (); }}... }}Copy the code

WindowManager is an interface whose implementation class is the WindowManagerImpl class, which in turn hands the related logic to The WindowManagerGlobal. WindowManagerGlobal is a singleton class that has only one instance in the process, and it is its internal addView method that ultimately creates our core class ViewRootImpl. Set the current Activity member variable mWindowAdded to true to indicate that Window has been added.

/ / WindowManagerImpl class:  @Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); } // WindowManagerGlobal class: Public void addView(View View, ViewGroup.LayoutParams params, Display Display, Window parentWindow) {// 2 public void addView(View View, ViewGroup.LayoutParams params, Display Display, Window parentWindow) {... ViewRootImpl root; View panelParentView = null; synchronized (mLock) { ... ViewRootImpl root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); // mRoots is a collection of ViewRootImpl, that is, the number of ViewRootImpl addView is the number of ViewRootImpl mroots.add (root); mParams.add(wparams); Try {// Execute ViewRootImplsetRoot. setView(View, wparams, panelParentView); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up.if (index >= 0) {
                    removeViewLocked(index, true); } throw e; // Activity class: void at // 3makeVisible() {// the first code, mWindowAdded istrue
        if(! mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded =true; } // mDecor set to show mDecor. SetVisibility (view.visible); }Copy the code

This process creates a ViewRootImpl and passes in the DecoView created earlier as a parameter. After the DecoView events are managed by the ViewRootImpl, for example, adding a View to the DecoView, deleting a View. ViewRootImpl implements the ViewParent interface, one of the most common methods of which is requestLayout().

// ViewRootImpl class: public ViewRootImpl(Context Context, Display Display) {... // Get an IWindowSession instance from WindowManagerGlobal. It is the agent ViewRootImpl and WMS communicate mWindowSession = WindowManagerGlobal. GetWindowSession (); . mWindow = new W(this); // Create a W local Binder object to notify application processes of WMS events... mChoreographer = Choreographer.getInstance(); //Choreographer object for unified scheduling window drawing... } // WindowManagerGlobal class: public static IWindowSessiongetWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if(sWindowSession == null) { try { InputMethodManager imm = InputMethodManager.getInstance(); // Obtain WindowManagerService Binder windowManager IWindowManager windowManager = getWindowManagerService(); // Call openSession with Binder agent windowManager // get instance sWindowSession: represents active client session. Each process typically has a Session object that interacts with windowManager. / / by openSession function to establish a communication session with WMS, behind continue to elaborate sWindowSession = windowManager. OpenSession (new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(floatscale) { ValueAnimator.setDurationScale(scale); } }, imm.getClient(), imm.getInputContext()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); }}returnsWindowSession; }}Copy the code

This code introduces two concepts: mWindowSession and mWindow, respectively IWindowSession and IWindow.

The onResume screen displays IWindowSession and IWindow drawn

  1. After associating Windows with ViewRootImpl, ViewRootImpl immediately executes setView and begins the process of View drawing.
// ViewRootImpl class: public voidsetView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if(mView == null) { mView = view; . requestLayout(); . try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes =true; collectViewAttributes(); // Call the IWindowSession addToDisplay method, The first parameter is the IWindow res = mWindowSession. AddToDisplay (mWindow mSeq, mWindowAttributes, getHostVisibility (), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); } catch (RemoteException e) { mAdded =false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mInputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }
            }
        }
    }
Copy the code

The setView method of the ViewRootImpl class does three things:

  • Save the view argument passed in as mView, which only calls to the PhoneWindow DecorView
  • RequestLayout () is executed to start drawing;
  • Call the addToDisplay function of IWindowSession, which is a cross-process Binder communication with the first argument mWindow, which is of type W derived from iWindow.stub.

As you can see from the above code, ViewRoot interacts with the WMS of the remote SystemServer process.

  • When ViewRootImpl is initialized, Windows ManagerGlobal calls getWindowSession and IWindowManager calls openSession to get the IWindowSession object.
  • The setView method calls the addToDisplay function of IWindowSession, passing in an IWindow object as an argument.

Take a look at the openSession method:

/ / WindowManagerService class:  @Override public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client, IInputContext inputContext) { ... // Return a Session object that supports Binder communication and belongs to the Bn side. // Bn means Binder Native end, Bp is Binder Proxy end // Both ends implement the same interface, but the Proxy end just sends a Binder Transaction through the Binder IPC, // The Native end actually does things, And return the result. Session session = new Session(this, callback, client, inputContext);returnsession; } // Session class:  @Override public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) {// Call the addWindow method of WMSreturnmService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outStableInsets, outOutsets, outInputChannel); } // WindowManagerService class:  public int addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) { ...... synchronized(mWindowMap) { ... / / create WindowToken WindowToken token = displayContent. GetWindowToken (hasParent? parentWindow.mAttrs.token : attrs.token); . // Call the attach method win.attach(); }...returnres; } // WindowToken class: voidattach() {
        if (localLOGV) Slog.v(TAG, "Attaching " + this + " token="+ mToken); mSession.windowAddedLocked(mAttrs.packageName); } // Session class: void windowAddedLocked(String packageName) {...if(mSurfaceSession == null) { ... MSurfaceSession = new SurfaceSession(); . } mNumWindow++; }Copy the code

MSurfaceSession (IWindowSession, Windows, Windows, Windows, Windows, Windows, Windows, Windows, Windows)

  • ViewRootImpl communicates with WMS across processes via IWindowSession, which is defined in the iWindowSession.aidl file
  • Inside ViewRootImpl is a W inner class, which is also a binder-based communication class. W is the Bn side of IWindow for request responses. Let’s look at the methods in class W:
    static class W extends IWindow.Stub {
        private final WeakReference<ViewRootImpl> mViewAncestor;
        private final IWindowSession mWindowSession;

        W(ViewRootImpl viewAncestor) {
            mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
            mWindowSession = viewAncestor.mWindowSession;
        }

        @Override
        public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
                Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
                MergedConfiguration mergedConfiguration, Rect backDropFrame, boolean forceLayout,
                boolean alwaysConsumeNavBar, int displayId) {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if(viewAncestor ! = null) { viewAncestor.dispatchResized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets, outsets, reportDraw, mergedConfiguration, backDropFrame, forceLayout, alwaysConsumeNavBar, displayId); } } @Override public void moved(int newX, int newY) { final ViewRootImpl viewAncestor = mViewAncestor.get();if(viewAncestor ! = null) { viewAncestor.dispatchMoved(newX, newY); } } @Override public void dispatchAppVisibility(boolean visible) { final ViewRootImpl viewAncestor = mViewAncestor.get();if(viewAncestor ! = null) { viewAncestor.dispatchAppVisibility(visible); } } @Override public voiddispatchGetNewSurface() {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if(viewAncestor ! = null) { viewAncestor.dispatchGetNewSurface(); } } @Override public void windowFocusChanged(boolean hasFocus, booleaninTouchMode) {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if(viewAncestor ! = null) { viewAncestor.windowFocusChanged(hasFocus,inTouchMode);
            }
        }

        private static int checkCallingPermission(String permission) {
            try {
                return ActivityManager.getService().checkPermission(
                        permission, Binder.getCallingPid(), Binder.getCallingUid());
            } catch (RemoteException e) {
                returnPackageManager.PERMISSION_DENIED; }}... /* Drag/drop */ @Override public void dispatchDragEvent(DragEvent event) { final ViewRootImpl viewAncestor = mViewAncestor.get();if(viewAncestor ! = null) { viewAncestor.dispatchDragEvent(event); } } @Override public void updatePointerIcon(float x, float y) {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if(viewAncestor ! = null) { viewAncestor.updatePointerIcon(x, y); }}... }Copy the code

You can see that W has a weak reference to ViewRootImpl inside it, and as you can see from the inherited methods in W, W, or IWindow, will notify ViewRootImpl of some event. By events, I mean buttons, touch screens, etc. How is a button distributed? The general process is as follows:

  • The SystemServer process where the WMS resides received the keystroke event. Procedure
  • WMS finds the IWindow object corresponding to the process whose UI is at the top of the screen, which is a BP-side object.
  • Call the dispatchKey of the IWindow object, the Bn end of the IWindow object is in the ViewRootImpl, and the ViewRootImpl finds the View that actually handles the event based on the location of the View inside, Finally, the dispatchKey method is called to complete keystroke processing.

At this point, you should probably understand the use of IWindowSession and IWindow. To summarize:

  • IWindowSession: Used to communicate with WMS. Each App process establishes an IWindowSession with WMS for communication.
  • IWindow: Used to call back WMS events. IWindow is used by WMS for event notification. Whenever some event occurs, WMS tells an IWindow about the event, and the IWindow calls back to a View in the ViewRootImpl in response to the event.

The onResume screen shows the synchronization barrier drawn -VSYNC synchronization

RequestLayout () : Surface and SurfaceSession (SurfaceSession)

// ViewRootImpl class: @override public voidrequestLayout() {
        if(! MHandlingLayoutInLayoutRequest) {/ / check whether in the UI thread to update the UI checkThread (); mLayoutRequested =true; / / traverse scheduleTraversals (); } } voidcheckThread() {
        if(mThread ! = Thread. CurrentThread ()) {/ / if not the UI Thread has an exception is thrown throw new CalledFromWrongThreadException ("Only the original thread that created a view hierarchy can touch its views.");
        }
    }   
    
    void scheduleTraversals() {
        if(! mTraversalScheduled) { mTraversalScheduled =true; // postSyncBarrier method, called synchronization barrier mTraversalBarrier = mhandler.getlooper ().getQueue().postsyncbarrier (); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    } 
    
Copy the code

The MessageQueue#postSyncBarrier method, known as a synchronization barrier, is here to keep the main thread UI from being drawn. A synchronization barrier can be understood as adding a special MSG to a MessageQueue, which is used as a token. Until the token is removed, other (non-async) messages following it in the current MessageQueue queue will not be processed by the handler. So the code here continues to execute postCallback, and let’s see what’s going on in this method:

// Choreographer class: Choreographer is the class that takes the Vsync signal and controls the App thread (the main thread) to complete the image drawing. public void postCallback(int callbackType, Runnable action, Object token) {// The passed argument delayMillis is 0 postCallbackDelayed(callbackType, Action, token, 0); } public void postCallbackDelayed(int callbackType, Runnable action, Object token, long delayMillis) { ... postCallbackDelayedInternal(callbackType, action, token, delayMillis); } private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) { ... Synchronized (mLock) {final long now = systemclock. uptimeMillis(); synchronized (mLock) {final long now = systemclock. uptimeMillis(); DelayMillis == 0 final long dueTime = now + delayMillis; mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);if (dueTime <= now) { //  trueScheduleFrameLocked (now); }elseMessage MSG = mhandler. obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }
    
    private void scheduleFrameLocked(long now) {
        if(! mFrameScheduled) { mFrameScheduled =true; // Whether to allow vertical synchronization of animation and drawing. Default is yestrue
            if (USE_VSYNC) {
                ...
                // If running on the Looper thread, thenschedule the vsync immediately, // Otherwise post a message to schedule the vsync from the UI thread // as soon as possible. That's the main thread, and this is basically the main thread when the View drawsif (isRunningOnLooperThreadLocked()) { // trueScheduleVsyncLocked (); }else{// switch to the main thread and schedule vsync Message MSG = mhandler. obtainMessage(MSG_DO_SCHEDULE_VSYNC); msg.setAsynchronous(true); mHandler.sendMessageAtFrontOfQueue(msg); }}else{// If there is no synchronization with VSYNC, Final Long nextFrameTime = math. Max (mLastFrameTimeNanos/timeutils.nanOS_per_ms + sFrameDelay, now);if (DEBUG) {
                    Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
                }
                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, nextFrameTime);
            }
        }
    }
    private void scheduleVsyncLocked() { mDisplayEventReceiver.scheduleVsync(); // DisplayEventReceiver class: public voidscheduleVsync() {
        if (mReceiverPtr == 0) {
            ...
        } else{ nativeScheduleVsync(mReceiverPtr); }}Copy the code

Now, with the source code to the mDisplayEventReceiver scheduleVsync (); Method, and when mDisplayEventReceiver is initialized, you can see from the source that it was new when the Choreographer class was initialized, Again through the source can be seen is the ViewRootImpl class initialization is new out.

public ViewRootImpl(Context context, Display display) { ... mChoreographer = Choreographer.getInstance(); . } // Choreographer class: Choreographer is the class that takes Vsync signals and controls the App thread (the main thread) to complete the image drawing. // One instance per thread private static final ThreadLocal<Choreographer> sThreadInstance = new ThreadLocal<Choreographer>() { @Override protected ChoreographerinitialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            returnnew Choreographer(looper); }}; private Choreographer(Looper looper, int vsyncSource) { mLooper = looper; MHandler = new FrameHandler(looper); mHandler = new FrameHandler(looper); // Create VSYNC's signal receiver USE_VSYNCtruemDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null; // Initialize the last frame rendering time mLastFrameTimeNanos = long.min_value; GetRefreshRate = (long)(1000000000 / getRefreshRate()); MCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];for(int i = 0; i <= CALLBACK_LAST; i++) { mCallbackQueues[i] = new CallbackQueue(); }} // DisplayEventReceiver class: Subclasses FrameDisplayEventReceiver public DisplayEventReceiver (which stars, int vsyncSource) {... mMessageQueue = looper.getQueue(); MReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue, vsyncSource); mCloseGuard.open("dispose"); } // JNI--nativeInit (android_view_DisplayEventReceiver. CPP) jobject receiverWeak, jobject messageQueueObj) { sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj); . sp<NativeDisplayEventReceiver> receiver = new NativeDisplayEventReceiver(env, receiverWeak, messageQueue); status_t status = receiver->initialize(); . receiver->incStrong(gDisplayEventReceiverClassInfo.clazz); / / in c + + NativeDisplayEventReceiver address back to the Java object layer / / in this way will the Java objects associated with Native layer objects together.return reinterpret_cast<jlong>(receiver.get());
    }
Copy the code

Now, let’s go back to mDisplayEventReceiver scheduleVsync (); Method:

// DisplayEventReceiver class: public voidscheduleVsync() {/ / from the above analysis shows that the mReceiverPtr holds / / c + + object NativeDisplayEventReceiver address / / the name will know that this object is: native display receiver (role: request VSYNC synchronization)if (mReceiverPtr == 0) {
            ...
        } else{/ / / / this method for native method was introduced into NativeDisplayEventReceiver object address, request VSYNC synchronization nativeScheduleVsync (mReceiverPtr); }}Copy the code

VSYNC synchronization: its main function is to make the graphics card operation and display refresh rate consistent to stabilize the output picture quality. VSYNC how to use the synchronization, specific how, is not the focus of this article, interested partners search it. . We are performing mChoreographer postCallback method into the JNI, so its will eventually mTraversalRunnable callback to the parameters (TraversalRunnable) within the class methods. Want to know the specific VSYNC friend might as well take a look at: dandanlove.com/2018/04/13/…

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false; Mhandler.getlooper ().getQueue().removesyncBarrier (mTraversalBarrier);if (mProfile) {
                Debug.startMethodTracing("ViewAncestor"); } performTraversals(); . }}Copy the code

TraversalRunnable class, doTraversal(); In doTraversal, synchronization is removed, followed by performing performTraversals.

Drawing process Trilogy

  1. Traversals perform the familiar Measure, Layout, and Draw traversals:
    private void performTraversals() { final View host = mView; // This is a DecorView... boolean newSurface =false; . RelayoutResult = relayoutWindow(Params, viewVisibility, insetsPending); . .if(mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params ! = null || mForceNextWindowRelayout) {if(! mStopped || mReportNextDraw) { boolean focusChangedDueToTouchMode = ensureTouchModeLocally( (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) ! = 0);if(focusChangedDueToTouchMode || mWidth ! = host.getMeasuredWidth() || mHeight ! = host. GetMeasuredHeight () | | contentInsetsChanged | | updatedConfiguration) {/ / retrieve the root View MeasureSpec method int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); . PerformMeasure (childWidthMeasureSpec, childHeightMeasureSpec); . int width = host.getMeasuredWidth(); int height = host.getMeasuredHeight(); boolean measureAgain =false;

                    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;
                    }

                    if (measureAgain) {
                        if (DEBUG_LAYOUT) Log.v(mTag,
                                "And hey let's measure once more: width=" + width
                                + " height="+ height); PerformMeasure (childWidthMeasureSpec, childHeightMeasureSpec); } layoutRequested =true; }}}if(didLayout) {// 27 source code 2212 performLayout(lp, mWidth, mHeight); . }... // Decide if newSurface istrueIf performDraw cannot be called, then re-scheduleTraversalsif(! hadSurface) {if (mSurface.isValid()) {
                // If we are creating a new surface, then we need to
                // completely redraw it.  Also, when we get to the
                // point of drawing it we will hold off and schedule
                // a new traversal instead.  This is so we can tell the
                // window manager about all of the windows being displayed
                // before actually drawing them, so it can display then
                // all at once.
                newSurface = true; . }}...if(! cancelDraw && ! NewSurface) {// 27 source code 2359 line performDraw(); }else {
            if(isViewVisible) {// Execute scheduleTraversals again, that is, execute performTraversals again scheduleTraversals(); }else if(mPendingTransitions ! = null && mPendingTransitions.size() > 0) {for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).endChangingAnimations();
                }
                mPendingTransitions.clear();
            }
        }

        mIsInTraversal = false;
    }

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }
    
    private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {
        ...
        int relayoutResult = mWindowSession.relayout(
                mWindow, mSeq, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f),
                viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
                mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets, mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingMergedConfiguration, mSurface); // Pass the mSurface argument to...return relayoutResult;
    }

Copy the code

PerformTraversals () calls relayoutWindow(), and relayoutWindow() calls IWindowSession relayout() and passes in mSurface. We’ll leave it to the end.

Draw the Measure of the process trilogy

Here’s an important class MeasureSpec: In the Measure process, the system converts the View’s LayoutParams into a Corresponding MeasureSpec based on the rules imposed by the parent container, and then determines the View’s measurement width and height based on this MeasureSpec in the onMeasure method. The upper two bits are used to indicate the mode SpecMode and the lower 30 bits are used to indicate the size SpecSize. There are three types of SpecMode:

  1. UNSPECIFIED: The parent container is UNSPECIFIED, and the child views are as large as they wish. The child views are used internally, such as ScrollView.
  2. EXACTLY: EXACTLY, the parent determines the exact size of the child View, when set to a certain value: width=20dp, height=30dp, or match_parent.
  3. AT_MOST: maximum mode. The size cannot be larger than SpecSize, that is, there is an upper limit on the size of the child View, corresponding to warp_Content in LayoutParams. Take a look at some of the source code:
public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; /** * UNSPECIFIED mode: * The parent View has no restrictions on its child views, and the child views are as large as they need to be. */ public static final int UNSPECIFIED = 0 << MODE_SHIFT; /** * EXACTYLY mode: The final View size * is the value specified by SpecSize when the parent View has EXACTYLY measured the exact size required by the child Viwe. */ public static final int EXACTLY = 1 << MODE_SHIFT; /** * AT_MOST mode: * The final size of the child View is the SpecSize value specified by the parent View, and the child View cannot be larger than this value. Public static final int AT_MOST = 2 << MODE_SHIFT; // Size and mode are packed into a 32-bit int value // The higher 2 bits represent the SpecMode, the lower 30 bits represent the SpecSize, Public static int makeMeasureSpec(int size, int mode) {public static int makeMeasureSpec(int size, int mode) {if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return(size & ~MODE_MASK) | (mode & MODE_MASK); Public static int MeasureSpec (MeasureSpec) {MeasureSpec (MeasureSpec) {return(measureSpec & MODE_MASK); } public static int MeasureSpec (int MeasureSpec) {return(measureSpec & ~MODE_MASK); } / /... }Copy the code

As you can see, the class is pretty clean. For each View, including the DecorView, there is a MeasureSpec that holds the dimensions of the View. In the View measurement process, makeMeasureSpec is used to package size and Mode into a 32-bit int value. In other processes, getMode or getSize is used to get the mode and width.

Let’s go back to the implementation of viewrotimpl #getRootMeasureSpec above: Set MeasureSpec according to different modes, if layoutparams.match_parent mode, the size of the window, WRAP_CONTENT mode, the size is indepth, but cannot exceed the size of the window, etc. For a DecorView, it is already a top-level view and has no parent, so the DecorView’s MeasureSpec uses the screen window’s windowSize and the DecorView’s LayoutParams to validate MeasureSpec’s. So now you have a DecorView’s MeasureSpec, which represents the size and dimensions of the root View, and in the measure process you measure each child View layer by layer against the MeasureSpec of the root View.

Next, analyze the logic of measurement:

Private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); Mview. measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); }} Public final void measure(int widthMeasureSpec, int heightMeasureSpec) {... // Call onMeasure(widthMeasureSpec, heightMeasureSpec); . Void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } // Both EXACTLY and AT_MOST are set according to the measured results. public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) {case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }
    
    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if(optical ! = isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int opticalWidth = insets.left + insets.right; int opticalHeight = insets.top + insets.bottom; measuredWidth += optical ? opticalWidth : -opticalWidth; measuredHeight += optical ? opticalHeight : -opticalHeight; }setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }
    private void setMeasuredDimensionRaw(int measuredWidth, Int measuredHeight) {// Save to the member variables mMeasuredWidth and mMeasuredHeight: mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }Copy the code

As you trace the source code, you end up saving the measurements in your own mMeasuredWidth and mMeasuredHeight member variables. The measurement process of ViewGroup is the same as this, except that it needs to measure subviews in onMeasure. Let’s take a look at FrameLayout’s onMeasure:

@Override protected void onMeasure(int widthMeasureSpec, Int heightMeasureSpec) {getChildCount = getChildCount(); MeasureMatchParent = measureMatchParent = measureMatchParent = measureMatchParent = measureMatchParent = measureMatchParent = measureMatchParent Not be restricted by child View final Boolean measureMatchParentChildren = MeasureSpec. GetMode (widthMeasureSpec)! = MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) ! = MeasureSpec.EXACTLY; / / clear need to measure a collection of child view match_parent width or height mMatchParentChildren. The clear (); int maxHeight = 0; int maxWidth = 0; int childState = 0; // Go through the sub-view to get the maximum width and heightfor (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if(mMeasureAllChildren || child.getVisibility() ! = GONE) {// Measure child, MeasureChildWithMargins (Child, widthMeasureSpec, 0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); // Find the largest piece in the View, Because if FrameLayout is wrap_content then its size depends on the largest subview 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()); // If FrameLayout is wrap_content, add a child View with the width or height of match_parent to mMatchParentChildren. // Because the final measured size of this subview affects the final measured size of FrameLayoutif (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

        // Account forPadding too / / child View the largest wide high and its own padding values will affect the size of the final maxWidth + = getPaddingLeftWithForeground () + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // Check against our minimum height and width GetSuggestedMinimumHeight/Width there are instructions below maxHeight = math.h Max (maxHeight, getSuggestedMinimumHeight ()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); final Drawable drawable = getForeground();if(drawable ! = null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } // Re-set the measurement result to FrameLayout (save the measurement result)setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); / / read need to measure the number of child views (View) is set to match_parent count = mMatchParentChildren. The size (); // The judgment must be greater than 1. If it is not greater than 1, the subview will not changeif (count > 1) {
            for(int i = 0; i < count; i++) { final View child = mMatchParentChildren.get(i); final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec; /** * If the child View width is match_parent, modify the current FrameLayout MeasureSpec: * Change widthMeasureSpec width to total width-padding-margin. * For the child Viw, if match_parent is to be used, then the range it can cover is the measurement width of FrameLayout * minus the padding and margin. * * If the width of the child View is a certain value, such as 50dp, then FrameLayout widthMeasureSpec is changed to: FrameLayout widthMeasureSpec widthMeasureSpec widthMeasureSpec widthMeasureSpec widthMeasureSpec widthMeasureSpec widthMeasureSpec widthMeasureSpec widthMeasureSpec widthMeasureSpec SpecSize is the width of the child View minus the padding minus the margin, SpecMode is AT_MOST mode */if(lp.width == LayoutParams.MATCH_PARENT) { final int width = Math.max(0, getMeasuredWidth() - getPaddingLeftWithForeground() - getPaddingRightWithForeground() - lp.leftMargin - lp.rightMargin);  childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( width, MeasureSpec.EXACTLY); }else{ childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeftWithForeground() + getPaddingRightWithForeground() + lp.leftMargin + lp.rightMargin, lp.width); } final int childHeightMeasureSpec; // Do the same for heightif (lp.height == LayoutParams.MATCH_PARENT) {
                    final int height = Math.max(0, getMeasuredHeight()
                            - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                            - lp.topMargin - lp.bottomMargin);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            height, MeasureSpec.EXACTLY);
                } else{ childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTopWithForeground() + getPaddingBottomWithForeground() + lp.topMargin + lp.bottomMargin, lp.height); } childWidthMeasureSpec (childWidthMeasureSpec, childHeightMeasureSpec); }}} / / getSuggestedMinimumHeight similarly protected intgetSuggestedMinimumWidth() {// If no background is set for the View, return the minimum width of the View itselfinWidth // If the View has a background, take the minimum View Width of mMinWidth and the maximum minimum Width of the backgroundreturn (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
Copy the code

To summarize the code, FrameLayout measures each child View according to its MeasureSpec, calling measureChildWithMargin. For each measured child View, the measurement of FrameLayout is affected by the measured child View’s maximum width (wrAP_content mode). SetMeasureDimension is then called. Save FrameLayout’s measurement width and height. Finally, if FrameLayout is wrap_content and its child View is match_parent, the FrameLayout measurement should be reset, and the part of the View should be measured again. Let’s see how it measures the child as follows:

// ViewGroup class protected void measureChildWithMargins(View Child, int parentWidthMeasureSpec, int widthUsed, Int parentHeightMeasureSpec, int heightUsed) {// LayoutParams, Your layout_width and layout_height, // layout_xxx values in XML will be encapsulated in this LayoutParams. final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }Copy the code

The parent container’s MeasureSpec and its own layoutParams attribute are passed in to get the child’s MeasureSpec. A child View’s MeasureSpec is determined by its parent’s MeasureSpec and its own LayoutParams. Now let’s look at the implementation of getChildMeasureSpec:

// ViewGroup class // spec = parent View's size // padding = parent View's size in the corresponding direction plus parent View's padding and child View's margin // Public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); // size indicates the available space of the child View: parent container size minus the padding int size = math.max (0, specsie-padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on uscase MeasureSpec.EXACTLY:
            if(childDimension >= 0) {// The LayoutParams of the child View specifies the specific size (xx dp) resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; }else if(childDimension == layoutparams.match_parent) {// The child View wants to be the same size as the parent View. resultMode = MeasureSpec.EXACTLY; }else if(childDimension == layoutparams.wrap_content) {// The child View wants to determine its own size, but cannot be larger than the parent View. resultMode = MeasureSpec.AT_MOST; }break;

        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
Copy the code

Here, different modes and sizes are set for the child according to the SpecMode of the ViewGroup to ensure that the child can correctly complete the measurement process. Now we’re returning the measureChildWithMargins method and executing the Child.measure method. If the Child is still ViewGroup, the onMeasure process for the Fragment is still being lifted. Until child is a pure View. This brings us back to the View’s measure method.

MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec

  1. To clarify the concept, there are now three layouts: DecorView, FrameLayout, and Several ChildViews
  2. The original source of MeasureSpec is the DecorView, using the screen window’s windowSize and the DecorView’s LayoutParams to acknowledge firstWidthMeasureSpec and firstHeightMeasureSpec, So the size of the DecorView is confirmed from the start
  3. DecorView root layout, FrameLayout is a childView of the DecorView, and several childviews are child FrameLayout views
  4. Each child View calculates widthMeasureSpec and heightMeasureSpec from the parent layout before measuring its own widthMeasureSpec and heightMeasureSpec, as well as the padding and margin, and its own width and height ureSpec
  5. If FrameLayout is match_parent, then FrameLayout is the same size as the DecorView. DecorView is the same as FrameLayout’s widthMeasureSpec and heightMeasureSpec.
  6. If FrameLayout is wrAP_content or wrap_content, the size of FrameLayout is not necessarily the same as that of the DecorView. In the wrAP_content case, the width and height of the wrAP_content case is equal to the parent width and height (DecorView) -padding, so the widthMeasureSpec and heightMeasureSpec are different, at least in mode. Assuming the padding is not set and FrameLayout width is match_paren height is wrap_content, FrameLayout width and height is the same as the screen size, but heightMeasureSpec mode is AT_MOST.
  7. The size of FrameLayout is uncertain, and the childView of FrameLayout needs to be measured first. WidthMeasureSpec and heightMeasureSpec are passed into childView with AT_MOST mode and size matching screen size.
  8. WidthMeasureSpec and heightMeasureSpec are the widthMeasureSpec and heightMeasureSpec of childView.
  9. After childView measurement is completed, FrameLayout needs to set its final width and height according to the maximum width and height in several ChildViews, its own padding value and the size caused by the background image or not.
  10. If there is a match_parent View among many childViews, this part of the View needs to be measured again. Since the last measurement used widthMeasureSpec and heightMeasureSpec with the same size as the screen but not the same mode, now that the final FrameLayout size is determined, So FrameLayout’s widthMeasureSpec and heightMeasureSpec have changed. So we need to iterate through this part of the childView(this molecular View may change its size depending on the FrameLayout height). Since we know the exact size of the parent layout in step 8, MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = measure
  11. Several childView before measuring yourself also need to combined with the parameters given in the first widthMeasureSpec and heightMeasureSpec, and padding, margins, own suits own MeasureSpec wide high calculated value, and then spread in the measure, Finally, it is further measured in onMeasure.
  12. Because of the situation in step 9, the View sometimes onMeasure multiple times.
// FrameLayout has two textViews, both with match_parent <? xml version="1.0" encoding="utf-8"? > <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="100dp"
    android:layout_height="wrap_content"
    tools:context=".MainActivity">
    
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:text="Hello World! Hello World" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="Hello World!"/> </FrameLayout>  @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width; int height; .if (heightMode == MeasureSpec.EXACTLY) {
            // Parent has told us how big to be. So be it.
            height = heightSize;
            mDesiredHeightAtMeasure = -1;
        } else{// Obtain the desired height int desired = getDesiredHeight(); height = desired; . }...setMeasuredDimension(width, height);
    }
Copy the code

Match_parent’s child View must be greater than 1 in order to measure the child View. Set the width and height of the first TextView to wrap_content, and if so the height of the second TextView will be one row high.

Draw the Layout of the process trilogy

The onLayout layout is used to place the View tree in the appropriate position according to the size and layout parameters of the child View. Back to the performLayout method of the ViewRootImpl class:

/ / ViewRootImpl class private void performLayout (WindowManager. LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { mLayoutRequested =false;
        mScrollMayChange = true;
        mInLayout = true; final View host = mView; . // Call mView layout host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); mInLayout =false; Int numViewsRequestingLayout = mLayoutRequesters. Size ();if(numViewsRequestingLayout > 0) {requestLayout() was called during layout. // If no layout-request flags areset on the requesting views, there is no problem.
                // If some requests are still pending, then we need to clear those flags and do
                // a full request/measure/layout pass to handle this situation.
                ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,false);
                if(validLayoutRequesters ! = null) { mHandlingLayoutInLayoutRequest =true;

                    // Process fresh layout requests, then measure and layout
                    int numValidRequests = validLayoutRequesters.size();
                    for(int i = 0; i < numValidRequests; ++i) { final View view = validLayoutRequesters.get(i); // Call their requestLayout method, view.requestLayout(); } // Measure measureHierarchy(host, LP, mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight) again; mInLayout =true; // Relayout host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); // Relayout host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); mHandlingLayoutInLayoutRequest =false;

                    validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
                    if(validLayoutRequesters ! = null) { final ArrayList<View> finalRequesters = validLayoutRequesters; GetRunQueue ().post(new); // Check if there are still views that need layout. If so, proceed to the next frameRunnable() {
                            @Override
                            public void run() {
                                int numValidRequests = finalRequesters.size();
                                for (int i = 0; i < numValidRequests; ++i) {
                                    final View view = finalRequesters.get(i);
                                    Log.w("View"."requestLayout() improperly called by " + view +
                                            " during second layout pass: posting in next frame"); view.requestLayout(); }}}); } } } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } mInLayout =false;
    }
    
    boolean requestLayoutDuringLayout(final View view) {
        if (view.mParent == null || view.mAttachInfo == null) {
            // Would not normally trigger another layout, so just let it pass through as usual
            return true;
        }
        if(! mLayoutRequesters.contains(view)) { mLayoutRequesters.add(view); }... } // View class: @callsuper public voidrequestLayout() {
        if(mMeasureCache ! = null) mMeasureCache.clear();if(mAttachInfo ! = null && mAttachInfo.mViewRequestingLayout == null) { ViewRootImpl viewRoot = getViewRootImpl();if(viewRoot ! = null && viewRoot.isInLayout()) {if(! viewRoot.requestLayoutDuringLayout(this)) {return; } } mAttachInfo.mViewRequestingLayout = this; }... }Copy the code

In the performLayout method, the layout control of View is first called, and then a collection mLayoutRequesters, From a known source mLayoutRequesters collection data is added in the requestLayoutDuringLayout method, and requestLayoutDuringLayout methods is being call requestLayout method in the View. The requestLayout and invalidate methods in the View class are used to customize the View.

  1. RequestLayout causes the View’s onMeasure, onLayout, and onDraw methods to be called.
  2. The invalidate method only causes the View’s onDraw method to be called

Therefore, in this case the code does not go to if (numViewsRequestingLayout > 0), so just look at the View layout method.

Public void layout(int l, int t, int r, int b) {// View class: public void layout(int l, int t, int r, int b) {if((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) ! = 0) { onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; // Check the layout mode of the view and its parent view. If the two are not synchronized, modify the size of the child viewif// First, the child view has a special optical boundary, while the parent view does nottrue// The parent view has a special optical boundary, while the child view does notfalse
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if(changed | | (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) = = PFLAG_LAYOUT_REQUIRED) {/ / callback onLayout () method onLayout (changed, l, t, r, b); . }... } public static boolean isLayoutModeOptical(Object o) {returno instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical(); } // ViewGroup class: private int mLayoutMode = LAYOUT_MODE_UNDEFINED; booleanisLayoutModeOptical() {
        return mLayoutMode == LAYOUT_MODE_OPTICAL_BOUNDS;
    }
Copy the code

The default value of mLayoutMode is LAYOUT_MODE_UNDEFINED, that is: IsLayoutModeOptical (mParent) returns false, so will call setFrame method, and the four locations in information transmission, the method is used to determine the position of the View of four vertices, namely the initialization mLeft, mRight, mTop, mBottom these four values, When initialization is complete, the layout flow of the ViewGroup is complete. The layout method then calls the onLayout() method, which is called in the ViewGroup to determine the location of the child View. Within the layout method, the child View calls its own Layout method to further complete its layout process. Since the above Measure analyzes FrameLayout, now Layout also uses FrameLayout analysis:

@Override protected void onLayout(boolean changed, int left, int top, int right, LayoutChildren (left, top, right, bottom, layoutChildren(left, top, right, bottom, layoutChildren) {false/* no force left gravity */); } void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) { final int count = getChildCount(); / / the following four values will affect the child / / parentLeft View layout parameters determined by the padding and the Foreground of the parent container final int parentLeft = getPaddingLeftWithForeground (); / / parentRight determined by the width and the padding and the Foreground of the parent container 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) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); Final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); int childLeft; int childTop; int gravity = lp.gravity;if(gravity == -1) { gravity = DEFAULT_CHILD_GRAVITY; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; // When the child View sets the horizontal layout_gravity property, ChildLeft: X switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {absoluteGravity & Gravity. Since the child View is to be displayed in the middle of the horizontal, we need to calculate the following: * (parentRight - parentleft-width)/2 * (parentRight - parentleft-width)/2 * (parentRight - parentLeft)/2 * if the child View is also constrained by margin, and leftMargin makes the child View right-biased and rightMargin makes the child View left-biased, the final * is + leftmargin-rightMargincase Gravity.CENTER_HORIZONTAL:
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                        break; ParentRight minus the measurement width of the child View minus margincase Gravity.RIGHT:
                        if(! forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin;break; } // If layout_gravity is not set horizontally, it defaults to horizontal left, and the upper left abscess of the child View is equal to parentLeft plus the magin value of the child ViewcaseGravity.LEFT: default: childLeft = parentLeft + lp.leftMargin; } childTop = childTop;}} switch (gravity) {childTop = childTop;}case Gravity.TOP:
                        childTop = parentTop + lp.topMargin;
                        break;
                    case Gravity.CENTER_VERTICAL:
                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                        lp.topMargin - lp.bottomMargin;
                        break;
                    case Gravity.BOTTOM:
                        childTop = parentBottom - height - lp.bottomMargin;
                        break; default: childTop = parentTop + lp.topMargin; } // Layout the child elements with the upper-left coordinate (childLeft, childTop), ChildLeft +width,childTop+height (childLeft, childTop, childTop+height); }}}Copy the code

The onLayout method directly calls the layoutChildren method, which is the concrete implementation. First, get the padding value of the parent container, then walk through each of its child views, determine the layout parameters of the child View according to the layout_gravity property of the child View, the width and height of the child View, and the padding value of the parent container. Then call the child.layout method. Pass layout flow from parent container to child element. If the child View is a ViewGroup, the above steps are repeated; if it is a View, the View# Layout method is called directly.

Draw process trilogy Draw

Now that we have the final Draw, let’s go back to view Animation PL and look at the performDraw method:

// ViewRootImpl class: private voidperformDraw() {... try { draw(fullRedrawNeeded); } finally { mIsDrawing =false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); }...if (mReportNextDraw) {
            ...
            try {
                mWindowSession.finishDrawing(mWindow);
            } catch (RemoteException e) {
            }
        }
    }
Copy the code

The ViewRootImpl#draw method is called and the fullRedrawNeeded parameter is passed. This parameter is obtained by the mFullRedrawNeeded member variable. This parameter determines whether the view needs to be redrawn for the first time. So obviously all views should be drawn, and if for some reason the view is redrawn, then it’s not necessary to draw all views.

// ViewRootImpl: private void draw(Boolean fullRedrawNeeded) {Surface = Surface; .if(! sFirstDrawComplete) { synchronized (sFirstDrawHandlers) { sFirstDrawComplete =true;
                final int count = sFirstDrawHandlers.size();
                for(int i = 0; i< count; i++) { mHandler.post(sFirstDrawHandlers.get(i)); }}} scrollToRectOrFocus(null,false); // Distribute the OnScrollChanged eventif (mAttachInfo.mViewScrollChanged) {
            mAttachInfo.mViewScrollChanged = false; mAttachInfo.mTreeObserver.dispatchOnScrollChanged(); } boolean animating = mScroller ! = null && mScroller.computeScrollOffset(); final int curScrollY;if (animating) {
            curScrollY = mScroller.getCurrY();
        } else{ curScrollY = mScrollY; } // RootView slide callbackif(mCurScrollY ! = curScrollY) { mCurScrollY = curScrollY; fullRedrawNeeded =true;
            if (mView instanceof RootViewSurfaceTaker) {
                ((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
            }
        }

        final floatappScale = mAttachInfo.mApplicationScale; final boolean scalingRequired = mAttachInfo.mScalingRequired; int resizeAlpha = 0; Final Rect dirty = mDirty;if(mSurfaceHolder ! = null) { dirty.setEmpty();if(animating && mScroller ! = null) { mScroller.abortAnimation(); }return; } // If fullRedrawNeeded is true, the dirty area is set to the entire screen, indicating that the entire view needs to be drawn // The first time the process is drawn, all views need to be drawnif (fullRedrawNeeded) {
            mAttachInfo.mIgnoreDirtyState = true; Dirty. Set (0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); }... / / distribute ontouch mAttachInfo. MTreeObserver. DispatchOnDraw ();if(! dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {if(mAttachInfo.mThreadedRenderer ! = null && mAttachInfo.mThreadedRenderer.isEnabled()) { // }else {
                //
                if(! drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {return; }}}... }Copy the code

Sliding into a designated area first, and then obtain the mDirty value, the value to save the area need to redraw the information, then according to the fullRedrawNeeded to determine whether you need to reset the dirty area, the last call ViewRootImpl. DrawSoftware method, and the related parameter passing in, Including dirty areas.

// ViewRootImpl class: Final Surface mSurface = new Surface(); Private Boolean drawSoftware(Surface Surface, AttachInfo AttachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty) { // Draw with software renderer. final Canvas canvas; try { final int left = dirty.left; final int top = dirty.top; final int right = dirty.right; final int bottom = dirty.bottom; Canvas = msurface.lockCanvas (dirty); // The dirty rectangle can be modified by Surface.lockCanvas() //noinspection ConstantConditionsif(left ! = dirty.left || top ! = dirty.top || right ! = dirty.right || bottom ! = dirty.bottom) { attachInfo.mIgnoreDirtyState =true;
            }

            // TODO: Do this in native
            canvas.setDensity(mDensity);
        } catch (Surface.OutOfResourcesException e) {
            handleOutOfResourcesException(e);
            return false;
        } catch (IllegalArgumentException e) {
            ...
            mLayoutRequested = true;    // ask wm for a new surface next time.
            return false;
        }

        try {
            ...
            try {
                canvas.translate(-xoff, -yoff);
                if(mTranslator ! = null) { mTranslator.translateCanvas(canvas); } canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0); attachInfo.mSetIgnoreDirtyState =false; // Start drawing mview.draw (canvas); drawAccessibilityFocusedDrawableIfNeeded(canvas); } finally { ... Finally}} {the try {/ / unlock Canvas, draw back immediately appear on the screen surface. The picture looks unlockCanvasAndPost (Canvas); } catch (IllegalArgumentException e) { ... }... }return true;
    }
Copy the code

First, the Canvas object is instantiated, and then a Canvas area is locked from the mSurface, which is determined by the dirty area. Then, a series of attributes are assigned to the Canvas, and then the mview.draw (Canvas) method is called to draw. Finally, the display screen is unlocked. Surface and SurfaceSession have been mentioned above in the section “onResume visible draw synchronization barrier –VSYNC Synchronization”. From ViewRootImpl. DrawSoftware method also saw the Surface figure. Here’s a rundown of the Surface games we’ve seen so far:

  • When viewrotimPL is initialized (when constructed), a Surface is created, where the code is visible, directly from the member variable new.
  • The ViewRootImpl interacts with WMS via IWindowSession, and an AttaHC method called by WMS constructs a SurfaceSession, For those who forget, please refer back to the section “IWindowSession and IWindow drawn on the onResume screen”.
  • The ViewRootImpl calls the Relayout method of IWindowSession during performTransval processing, looking back at the “Draw Process Trilogy” section.
  • ViewRootImpl. DrawSoftware method called Surface lockCanvas method, get a piece of canvas
  • ViewRootImpl. DrawSoftware method called Surface unlockCanvasAndPost method, release the canvas

Now let’s talk about Surface through the figures related to Surface. Let’s start with a simplified flow chart:

// SurfaceSession class: publicSurfaceSession() {// call the native method mNativeClient = nativeCreate(); } // android_view_SurfaceSession. CPP file static jlong nativeCreate(JNIEnv* env, Jclass clazz) {// Initialize SurfaceComposerClient, The object will interact with SurfaceFlinger SurfaceComposerClient* client = new SurfaceComposerClient(); client->incStrong((void*)nativeCreate);return reinterpret_cast<jlong>(client);
}
Copy the code

Through the simplified flowchart and the code above, we can see a few things:

  1. SurfaceComposerClient is initialized only when SurfaceSession is initialized
  2. In the “onResume interface visible drawing IWindowSession and IWindow” section when we were talking about the setView method in the ViewRootImpl class, In mWindowSession can see requestLayout (). AddToDisplay () calls before, and it also illustrates the SurfaceSession initialization time is in mWindowSession addToDisplay
  3. The logic for all trilogies comes from requestLayout()
  4. Streamline need SurfaceComposerClient displayed on the flow chart, just can have the back of the mSurface. LockCanvas and mSurface unlockCanvasAndPost

So what makes SurfaceSession initialized before going trilogy logic?

The answer to this question can be found in requestLayout(). The onResume screen displays a synchronization barrier called VSYNC. Mhandler.getlooper ().getQueue().postsyncBarrier (), which sends a synchronization barrier message, blocks the execution of other messages in the message queue, but does not stop code execution. SurfaceSession initializes the SurfaceSession… , wait for Looper to poll for a synchronization handicap message and continue until mTraversalRunnable callback doTraversal(). This is similar to the code in handler.post and the normal main thread:

Handler Handler = new Handler(); handler.post(newRunnable() {
        @Override
        public void run() {
            Log.e("handler.post"."Handler. Post: 111111111111111111111111111111"); }});for(int i = 0; i <10000; i++){
        Log.e("handler.post"."Handler. Post:"+i); } // The output is...... Handler. Post: 9998 handler. Post: 9999-04-18 21:29:26. 521, 6249-6249 / com. Android. Sourcecodeanalysis E/handler. Post: Handler. Post: 111111111111111111111111111111Copy the code

So to summarize the Surface briefly: the whole drawing process of the Activity is to lock a Canvas from the mSurface, and then hand it to the mView to give free play to the drawing ability. Finally, unlockCanvasAndPost releases the Canvas. Because Surface is such a complex system, It includes Layer(display Layer), FrameBuffer(FrameBuffer), PageFlipping(screen exchange :FrontBuffer before the FrameBuffer screen and BackBuffer after the FrameBuffer screen), SurfaceFlinger(image mixing: Multiple display layers mixed together to display pictures) and Linux shared memory knowledge points, here is no longer in-depth, interested partners, you can refer to the “in-depth understanding of Android volume 1 (Deng Fanping)” book chapter 8 Surface system, to further study (remember to read the source code to be careful, If you’re feeling pretty good about c/ C ++, try digging deeper.

// 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; /* * Draw traversal performs several drawing stepswhich must be executed
     * in the appropriate order:
     *
     *      1. Draw the background
     *      2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content
     *      4. Draw children
     *      5. If necessary, draw the fading edges and restore layers
     *      6. Draw decorations (scrollbars for instance)
     */

    // Step 1, draw the background, if needed
    int saveCount;

    if(! dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5if possible (common case) final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) ! = 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) ! = 0;if(! verticalEdges && ! horizontalEdges) { // Step 3, draw the contentif(! dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Overlay is part of the content and draws beneath Foregroundif(mOverlay ! = null && ! mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); // Step 7, draw the default focus highlight drawDefaultFocusHighlight(canvas);if (debugDraw()) {
            debugDrawFocus(canvas);
        }
        
        // we're done... return; }... }Copy the code

The source code has given the steps of DRAW:

  1. Draw the background of the View
  2. Save the current layer information (skip)
  3. Draws the contents of the View
  4. Draw a child View of a View (if any)
  5. Draw the faded edges of the View, similar to the shadow effect (skip)
  6. Draw the View decoration (e.g. scroll bar) source hint, where steps 2 and 5 can be skipped, is a common situation.
/ / the View class: Private void drawBackground(Canvas Canvas) {// mBackground is the View's background argument, Final Drawable background = mBackground;if (background == null) {
            return; } // Determine the background boundary according to the View four layout parameterssetBackgroundBounds(); . Final int scrollX = mScrollX; final int scrollX = mScrollX; final int scrollY = mScrollY; // The view offset parameters are taken into account :scrollX and scrollYif ((scrollX | scrollY) == 0) {
            background.draw(canvas);
        } else{// If scrollX and scrollY have values, offset the canvas coordinates and draw the background Canvas.translate (scrollX, scrollY); background.draw(canvas); canvas.translate(-scrollX, -scrollY); }} protected void onDraw(Canvas Canvas) {// This method is an empty implementation because different views have different contents. Protected void dispatchDraw(Canvas Canvas) {}Copy the code

If the View is a ViewGroup, its drawing calls dispatchDraw(canvas);

/ / ViewGroup class:  protected void dispatchDraw(Canvas canvas) { boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode); final int childrenCount = mChildrenCount; final View[] children = mChildren; int flags = mGroupFlags; // Traverses all subviewsfor (int i = 0; i < childrenCount; i++) {
        while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
            final View transientChild = mTransientViews.get(transientIndex);
            if((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || transientChild.getAnimation() ! = null) { more |= drawChild(canvas, transientChild, drawingTime); } transientIndex++;if (transientIndex >= transientCount) {
                transientIndex = -1;
            }
        }
        int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
        final View child = (preorderedList == null)
                ? children[childIndex] : preorderedList.get(childIndex);
        if((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() ! = null) { more |= drawChild(canvas, child, drawingTime); }}... }Copy the code

You can see that the dispatchDraw method iterates through all the child Views and calls the drawChild method:

/ / ViewGroup class: Protected Boolean drawChild(Canvas Canvas, View child, long drawingTime) {// Call the View draw method, but this method is not as described above, because the arguments are differentreturnchild.draw(canvas, this, drawingTime); } // View class: Boolean draw(Canvas Canvas, ViewGroup parent, long drawingTime) {......if(! drawingWithDrawingCache) {if (drawingWithRenderNode) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
            } else {
                // Fast path for layouts with no backgrounds
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    dispatchDraw(canvas);
                } else{ draw(canvas); }}}else if(cache ! = null) { mPrivateFlags &= ~PFLAG_DIRTY_MASK;if (layerType == LAYER_TYPE_NONE) {
                // no layer paint, use temporary paint to draw bitmap
                Paint cachePaint = parent.mCachePaint;
                if (cachePaint == null) {
                    cachePaint = new Paint();
                    cachePaint.setDither(false); parent.mCachePaint = cachePaint; } cachePaint.setAlpha((int) (alpha * 255)); Canvas. DrawBitmap (cache, 0.0 f, 0.0 f, cachePaint); }else{ // use layer paint to draw the bitmap, merging the two alphas, but also restore int layerPaintAlpha = mLayerPaint.getAlpha(); mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha)); Canvas. DrawBitmap (cache, 0.0 f, 0.0 f, mLayerPaint); mLayerPaint.setAlpha(layerPaintAlpha); }}}Copy the code

The first step is to determine whether there is already a cache, i.e. whether it has been drawn once before. If not, the draw(canvas) method will be called and normal drawing will begin, i.e. the six steps mentioned above. Otherwise, the display will be drawn using cache. ViewGroup rendering process: dispatchDraw draws child View, if child View is still ViewGroup then dispatchDraw draws child View until it is not a ViewGroup. Finally, only onDrawForeground remains: the so-called foreground foreground foreground refers to the rest of the View except the background, content, and sub-view, such as the scroll bar, etc.

// View class: public void onDrawForeground(Canvas Canvas) {onDrawScrollIndicators(Canvas); onDrawScrollBars(canvas); final Drawable foreground = mForegroundInfo ! = null ? mForegroundInfo.mDrawable : null;if(foreground ! = null) {if (mForegroundInfo.mBoundsChanged) {
                mForegroundInfo.mBoundsChanged = false;
                final Rect selfBounds = mForegroundInfo.mSelfBounds;
                final Rect overlayBounds = mForegroundInfo.mOverlayBounds;

                if (mForegroundInfo.mInsidePadding) {
                    selfBounds.set(0, 0, getWidth(), getHeight());
                } else{ selfBounds.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()); } final int ld = getLayoutDirection(); Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(), foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld); foreground.setBounds(overlayBounds); } foreground.draw(canvas); }}Copy the code

It is very similar to the general drawing process, which is to set the drawing area first and then draw with canvas.

At this point, the View overall drawing process is complete.

Said the experience, this article to prepare and write back and forth, with 5 days free time, the biggest harvest is not only know the View of drawing process, but to understand the custom View when you need to call the principle of method and why do you call, and some don’t understand the place before, suddenly feel suddenly enlightened. It was hard work and took a few nights, but it was worth it. Maybe not a few people can read the whole article, but still hope to see the partners can gain a little in this article, a little is also good. Finally, warn yourself: deep source code to be careful, ability is not very enough, do not go too far, otherwise you will get lost in it.

Reference links:

www.jianshu.com/p/a13e3a325…

www.jianshu.com/p/3299c3de0…

www.jianshu.com/p/dc6eeeb73…

Blog.csdn.net/freekiteyu/…

In-depth Understanding of Android Volume 1 (Deng Fanping)

.

If you think the article is good or inadequate, welcome to like the message, thank you