目录
  1. 1. 简介
  2. 2. 底层剖析 Window、Activity、View
    1. 2.1. Activity 的 setContentView
    2. 2.2. PhoneWindow 的 setContentView
    3. 2.3. WindowManager 的 addView
    4. 2.4. ViewRootImpl 的 setView
    5. 2.5. 再次回到 Activity
重拾Android-UI进阶之摸清WAV关系

简介

对一般工程来说,程序猿可能只需要能通过 XML 绘制界面,然后在 Activity 中一系列控件操作即可,或者更高级一点自定义控件等等,但是这里涉及到的底层原理:

  • setContentView 里面究竟进行了怎样的操作?
  • View 是如何渲染界面的?
  • 为什么触摸事件可以进行分发?

相信只有一探源码,才能解决我们疑惑的问题。

底层剖析 Window、Activity、View

在串起流程之前,先描述下这三者的定义

Window: Window 是一个窗口的概念,它是一个抽象类,其具体实现是 PhoneWindow

Activity: Activity 是一个页面的概念,它是一个继承自ContextThemeWrapper,带有主题的上下文环境包装对象,是系统Framework层的具体实现类,且它实现了一系列接口,完成界面的生命周期管理及事件分发等等。

View View 是一个控件、组件的概念,它是组成界面的最小单元,由它可组成 视图组ViewGroup

接下来开始从底层源码一步一步剖析 W(Window)、 A(Activity)、 V(View)三者关系

Activity 的 setContentView

查看源码,可知:

private Window mWindow;

public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}

public Window getWindow() {
return mWindow;
}

显然Activity并没有操作什么,而是将其直接交给Window来处理,而分析startActivity 过程可知,最终代码会调用到 ActivityThread 的 PerformLaunchActivity 方法,其通过反射创建 Activity 对象,并执行 attach 方法。而 Window 也正是在此时被创建,详细代码见下:

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) {

... ...

mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);

... ...

mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

... ...
}

在 Activity 的 attach 方法中,将 mWindow 赋值给 PhoneWindow,然后调用 setWindowManager 方法,将系统 WindowManager 传给 PhoneWindow,如下所示:

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}

public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}

最终,在 phoneWindow 中持有一个 WindowManagerImpl对象的引用,即 mWindowManager。

PhoneWindow 的 setContentView

Activity 将 setContentView 的操作交给 PhoneWindow ,其实现过程如下:

@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
// 初始化 DecorView 和 mContentParent
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 {
// 调用 setContentView 传入的布局并添加到 mContentParent
mLayoutInflater.inflate(layoutResID, mContentParent);
}

... ...
}

private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(-1);

... ...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}

... ...
}

这里很好理解,也就是 DecorView 是 PhoneWindow 所持有的一个布局对象,并且这个布局包含 Activity setContentView 传递过来的布局,因此这里的布局包含关系为:

Android渲染绘制原理图4.png

接下来就是找出 DecorView 跟 Activity 究竟是在何时建立联系并被绘制到界面上显示。

这里需要了解 Activity 的生命周期,我们知道 Activity 执行到 onCreate 时并不可见,只有执行完 onResume 之后 Activity 中的内容才是屏幕可见状态。

因此我们大可推断这个中缘由:onCreate 阶段只是初始化了 Activity 需要显示的内容,onResume 阶段才是将 PhoneWindow 中的 DecorView 真正绘制到屏幕上

分析源码可知:

@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
... ...

if (r.window == null && !a.mFinished && willBeVisible) {

... ...

ViewManager wm = a.getWindowManager();

... ...

if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// 调用 WindowManager 的 addView 将 DecorView 添加到 WMS
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
} else if (!willBeVisible) {
...
}

... ...
}

WindowManager 的 addView 结果有两个:

  • DecorView 被渲染绘制到屏幕上显示;
  • DecorView 可以接收屏幕触摸事件。

WindowManager 的 addView

PhoneWindow 只是负责处理应用窗口的一些通用逻辑(比如:设置标题栏、导航栏等),但是真正完成把一个 View 作为窗口添加到 WMS 的过程是由 WindowManager 来实现的。

查看 WindowManager,它是一个接口,其真正的实现类是 WindowManagerImpl,阅读 addView 实现代码:

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

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

public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {

... ...

ViewRootImpl root;
View panelParentView = null;

synchronized (mLock) {

... ...

root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);

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

try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
...
}
}
}

由上可知:WindowManager 的 addView 实现最终还是交给 WindowManagerGlobal 这个单例来实现的,也就是这个对象在一个进程中只会存在一个实例对象。在其 addView 方法中,创建了一个至关重要的 ViewRootImpl 对象,然后通过 ViewRootImpl 的 setView() 将 View 添加到 WMS 中。

ViewRootImpl 的 setView

源码分析:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {

... ...

int res; /* = WindowManagerImpl.ADD_OKAY; */

// View 的绘制流程在这里
// 刷新布局操作会将 ViewRootImpl 所关联的 View 执行 measure - layout - draw 操作
// 确保在 View 添加到 Window 上显示到屏幕前,已经完成 测量、布局和绘制工作
requestLayout();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
// 创建 INPUT Channel -> 对应上面某图 CPU 里操作
mInputChannel = new InputChannel();
}

...
try {

... ...

// 将 View 添加到 WMS中
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
} catch (RemoteException e) {
...
} finally {
...
}

... ...

// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);

mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
}
}
}

WindowSession 是 WindowManagerGlobal 中的单例对象,其初始化代码如下:

private static IWindowSession sWindowSession;

public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();

// 采用 AIDL 获取 System 进程中的 Session 对象
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}

sWindowSession 实际上是 IWindowSession 类型,它是一个 Binder 类型,真正的实现类是 System 进程中的 Session。

代码如下:

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

return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);
}
`

上面的 mService 就是 WMS ,接下来,Window 已经被成功传递给了 WMS,后面就是全部交由系统进程中的 WMS 来完成最终的添加操作。

再次回到 Activity

上文有写到 addView 成功标志: 可以接收触屏事件,通过一系列源码分析可知,添加 View 的操作实际上是 PhoneWindow 全权处理的,但真正的“幕后大佬”其实是 WMS,那么当触屏事件触发后,Touch 事件首先是被分发给 Activity,然后才是各种下发布局 ViewGroup 或者 View

那么 Touch 事件是如何传递给到 Activity 的?

返回查看 ViewRootImpl 的 setView(),在注释 Set up the input pipeline. 这里 ,可以知道,设置了一系列的输入通道。一个触屏事件的发生始于屏幕,然后经过驱动层一系列的优化计算通过 Socket 跨进程通知 Android Framework 层(WMS) ,最终屏幕上的触摸事件会被发送到 输入管道 中。

这些输入管道实际上是一个链表结构,当屏幕上某个触摸事件到达其中的 ViewPostImeInputState 时,会经过 onProcess 来处理,如下所示:

final class ViewPostImeInputStage extends InputStage {

... ...

@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
// 触摸事件由此分发
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
return processTrackballEvent(q);
} else {
return processGenericMotionEvent(q);
}
}
}

private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
...
boolean handled = mView.dispatchPointerEvent(event);
...
mAttachInfo.mHandlingPointerEvent = false;
...
return handled ? FINISH_HANDLED : FORWARD;
}
...
}

由此可看出,onProcess 中最终会去调用 mView.dispatchPointerEvent(event); ,这里 mView实际上就是 PhoneWindow 中的 DecorView【注:mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);往下走流程即可看出】,而 dispatchPointerEvent 则是被 View.java 实现的,如下所示:

// View.java
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
// 由 DecorView 来实现
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}

// DecorView.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
// 最终调用了 PhoneWindow 类中的 Callback 的 dispatchTouchEvent 方法,那么这个 Callback 是谁的呢???
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}

再次回到启动 Activity 阶段,创建 Activity 对象时通过反射调用的 attach 方法,查看ActivityThread.java源码:

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) {

... ...

mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);

// ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
mWindow.setCallback(this);
// ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆

... ...
}

果然,Google诚不欺我也,最终源码都会告诉我们答案。Activity 将自身引用传递给了 PhoneWindow

再看 Activity 的 dispatchTouchEvent 方法:

// Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// 调用 PhoneWindow 的 superDispatchTouchEvent 方法
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}

// PhoneWindow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
//调用 DecorView 的 superDispatchTouchEvent 方法
return mDecor.superDispatchTouchEvent(event);
}

所以,Touch 事件在 Activity 中兜兜转转又回到了 PhoneWindow 中的 DecorView 来处理。接下来的处理就是从 DecorView 开始将事件层层传递给内部的子 View。

打赏
  • 微信
  • 支付宝

评论