[Android] Understand the drawing of View from the perspective of WindowManager

foreword

It's the annual 1024 Programmer's Day again. I feel sorry for this festival if I don't write anything today. After much deliberation, just write about the drawing of View. This article will not focus on the three major callback functions of View drawing: onMeasure, onLayout, onDraw, but analyze View drawing from the perspective of Android framework.

  • How is View rendered to the screen?
  • What is the relationship between ViewRoot, DecorView, Activity, Window, and WindowManager?
  • What is the relationship between View and Surface?
  • What is the relationship between View and SurfaceFlinger and OpenGL ES?

The image of the computer generally needs to output the data required by the GPU through the underlying image engine to the GPU, and the display will continuously take out the rendered data from the GPU and display it on the screen.

Anyone familiar with the Andriod architecture knows that the underlying Android engine for rendering images is OpenGL ES/Vulkan. So who is the View rendered by? That's right, View is finally handed over to the underlying rendering engine, so what process has it experienced from View to OpenGL ES?

setContentView() process

In the onCreate of the Activity, we generally set the interface layout for the Activity through setContentView. At this time, does the Activity start rendering? No, setContentView just builds the entire DecorView tree.

//android.app.Activity
 public void setContentView(@LayoutRes int layoutResID) {
    
    
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

setContentView calls Window's setContentView, and PhoneWindow is the only implementation class of Window:

//com.android.internal.policy.PhoneWindow
 @Override
    public void setContentView(int layoutResID) {
    
    
         if (mContentParent == null) {
    
    
            installDecor();//1,安装DecorView
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
    
    
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
    
    
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
    
    
            mLayoutInflater.inflate(layoutResID, mContentParent);//2,解析layoutResID
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
    
    
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;

1 starts to install DecorView, mainly to create a new DecorView, and find the layout whose id is equal to content, and refer to it through mContentParent. The layout we wrote in xml is added to this mContentParent container.

//com.android.internal.policy.PhoneWindow
private void installDecor() {
    
    
        mForceDecorInstall = false;
        if (mDecor == null) {
    
    
            mDecor = generateDecor(-1);//new一个DecorView
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
    
    
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
    
    
            mDecor.setWindow(this);
        }
         if (mContentParent == null) {
    
    
            mContentParent = generateLayout(mDecor);//找到id==content的ViewGroup
            ...
  }

2. Parse the incoming layoutResID through LayoutInflator, parse it into View and add it to mContentParent. mContentParent is the layout where the id in our xml interface is equal to the content:
insert image description here
In summary, setContentView mainly completes two functions:
1. Build DecorView
2. Parse the custom xml layout file and add it to the content of DecorView.

So setContentView hasn't actually started rendering the image yet.

Thinking: If we don't call setContentView, can the Activity start normally? Why?

WindowManager.addView process

All Android Views are added to the screen through WindowManager.addView. So when is the Activity's DecorView added to the screen?

The answer is in the method ActivityThreadof handleResumeActivity:

//android.app.ActivityThread
   @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
    
    
            
         ...
         //执行Activity的onResume生命周期
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
         ...
            
		if (r.window == null && !a.mFinished && willBeVisible) {
    
    
	            r.window = r.activity.getWindow();
	            View decor = r.window.getDecorView();//1、调用了window.getDecorView()方法
	            decor.setVisibility(View.INVISIBLE);
	            ViewManager wm = a.getWindowManager();
	            WindowManager.LayoutParams l = r.window.getAttributes();
	            a.mDecor = decor;
	            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
	            l.softInputMode |= forwardBit;
	            if (r.mPreserveWindow) {
    
    
	                a.mWindowAdded = true;
	                r.mPreserveWindow = false;
	                // Normally the ViewRoot sets up callbacks with the Activity
	                // in addView->ViewRootImpl#setView. If we are instead reusing
	                // the decor view we have to notify the view root that the
	                // callbacks may have changed.
	                ViewRootImpl impl = decor.getViewRootImpl();
	                if (impl != null) {
    
    
	                    impl.notifyChildRebuilt();
	                }
	            }
	            if (a.mVisibleFromClient) {
    
    
	                if (!a.mWindowAdded) {
    
    
	                    a.mWindowAdded = true;
	                    wm.addView(decor, l);//2、开始调用WindowManager.addView将view添加到屏幕
	                } else {
    
    
	                    // The activity will get a callback for this {@link LayoutParams} change
	                    // earlier. However, at that time the decor will not be set (this is set
	                    // in this method), so no action will be taken. This call ensures the
	                    // callback occurs with the decor set.
	                    a.onWindowAttributesChanged(l);
	                }
	            }
	
	            // If the window has already been added, but during resume
	            // we started another activity, then don't yet make the
	            // window visible.
	        } else if (!willBeVisible) {
    
    
	            if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
	            r.hideForNow = true;
	        }
	        
		...省略无关代码

wm.addViewThis is the entry point to start DecorViewrendering. And its trigger timing is onResumeafter the life cycle of the Activity, so the View will be displayed on the screen after onResume, and the width of the View can only be obtained after the rendering is completed.

Mainly look at one place where the getDecorView() method of window is called:

//com.android.internal.policy.PhoneWindow
   @Override
    public final @NonNull View getDecorView() {
    
    
        if (mDecor == null || mForceDecorInstall) {
    
    
            installDecor();
        }
        return mDecor;
    }

It can be seen here that even though we didn't call setContentView, DecorView will initialize, just display a blank page.

Then we focus on the code at 2 places, and add DecorView to the window through the addView method of WindowManager:wm.addView(decor, l)

Before continuing to analyze addView, sort out the necessary basic knowledge.

Although the above wm is of ViewManager type, it is actually WindowManager.

WindowManager is an interface which inherits from ViewManager.

public interface WindowManager extends ViewManager {
    
    
...
}

WindowManagerThe implementation class that can be seen is WindowManagerImplthat the following WindowManagerfunctions are all WindowManagerImplimplemented by .

Window is an abstract class and PhoneWindow is its implementation. WindowManager is a member variable of Window, both Window and WindowManager are initialized in the attach method of Activity:

//android.app.Activity
  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, IBinder assistToken) {
    
    
        attachBaseContext(context);

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

        mWindow = new PhoneWindow(this, window, activityConfigCallback);//1,初始化window
        
        ...省略无关代码

        mWindow.setWindowManager(  //2、给window初始化windowManager
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
    
    
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();//3、Activity通过mWindowManager引用window中的WindowManager,两个wm是同一个东西。
        mCurrentConfig = config;

        mWindow.setColorMode(info.colorMode);

        setAutofillOptions(application.getAutofillOptions());
        setContentCaptureOptions(application.getContentCaptureOptions());
    }
  • 1 starts to initialize the window and assigns it to the member variable mWindow of the Activity
  • 2 set windowManager for window
  • The 3 activities refer to the WindowManager in the window through mWindowManager, and the two wm are the same thing.

Then focus on the implementation of the setWindowManager method:

//android.view.Window
   public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
    
    
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated;
        if (wm == null) {
    
    
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

After sorting out the basic relationship, look back at wm.addViewthe process.

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

You can see that wm.addView is handed over to the mGolbal object.

mGolbal is a global singleton of type WindowManagerGlobal:

public final class WindowManagerImpl implements WindowManager {
    
    
   @UnsupportedAppUsage
   private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
   ...

Continue to see how WindowManagerGlobal.addView is implemented.

//android.view.WindowManagerGlobal
 public void addView(View view, ViewGroup.LayoutParams params,
           Display display, Window parentWindow) {
    
    
           
 	   		// ...省略无关代码

   			ViewRootImpl root;
       		View panelParentView = null;

       	 	// ...省略无关代码
        
           root = new ViewRootImpl(view.getContext(), display);

           view.setLayoutParams(wparams);

           mViews.add(view);
           mRoots.add(root);
           mParams.add(wparams);

           // do this last because it fires off messages to start doing things
           try {
    
    
               root.setView(view, wparams, panelParentView);
           } catch (RuntimeException e) {
    
    
               // BadTokenException or InvalidDisplayException, clean up.
               if (index >= 0) {
    
    
                   removeViewLocked(index, true);
               }
               throw e;
           }
       }
   }

ViewRootImplAs you can see, an object root is created here , and view, root, wparamsare saved in the collection. ViewRootImplFinally, the setView method is called to set the view.

Ways to keep ViewRootImpltrack setView.

//android.view.ViewRootImpl
  public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    
    
        synchronized (this) {
    
    
            if (mView == null) {
    
    
                mView = view;
				//...省略不重要代码
               
                mAdded = true;
                int res; /* = WindowManagerImpl.ADD_OKAY; */

                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
    
    
                    mInputChannel = new InputChannel();
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                try {
    
    
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                            mTempInsets);
                    setFrame(mTmpFrame);
                } 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();
                    }
                }

              //...省略不重要代码

                if (view instanceof RootViewSurfaceTaker) {
    
    
                    mInputQueueCallback =
                        ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
                }
                if (mInputChannel != null) {
    
    
                    if (mInputQueueCallback != null) {
    
    
                        mInputQueue = new InputQueue();
                        mInputQueueCallback.onInputQueueCreated(mInputQueue);
                    }
                    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                            Looper.myLooper());
                }

                view.assignParent(this);
                //...省略不重要代码
            }
        }
    }

WMS is the Android window management system. Before registering the View tree with WMS, a layout must be executed first. In addition to window management, WMS is also responsible for dispatching various events, so the app ensures the view tree before registering with WMS. Get ready to receive events.

ViewRoot acts as an intermediary, it is the manager of the View tree, and also serves as the function of communicating with WMS.

mWindowSession.addToDisplay hands over the rendering of View to WindowManagerService.

mWindowSession is a variable of IWindowSession type, and the implementation class on the server side is Session.java, which is a Binder object.

  @UnsupportedAppUsage
    public static IWindowSession getWindowSession() {
    
    
        synchronized (WindowManagerGlobal.class) {
    
    
            if (sWindowSession == null) {
    
    
                try {
    
    
                    // Emulate the legacy behavior.  The global instance of InputMethodManager
                    // was instantiated here.
                    // TODO(b/116157766): Remove this hack after cleaning up @UnsupportedAppUsage
                    InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
    
    
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
    
    
                                    ValueAnimator.setDurationScale(scale);
                                }
                            });
                } catch (RemoteException e) {
    
    
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }
 @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
    
    
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }

WindowManagerServiceIt can be seen that the addition of Window is finally completed.

To be continued...

Guess you like

Origin blog.csdn.net/devnn/article/details/127500258