Android View体系

以往对于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对象,它的作用有两个:

  1. 作为Binder对象供WMS回调使用
  2. 作为ActivityRecord,Activity的唯一标识

网上找了一幅图,画的很好:
Token

所以应用级别的窗口操作必须带上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

文章目录
  1. 1. WindowManagerGlobal
  2. 2. WindowManagerImpl
  3. 3. Window
  4. 4. Token
  5. 5. ViewRootImpl
  6. 6. 硬件加速
  7. 7. Touch事件的传递流程
  • 参考链接
  • |