简介 对一般工程来说,程序员可能只需要能通过 XML 绘制界面,然后在 Activity 中一系列控件操作即可,或者更高级一点自定义控件等等,但是这里涉及到的底层原理:
setContentView 里面究竟进行了怎样的操作?
View 是如何渲染界面的?
为什么触摸事件可以进行分发?
相信只有一探源码,才能解决我们疑惑的问题。
底层剖析 Window、Activity、View 在串起流程之前,先描述下这三者的定义:
Window :Window 是一个窗口的概念,它是一个抽象类,其具体实现是 PhoneWindow。Window 负责管理一个 View 树(DecorView)和一个 WindowManager.LayoutParams。每个 Window 对应屏幕上的一个矩形区域——这是 WMS 管理的最小单位。
Activity :Activity 是 Android 提供给开发者的 UI 交互单元,管理一个 PhoneWindow 的生命周期。Activity 本身不直接参与 View 的渲染——它将渲染相关的工作全权委托给 PhoneWindow。
View :View 是屏幕上可见的矩形区域,是 Android 中所有 UI 控件的基类。View 负责绘制(onDraw)、事件处理(onTouchEvent)和布局(onMeasure/onLayout)。
三者关系:Activity 持有 PhoneWindow,PhoneWindow 持有 DecorView(顶层 View),DecorView 包含用户布局的 View 树。WMS 管理所有 Window 的显示层级、位置和焦点。
接下来开始从底层源码一步一步剖析 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。
关键点 :mWindow.setCallback(this) 这一行至关重要——它将 Activity 自身设置为 PhoneWindow 的 Callback。这个 Callback 接口定义了 dispatchTouchEvent、onMenuItemSelected 等方法。这就是触摸事件如何从 DecorView 流转到 Activity 的桥梁。
PhoneWindow 的 setContentView Activity 将 setContentView 的操作交给 PhoneWindow ,其实现过程如下:
@Override public void setContentView (int layoutResID) { if (mContentParent == null ) { 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 { mLayoutInflater.inflate(layoutResID, mContentParent); } ... ... } private void installDecor () { if (mDecor == null ) { mDecor = generateDecor(-1 ); ... ... } else { mDecor.setWindow(this ); } if (mContentParent == null ) { mContentParent = generateLayout(mDecor); } ... ... }
generateLayout 的内部逻辑 :
根据 Window 的 features(如 FEATURE_NO_TITLE、FEATURE_ACTION_BAR)选择不同的布局模板
常见的模板是 com.android.internal.R.layout.screen_simple.xml,结构如下:
<LinearLayout > <ViewStub android:id ="@+id/action_mode_bar_stub" /> <FrameLayout android:id ="@android:id/content" /> </LinearLayout >
将选中的布局 inflate 到 DecorView 中
将 @android:id/content 对应的 View 赋值给 mContentParent
用户的 setContentView(layoutResId) 就是将自定义布局添加到这个 mContentParent 中
这里很好理解,也就是 DecorView 是 PhoneWindow 所持有的一个布局对象,并且这个布局包含 Activity setContentView 传递过来的布局,因此这里的布局包含关系为:
接下来就是找出 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 ; 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; requestLayout(); if ((mWindowAttributes.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0 ) { mInputChannel = new InputChannel (); } ... try { ... ... res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mWinFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel); } catch (RemoteException e) { ... } finally { ... } ... ... 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; } } }
ViewRootImpl 的角色 :它是连接 WindowManager 和 DecorView 的纽带。每一个 Window 对应一个 ViewRootImpl。它负责:
通过 IWindowSession 向 WMS 注册窗口
触发 View 的 measure/layout/draw 三大流程
管理 InputChannel,分发触摸事件到 View 树
与 Choreographer 协调 vsync 信号,驱动界面刷新
WindowSession 是 WindowManagerGlobal 中的单例对象,其初始化代码如下:
private static IWindowSession sWindowSession;public static IWindowSession getWindowSession () { synchronized (WindowManagerGlobal.class) { if (sWindowSession == null ) { try { InputMethodManager imm = InputMethodManager.getInstance(); IWindowManager windowManager = getWindowManagerService(); 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. 这里 ,可以知道,设置了一系列的输入通道。一个触屏事件的发生始于屏幕 ,然后经过驱动层 一系列处理通过 InputChannel 跨进程通知 Android Framework 层(InputDispatcher → WMS → ViewRootImpl) ,最终屏幕上的触摸事件会被发送到 输入管道 中。
这些输入管道实际上是一个责任链模式的结构,当屏幕上某个触摸事件到达其中的 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, …) 往下走流程即可看出】,而 dispatchPointerEvent 则是被 View.java 实现的,如下所示:
public final boolean dispatchPointerEvent (MotionEvent event) { if (event.isTouchEvent()) { return dispatchTouchEvent(event); } else { return dispatchGenericMotionEvent(event); } } @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 阶段,创建 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 方法:
public boolean dispatchTouchEvent (MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true ; } return onTouchEvent(ev); } @Override public boolean superDispatchTouchEvent (MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }
所以,Touch 事件在 Activity 中兜兜转转又回到了 PhoneWindow 中的 DecorView 来处理。接下来的处理就是从 DecorView 开始将事件层层传递给内部的子 View。
事件分发的完整流向总结 硬件屏幕触摸 → InputReader (读取 /dev/input/event*) → InputDispatcher (通过 InputChannel 发送到 App 进程) → ViewRootImpl.InputStage 责任链 → ViewPostImeInputStage.processPointerEvent() → DecorView.dispatchPointerEvent() → DecorView.dispatchTouchEvent() → Window.Callback.dispatchTouchEvent() [即 Activity.dispatchTouchEvent()] → PhoneWindow.superDispatchTouchEvent() → DecorView.superDispatchTouchEvent() → ViewGroup.dispatchTouchEvent() [事件分发机制:onInterceptTouchEvent → child.dispatchTouchEvent] → View.onTouchEvent() [最终消费]
Choreographer 与 vsync 刷新机制 View 的绘制是由 vsync 信号驱动的。Choreographer 是 vsync 信号的分发器:
Choreographer.getInstance().postFrameCallback(new Choreographer .FrameCallback() { @Override public void doFrame (long frameTimeNanos) { } });
ViewRootImpl 在 scheduleTraversals() 中向 Choreographer 注册 TRAVERSAL callback,下一个 vsync 到来时执行 doTraversal(),最终触发 performMeasure() → performLayout() → performDraw()。
invalidate vs requestLayout 的区别
方法
作用
触发流程
invalidate()
仅重绘(draw)
跳过 measure/layout,仅 draw。标记脏区域,通过 ViewRootImpl 注册下一次 vsync draw
requestLayout()
重新测量+布局+绘制
从 View 向上标记 PFLAG_FORCE_LAYOUT,最终触发 ViewRootImpl.performLayout + performDraw
— 完整的 View 三大流程工作过程 —
核心面试题 Q1: setContentView 之后 View 何时开始渲染?为什么 onCreate 中获取不到 View 的宽高?
setContentView 只是将布局 inflate 到了 DecorView 中(创建了 View 树),但还没有执行 measure/layout/draw。实际渲染在 onResume 之后——handleResumeActivity 通过 WindowManager.addView 创建 ViewRootImpl,后者在下一个 vsync 信号到来时执行三大流程。因此 onCreate 中调用 view.getWidth() 返回 0。
Q2: ViewRootImpl 和 DecorView 的关系是什么?
一个 Window 对应一个 ViewRootImpl 和一个 DecorView。ViewRootImpl 不是 View,它的角色是”管理者”——负责与 WMS 通信、触发 View 树的测量/布局/绘制、管理 InputChannel 分发触摸事件。DecorView 是 Window 的顶层 View(是一个 FrameLayout 的子类)。
Q3: requestLayout 和 invalidate 的区别是什么?
requestLayout 触发完整的 measure → layout → draw 流程(从 View 向上标记到 ViewRootImpl)。invalidate 仅触发 draw 流程(跳过 measure/layout),且只在脏区域内绘制。如果一个 View 只改变了颜色/透明度但尺寸/位置未变,用 invalidate 即可——比 requestLayout 更高效。
Q4: 触摸事件从硬件到 Activity.dispatchTouchEvent 的完整路径是怎样的?
硬件 → InputReader(读 /dev/input) → InputDispatcher(通过 InputChannel 跨进程) → ViewRootImpl.InputStage 责任链 → DecorView.dispatchTouchEvent → Window.Callback.dispatchTouchEvent(即 Activity) → PhoneWindow.superDispatchTouchEvent → DecorView.superDispatchTouchEvent → ViewGroup 事件分发 → 最终 View.onTouchEvent。
参考源码路径:
Activity:frameworks/base/core/java/android/app/Activity.java
PhoneWindow:frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
WindowManagerImpl:frameworks/base/core/java/android/view/WindowManagerImpl.java
WindowManagerGlobal:frameworks/base/core/java/android/view/WindowManagerGlobal.java
ViewRootImpl:frameworks/base/core/java/android/view/ViewRootImpl.java
DecorView:frameworks/base/core/java/com/android/internal/policy/DecorView.java
Choreographer:frameworks/base/core/java/android/view/Choreographer.java