Android WindowManager深层理解view绘制实现流程

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

Android WindowManager深层理解view绘制实现流程

devnn   2022-11-16 我要评论

前言

又是一年一度的1024程序员节了,今天不写点什么总感觉对不起这个节日。想来想去,就写点关于View的绘制。本文不会重点讲View绘制三大回调函数:onMeasure、onLayout、onDraw,而是站在Android framework的角度去分析一下View的绘制。

  • View是如何被渲染到屏幕中的?
  • ViewRoot、DecorView、Activity、Window、WindowManager是什么关系?
  • View和Surface是什么关系?
  • View和SurfaceFlinger、OpenGL ES是什么关系?

计算机的图像一般是需要经过底层的图像引擎输出GPU需要的数据交给GPU,显示器从GPU从不断的取出渲染好的数据显示到屏幕上。

熟悉Andriod体系架构的人都知道,Android底层渲染图像的引擎是OpenGL ES/Vulkan。那么View是被谁渲染的呢?没错,View最终也是交给底层渲染引擎的,那么从View到OpenGL ES这中间经历了哪些过程呢?

setContentView()流程

在Activity的onCreate中我们一般会通过setContentView来给Activity设置界面布局。这个时候,Activity是否开始渲染了呢?并没有,setContentView只是构建整个DecorView的树。

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

setContentView是调用Window的setContentView,而PhoneWindow是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处开始安装DecorView,主要是new一个DecorView,并找到其中id等于content的布局,通过mContentParent引用。我们在xml的写的布局就是添加到这个mContentParent容器中。

//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处通过LayoutInflator解析传入的layoutResID,解析成View并添加到mContentParent中。mContentParent就是我们xml界面的中的id等于content的布局:

综上分析,setContentView主要完成两个功能:

1、构建DecorView

2、解析自定义的xml布局文件,添加到DecorView的content中。

所以setContentView还没有真正开始渲染图像。

思考:如果我们没有调用setContentView,Activity能正常启动吗?为什么?

WindowManager.addView流程

Android的所有View都是通过WindowManager.addView添加到屏幕中的。那么Activity的DecorView是什么时调被添加到屏幕中的呢?

答案在ActivityThreadhandleResumeActivity方法中:

//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.addView才是开始DecorView渲染的入口。而它的触发时机是在Activity的onResume生命周期之后,所以说onResume之后View才会显示在屏幕上,并且渲染完成才可以获取到View的宽度。

主要看下1处调用了window的getDecorView()方法:

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

这里可以看出,即使我们没有调用setContentView,DecorView也会初始化,只是会显示空白页面。

然后我们重点看下2处的代码,通过WindowManager的addView方法将DecorView添加到window中了:wm.addView(decor, l)

继续分析addView之前先梳理一下必要的基本知识。

上面的wm虽然是ViewManager类型的,它实际就是WindowManager。

WindowManager是一个接口,它继承自ViewManager。

public interface WindowManager extends ViewManager {
...
}

可以看到WindowManager的实现类是WindowManagerImpl,后面WindowManager的功能都是靠WindowManagerImpl来实现的。

Window是抽象类,PhoneWindow是它的实现。WindowManager是Window的成员变量,Window和WindowManager都是在Activity的attach方法中初始化的:

//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处开始初始化window,并赋值给Activity的成员变量mWindow

2处给window设置windowManager

3处Activity通过mWindowManager引用window中的WindowManager,两个wm是同一个东西。

然后重点看下setWindowManager方法的实现:

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

梳理完基本关系,再回头看下wm.addView过程。

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

可以看到wm.addView交给了mGolbal对象。

mGolbal是WindowManagerGlobal类型的全局单例:

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

继续看WindowManagerGlobal.addView是如何实现的。

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

可以看到,这里创建了一个ViewRootImpl对象root,并将viewrootwparams保存到了集合中。最后调用了ViewRootImpl的setView方法设置视图。

继续跟踪ViewRootImplsetView方法。

//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是Android窗口管理系统,在将View树注册到WMS之前,必须先执行一次layout,WMS除了窗口管理之外,还负责各种事件的派发,所以在向WMS注册前app在确保这棵view树做好了接收事件准备。

ViewRoot起到中介的作用,它是View树的管理者,同时也兼任与WMS通信的功能。

mWindowSession.addToDisplay将View的渲染交给了WindowManagerService。

mWindowSession是IWindowSession类型的变量,在服务端的实现类是Session.java,它是一个Binder对象。

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

可以看到最终是通过WindowManagerService完成了Window的添加。

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们