Window should be clear, it is an abstract class, the concrete implementation class is PhoneWindow, it manages the View. WindowManager is an interface class that inherits from ViewManager and is named for managing Windows. Its implementation class is WindowManagerImpl. If you want to add, update, or delete Windows, you can use Windows Manager. Windows Manager hands over the work to WMS. Windows Manager and WMS communicate across processes with Binder.

Window and WindowManager

To implement a procedure for adding Windows through WIndowManager:

        Button mFloatingButton = new Button(this);
        mFloatingButton.setText("button");
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 0.0, PixelFormat.TRANSPARENT);

        layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;

        layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;

        layoutParams.gravity= Gravity.LEFT|Gravity.TOP;
        layoutParams.x=100;
        layoutParams.y=300;

        WindowManager windowManager = getWindowManager();

        windowManager.addView(mFloatingButton,layoutParams);

Copy the code

Add a Button to the screen coordinate of (100,300). Focus on the two attributes Type and Flag, which are important parameters.

The Type attribute

Window refers to the type of Window, which is divided into three types: application Window, child Window and system Window.

  • The application class Window corresponds to an Activity.
  • A child Window cannot stand alone, it needs to be attached to a particular parent WIindow (Dialog is a child Window)
  • A system Window is a Window that you need to declare permission to create (Toast and status bar are system Windows).
The Window type Scope of the hierarchy
Window 1~99
The child Window 1000 ~ 1999
The WIndow system 2000 ~ 2999

Flag properties

The Window Flag is used to control the display of the Window. It is also defined in the WindowManager inner class LaoyoutParams.

Flag describe
FLAG_ALLOW_LOCK_WHITE_SCREEN_ON Lock the screen on the open screen as long as the window is visible
FLAG_NOT_FOCUSABLE Windows don’t get input focus, so FLAG_NOT_TOUCH_MODAL is also set when you set the change flag
FLAG_NOT_TOUCHABLE The window does not receive any touch events
FLAG_NOT_TOUCH_MODAL It passes touch events outside the Window area to other Windows, and only handles touch events inside the Window area itself
FLAG_KEEP_SCREEN_ON The screen stays on as long as the window is visible
FLAG_LAYOUT_NO_LIMITS Allows Windows to go beyond the screen
FLAG_FULLSCREEN Hide all screen decoration Windows, such as full screen playback in games and players
FLAG_SHOW_WHEN_LOCKED Windows can be displayed above the lock screen window
FLAG_IGNORE_CHEEK_PRESSES This event does not respond when the user’s face is close to the screen, such as when making a phone call
FLAG_TURN_SCREEN_ON Light up the screen when the window is displayed

The internal operation of Window

Each Window corresponds to a View and a ViewRootImpl. The connection between Window and View is established through ViewRootImpl. Therefore, Window does not exist in time, it exists in the form of View. WindowManager manages Windows, and when it comes to managing Windows, it adds, updates, and deletes them.

// All operations are performed on the View, indicating that the View is the entity existing in the Window
public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}
Copy the code

Windows MangerImpl does not directly implement the three operations of Windows, but all are given to Windows ManagerGlobal.

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

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

Copy the code

Window adding process

In Windows ManagerGlobal addView, you create a ViewrotimPL and add a View to the list, update the interface and complete the Window addition process with viewrotimPL.

# # #WindowManagerGlobal
  
    public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
  // Check if the parameters are valid. If it is a child Window, some layout parameters need to be adjusted
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }
        if(! (paramsinstanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); }... ViewRootImpl root; View panelParentView =null;

        synchronized (mLock) {
          
           ......
             
             // Create ViewRootImpl and add the View to the list
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

          //mViews stores all views corresponding to Windows
            mViews.add(view);
          //mRoots stores the ViewRootImpl for all Windows
            mRoots.add(root);
            mParams.add(wparams);

            try {
              // Update the interface via ViewRootImpl and complete the Window addition process
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throwe; }}}Copy the code

Call requestLayouot and finally add Window via WindowSession. MWIndowSession is of type IWindowSession, a Binder object whose real implementation class is Sessiion. The addition of the Window is an IPC call.

# # #ViewRootImpl
  public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {...// Arrange the first layout before adding it to the window manager to ensure that we rearrange it before receiving any other events from the system.requestLayout(); .try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
      
      						// The process of adding Windows is finally completed through WindowSession
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, 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();
                    }
                }
}

### ViewRootImpl
public void requestLayout(a) {
        if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true;
          // This is actually the View drawing entryscheduleTraversals(); }}Copy the code

Windows ManagerService is used to add Windw within the Session.

### WindowManagerService
@Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {

  // Leave it to it
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }
Copy the code

The addToDisplay method internally calls the addWindow method of WMS and passes in its own Session. Each application will have a Session, and WMS will store it in an ArrayList, so WMS will do all the work.

/ / WMS added

The Window deletion process

Windows ManagerGlobal removeView implementation

# # #WindowManagerGlobal
   public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
          // Find the index of the View to be deleted
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
          // Delete further
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to "+ curView); }}Copy the code

RemoveView and removeViewImmediate are two delete interfaces provided in Windows Manager, indicating asynchronous and synchronous deletion respectively. RemoveViewLocked is used to perform further deletes, which are performed by the ViewRoootImpl’s Die method.

# # #WindowManagerGlobal
private void removeViewLocked(int index, boolean immediate) { ViewRootImpl root = mRoots.get(index); View view = root.getView(); .// Delete using ViewRoootImpl
        boolean deferred = root.die(immediate);
        if(view ! =null) {
            view.assignParent(null);
            if(deferred) { mDyingViews.add(view); }}}Copy the code

In the die method, doDie() is called directly for synchronous deletes, and an MSG_DIE message is sent for asynchronous deletes. In doDie approach, eventually call dispatchDetachedFromWindow () to actually delete the View.

# # #ViewRootImpl 
boolean die(boolean immediate) {
        // Sync delete calls doDie directly
        if(immediate && ! mIsInTraversal) { doDie();return false;
        }

        if(! mIsDrawing) { destroyHardwareRenderer(); }else {
            Log.e(mTag, "Attempting to destroy the window while drawing! \n" +
                    " window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
  	// Async remover sends an MSG_DIE message, still calls doDie()
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }

### ViewRootImpl 
  // Both end up calling the doDie method
     void doDie(a) {
        checkThread();
        if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
              // The real logic for deleting a View is implemented inside itdispatchDetachedFromWindow(); }... mAdded =false;
        }
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }
Copy the code

Windows update process

So what does updateViewLayout do

# # #WindowManagerGlobal
   public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if(! (paramsinstanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

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

  // Update View's LayoutParams and replace the old LayooutParams
        view.setLayoutParams(wparams);

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
          // Update LayoutParams in ViewRootImpl
            root.setLayoutParams(wparams, false); }}Copy the code

The ViewRootImpl setLayoutParams(wParams, False) method finally calls the ViewRootImpl schedultTraversals method to rearrange the View, including measurement, layout, and redraw. The Window view is also updated via WindowSession, which is finally implemented by relayoutWindow of WindowManagerService.

# # #ViewRootImpl
 void scheduleTraversals(a) {
        if(! mTraversalScheduled) { mTraversalScheduled =true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
          // It is used to receive a new VSYN number from the display system and perform some operations during the next frame rendering.
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
Copy the code

The Window of the Activity creates the add procedure

No matter what kind of window, its addition process is basically similar in the WMS processing part, here takes the most typical application window Activity as an example.

When an Activity is started, a new process is created if the process in which the Activity is started does not already exist. The new process will run the ActivityThread instance that represents the main thread. PreformLaunchActivity () in ActivityThread completes the entire startup process.

Inside this method, an instance object of the Activity is created through the class loader and the attach method is called for a series of associated operations.

# # #ActivityThread
   private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {...try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation); .if(activity ! =null) {... appContext.setOuterContext(activity);// Call attch to bind window
                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);

                if(customIntent ! =null) { activity.mIntent = customIntent; }... }}Copy the code

In the Attach method of the Activity, the system creates the Window to which the Activity belongs and sets the callback interface for it. Because the Activity implements the Window’s Callback interface, the Activity method is called back when the Window receives a state change.

###Activity.attach
  
  	// Create PhoneWindow with unique implementation class
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
		// Pass the window callback to bind
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if(info.softInputMode ! = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); }if(info.uiOptions ! =0) {
            mWindow.setUiOptions(info.uiOptions);
        }
Copy the code

And since the Actiivity view calls the setContentView method to show it, let’s now look at the setContentView method.

### Activity
// Add all top-level views to the activity
  public void setContentView(@LayoutRes int layoutResID) {
  		// The implementation is left to Window
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
Copy the code

The implementation of phoneWndow is left to The window, and the implementation of phoneWndow is PhoneWindow’s setConotentView method.

# # #PhoneWindow
   public void setContentView(int layoutResID) {
        if (mContentParent == null) {
          	/ / DecorView creation
            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 {
          // Add the Activity's layout file
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if(cb ! =null && !isDestroyed()) {
          // The onContentChanged method of the callback Activity notifies the Activity view that it has changed
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }
Copy the code

The DecorView creation is done by the installDecor method. Inside the method, the DecorView is created directly through the generateDecor method. GenerateLayout is also needed to load the specific layout file into the DecorView.

### PhoneWindow.installDecor
  if (mDecor == null) {
    			// Create a blank FrameLayout
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if(! mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures ! =0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); }}else {
            mDecor.setWindow(this);
        }

				if (mContentParent == null) {
          // Use this method to load the concrete layout file into the DecorViewmContentParent = generateLayout(mDecor); . } ### PhoneWiindow.generateDecor
  protected DecorView generateDecor(int featureId) {
  	// This is a blank FrameLayout
   return new DecorView(context, featureId, this, getAttributes());
}

### PhoneWiindow.generateLayout
  protected ViewGroup generateLayout(DecorView decor) {...// The ViewGroup corresponding to ID_ANDROID_CONTENT is mContentParent
  ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view"); }...return contentParent;
}
Copy the code

Once you’ve created the DecorView and PhoneWindow, the next step is to add the Activity view directly to the DecorView’s mContentParent. Let’s go back to the setContentView method and look at a line of code:

### PhoneWindow.setContentView
  // Add an XML view
mLayoutInflater.inflate(layoutResID, mContentParent);
Copy the code

At this point, the Activity layout file is added to the DecorView, but the DecorView is still not added to the Window by The WindowManager, The final screen display is in the ActiivtyThread’s handleResumeActivity method, which is called after onResume, and the Activity view is displayed.

###ActivityThread.handleResumeActivity

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {...if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                ViewRootImpl impl = decor.getViewRootImpl();
                if(impl ! =null) { impl.notifyChildRebuilt(); }}if (a.mVisibleFromClient) {
                if(! a.mWindowAdded) { a.mWindowAdded =true;
                  // The Decorview really does both the add and display
                  // The wm.addView method is familiar
                    wm.addView(decor, l);
                } else{ a.onWindowAttributesChanged(l); }}}... }Copy the code

View refresh process

The add and update of the Window will eventually go to the scheduleTraversals() method. What does that do?

# # #ViewRoootImpl
void scheduleTraversals(a) {
        if(! mTraversalScheduled) { mTraversalScheduled =true;
          // Add a synchronization barrier
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
          // This added callback will be executed on the next frame render
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
Copy the code

When a drawing task is submitted, a synchronization barrier is added so that the asynchronous task executes first and then waits for the Vsyn callback to start drawing the next frame. The add a callback refers to the type of TraversalRunnabl mTraversalRunnable, in mChoreographer. PostCallback sends an asynchronous message delay is carried out.

### ViewRootImpl
final class TraversalRunnable implements Runnable {
        @Override
        public void run(a) { doTraversal(); }}void doTraversal(a) {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
          // Remove the synchronization barrier
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

          // Start drawing
            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false; }}}Copy the code

This method starts by removing the synchronization barrier and then calls performTraversals(), enabling the View to draw, and then performMeasure, performLayout, and performDraw.

Summarize the refresh principle:

Both the View requestLayout and ViewRootImpl##setView will eventually call the ViewRootImpl requestLayout method. The scheduleTraversals method is then used to submit the drawing task, and the DisplayEventReceiver is used to request the vsync signal from the bottom layer. When the vsync signal comes in, it is called back by JNI. An asynchronous task is then posted to the message queue via Handler, the ViewRootImpl is used to perform the rendering task, and the performTraversals method is called to complete the rendering.

Finally the performTraversals() method triggers the rendering of the View. Inside the method, call performMeasure(), performLayout(), performDraw(), the View measure, layout, draw process, distributed from the top View down.

There’s a lot more to View refresh, and I’ll cover that later.

reference

Exploring the Art of Android Development