以往对于View生命周期的认识停留在onMeasure,onLayout,onDraw等方法上,对Touch事件的传递停留在dispatchTouchEvent,onInterceptTouchEvent,OnTouchEvent等方法上,这样写写自定义控件什么的还是没问题的,不过究其原理,还是有些模糊不清,因为这样的认识还停留在应用层。因此看了下Framework层对于View的组织和管理,以及与Window,ViewRootImpl,WindowManagerService之间的关系。对于整个View体系的认识进一步加深。
WindowManagerGlobal
本地所有窗口的管理者,本身是一个单例对象,在很多地方都可以看到它的影子。
例如ActivityThread中的handleLaunchActivity方法中调用了initialize方法,初始化了一个实现IWindowManager接口的binder单例,通过它可以与WMS进行交互。
在WindowManagerImpl中也有实例化的操作,可以看到WindowManagerImpl实际上相当于WindowManagerGlobal的一个代理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public static WindowManagerGlobal getInstance() { synchronized (WindowManagerGlobal.class) { if (sDefaultWindowManager == null) { sDefaultWindowManager = new WindowManagerGlobal(); } return sDefaultWindowManager; } } public static IWindowManager getWindowManagerService() { synchronized (WindowManagerGlobal.class) { if (sWindowManagerService == null) { sWindowManagerService = IWindowManager.Stub.asInterface( ServiceManager.getService("window")); try { sWindowManagerService = getWindowManagerService(); ValueAnimator.setDurationScale(sWindowManagerService.getCurrentAnimatorScale()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } return sWindowManagerService; } }
|
同时,WindowManagerGlobal持有对当前应用所有窗口的引用:
1 2 3 4
| private final ArrayList<View> mViews = new ArrayList<View>();//这是每个窗口的根view的集合 private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();//ViewRootImpl的集合,与根View一一对应 private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();//根View的布局属性 private final ArraySet<View> mDyingViews = new ArraySet<View>();//要回收的View
|
WindowManagerImpl
实现了WindowManager接口,WindowManager接口继承了ViewManager接口,ViewManager接口定义了:
1 2 3
| public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view);
|
这几个方法,我们在使用WindowManager实现自定义窗口时经常会用到。
在WindowManagerImpl中,我们可以看到
1
| private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
|
发现几乎所有方法的实现都是通过WindowManagerGlobal来进行的。其实我们也可以看到getSystemService(Context.WINDOW_SERVICE)方法,最终也只是new了一个WindowManagerImpl对象,所以,WindowManagerGlobal才是真正的实现类,全局的管理对象。
Window
PhoneWindow是Android中Window的唯一实现类,可以看到在Activity.attach方法中,创建了PhoneWindow对象:
1 2 3
| mWindow = new PhoneWindow(this, window); mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this);
|
这里可以看到Activity实现了Window中定义的相关回调接口,因此我们才能在Activity中收到Touch事件的回调信息,实际上Activity本身并不属于View体系,只是PhoneWindow在分发消息的时候将Activity考虑进去了而已。
Window可以看作一个封装类,对外提供了一系列的回调方法,定义了UI交互的标准,实际上Window本身是一个抽象的概念。可以看到Dialog中自己生成了一个PhoneWindow,而PopupWindow中直接通过WindowManager进行交互,没有生成PhoneWindow。
我们在调用setContentView方法的时候,最终调用的是PhoneWindow.setContentView方法,可以看下这个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Override public void setContentView(int layoutResID) { if (mContentParent == null) { installDecor();//这种情况下DecorView应该还没有初始化,需要初始化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);//将要设置的的布局加入内容区域 } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true; }
|
这里我们可以看到,其实通过源码我们可以发现,mContentParent实际上对应id的是android.R.id.content,本质是FragmentLayout。
这里说下DecorView的概念,DecorView为Activity中View的最高级ParentView,其本身继承了FrameLayout,子View一般上是LinearLayout,这个LinearLayout又包含顶部区域和内容区域,其中顶部区域包含状态栏、ActionBar等等不定(根据主题样式变化),内容区域为一个id的是android.R.id.content的FrameLayout。而我们调用setContentView设置内容布局时,其实就是往这个FrameLayout中添加子View而已。
接着上面分析,如果判断mContentParent为空,则调用installDecor()方法,这个方法中可以看到根据当前的主题初始化了DecorView以及其子View的层级关系,具体过程不再分析。
不过这里有个很重要的操作,mDecor.setWindow(this),这个将自己传递给了DecorView,从而使DecorView可以拿到Window的Callback,我们可以看看DecorView的这个方法:
1 2 3 4 5 6
| @Override public boolean dispatchTouchEvent(MotionEvent ev) { final Window.Callback cb = mWindow.getCallback(); return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev); }
|
通过这个操作,成功的将点击事件传递给了Activity或Dialog等等,让它们也参与了View事件分发的过程。
Token
从Activity的创建过程到BadTokenException,随处可见Token的影子。那么Token究竟是什么呢?
从Activity的启动流程开始回溯,一直到AMS,再到ActivityRecord中,找到了Token的定义。Token继承于IApplicationToken.Stub,是一个Binder对象,它的作用有两个:
- 作为Binder对象供WMS回调使用
- 作为ActivityRecord,Activity的唯一标识
网上找了一幅图,画的很好:
所以应用级别的窗口操作必须带上Token,否则就会报BadTokenException。
ViewRootImpl
在ActivityThread的handleResumeActivity中,我们可以看到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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; // 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 && !a.mWindowAdded) { a.mWindowAdded = true; wm.addView(decor, l); } }
|
这里通过PhoneWindow拿到了DecorView,将其置为不可见状态(之后会通过r.activity.makeVisible()来置为可见),然后调用了wm.addView(decor, l)方法,(注意这一步调用了requestLayout引起了scheduleTraversals操作),跟进去发现,最终调用了WindowManagerGlobal.addView方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| //省略判断的代码若干 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对象,并将View,ViewRootImpl,Params一一对应保存了起来,最终调用了root.setView()方法。这里我们看下ViewRootImpl的构造方法:
1 2 3 4 5 6 7 8 9 10 11
| public ViewRootImpl(Context context, Display display) { mContext = context; mWindowSession = WindowManagerGlobal.getWindowSession(); //...... mWindow = new W(this); //...... mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this); //...... mChoreographer = Choreographer.getInstance(); //...... }
|
可以看到ViewRootImpl构造方法中创建了WindowSession(Binder),W,AttachInfo,Choreographer等等。
IWindowSession的实现类是服务端的Session类。
至于ViewRootImpl.setView()方法最终调用了:
1 2 3 4 5 6
| res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); unscheduleTraversals();//调用performTraversals view.assignParent(this);//将自己关联到根View(一般是DecorView)
|
这个方法将Token随着mWindowAttributes传入WMS,一并传入的还有mInputChannel,mWindow,DisplayId等等。
最终跟到WMS中,发现调用了addWindow方法:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public int addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, InputChannel outInputChannel) { //...... boolean addToken = false; WindowToken token = mTokenMap.get(attrs.token); //...... win = new WindowState(this, session, client, token, attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent); win.attach(); mWindowMap.put(client.asBinder(), win); ...... }
|
这个方法会通过attrs拿到Token,然后用Token从mTokenMap去拿本地保存的WindowToken,然后会进行Window层级判断,如果是应用内窗口而且WindowToken为空就返回BadToken,拿到了说明Token有效,然后创建WindowState,并将IWindow作为key,将WindowState存入mWindowMap。
WindowState是Window在WMS中的表现形式,如同ActivityRecord和Activity一样。
ps:WindowState是Window在WMS中的表现形式,ActivityRecord是Activity的表现形式,TaskRecord是Task的表现形式,ActivityStack是所有Task的Manager.
win.attach()方法会调用mSession.windowAddedLocked(),然后会创建SurfaceSession的实例,Session中持有SurfaceSession,SurfaceSession构造方法里调用了nativeCreate,从这里开始就是native的世界,简单概括一下流程是通过创建SurfaceComposerClient与SurfaceFlinger进行交互,锁定一块共享内存。
这里mWindowSession.addToDisplay()方法先告一段落。
可以看到ViewRootImpl.setView()方法之后又执行了unscheduleTraversals()方法,之后发现又调用了performTraversals()方法,这个方法是View的measure,layout,draw的起点。我们平时调用的requestLayout()或者是invalidate()方法,最终都是会回到这个方法,只不过调用的方法不通,其遍历的流程也不一样。
大概看一下这个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| if (mFirst) { //...... host.dispatchAttachedToWindow(mAttachInfo, 0);//第一次执行需要分发attach事件 } //...... if (viewVisibilityChanged) {//可见性发生变化 mAttachInfo.mWindowVisibility = viewVisibility; host.dispatchWindowVisibilityChanged(viewVisibility); } //将之前View.post()方法所积攒的消息加入消息队列. //注意只有在mAttachInfo为空的时候(也就是在dispatchAttachedToWindow之前)才会积攒消息. //不为空的话是直接加入消息队列的. getRunQueue().executeActions(mAttachInfo.mHandler); //...... relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); //...... performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//measure流程的起点 //...... performLayout(lp, mWidth, mHeight);//layout流程的起点 //...... performDraw();//draw流程的起点
|
可以看到,在第一次执行performTraversals()时,会回调dispatchAttachedToWindow,将mAttachInfo传递给View,当View被remove的时候,会回调dispatchDetachFromWindow,mAttachInfo被同一个Window中所有的View所共用,View从mAttachInfo中获取要用的信息。
看下View.post()方法:
1 2 3 4 5 6 7 8
| public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } getRunQueue().post(action); return true; }
|
如果attachInfo为空,则会将action存入getRunQueue()方法的返回值中:
1 2 3 4 5 6
| private HandlerActionQueue getRunQueue() { if (mRunQueue == null) { mRunQueue = new HandlerActionQueue(); } return mRunQueue; }
|
可以看到getRunQueue是一个队列,它会将消息先存起来,到了performTraversals的时候,再将其加入主线程消息队列进行分发执行。
relayoutWindow最终在WMS中根据Window测量的大小相对应创建出SurfaceControl,通过SurfaceControl.getSurface将ViewRootImpl.mSurface和SurfaceSession锁定的共享内存关联起来,使其拥有了native surface的地址。
开始绘制时,会调用Surface.lockCanvas,由SurfaceFlinger锁定一块共享内存传递给Canvas,内存共享的是设备显存,在上面绘制相当于在屏幕上绘画。绘制结束调用Surface.unlockCanvasAndPost,从Suface上detach掉canvas,释放Surface。
ViewRootImpl本身并不是View,但其实现了ViewParent接口,所以看起来像一个ViewGroup.
硬件加速
不管是硬件加速还是软件绘制,绘制内存的分配都是类似的,都是需要请求SurfaceFlinger服务分配一块内存,两者的绘制都是在APP端,绘制完成之后同样需要通知SurfaceFlinger进行合成。
硬件加速和软件绘制的区别在于,软件绘制是利用CPU进行的,在遍历的过程中进行绘制。而硬件加速则是在遍历的过程中构建操作集合,通过GPU统一绘制渲染,因此效率会高很多。
硬件加速绘制流程会使用renderNode.start返回的DisplayListCanvas,软件绘制时则是mSurface.lockCanvas(dirty)返回的CompatibleCanvas.
Touch事件的传递流程
这里不谈Touch事件如何在View之间传递,只谈Touch事件是如何传递到View体系的。
我们在ViewRootImpl中可以看到在setView时,调用了mWindowSession.addToDisplay方法,这个方法传递了mInputChannel的参数到WMS,那么mInputChannel是什么呢?
1 2 3 4 5 6 7
| /** * An input channel specifies the file descriptors used to send input events to * a window in another process. It is Parcelable so that it can be sent * to the process that is to receive events. Only one thread should be reading * from an InputChannel at a time. * @hide */
|
根据其注释了解到它可以将input event从另一个进程发送到本地来的管道。
看看在WMS中对其做了什么:
1
| win.openInputChannel(outInputChannel);
|
再跟进去:
1
| mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);
|
发现最终调用了这句话,这里的mInputManager是InputManagerService,可以见的,所有input event都是通过InputManagerService分发下来的,具体细节先不深究了。
那么看看它在ViewRootImpl中的用途:
1 2
| mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper());
|
发现在ViewRootImpl.setView中将其作为参数传入了WindowInputEventReceiver的构造方法中。
可以发现WindowInputEventReceiver是一个InputEvent的监听器,收到消息后会进行分发。
最终会分发到ViewPostImeInputStage.processPointerEvent方法中,
1 2 3 4 5
| private int processPointerEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; boolean handled = eventTarget.dispatchPointerEvent(event); return handled ? FINISH_HANDLED : FORWARD; }
|
这里的eventTarget一般来说是DecorView,从这里开始,事件开始正式传入了View体系中。
至于在View体系中的分发流程,这里不再赘述。
参考链接
https://blog.csdn.net/guoqifa29/article/details/46819377