前言
setContentView(R.layout.activity_main)这么简简单单的一段代码做了事情可不简单;
接下来我们会跟着源码大概走一遍这个方法,一起总结下
一、DecorView的创建
Activity.setContentView
- public void setContentView(@LayoutRes int layoutResID) {
- getWindow().setContentView(layoutResID); //最终调用mWindow的setContentView方法
- initWindowDecorActionBar();
- }
getWindow返回的是mWindow, mWindow在Activity的attach方法里被赋值,是个PhoneWindow对象。
(PhoneWindow是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) {
- attachBaseContext(context);
- mFragments.attachHost(null /*parent*/);
- mWindow = new PhoneWindow(this, window, activityConfigCallback);
- ...
- mWindow.setWindowManager( //设置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();
- }
PhoneWindow.setContentView
几个关键变量
1.mDecor 是Window的最顶层的View,是个FrameLayout。
2.mContentParent 是用来真正装载Activity传入的布局文件的容器,本身是个ViewGroup。
- public void setContentView(int layoutResID) {
- // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
- // decor, when theme attributes and the like are crystalized. Do not check the feature
- // before this happens.
- if (mContentParent == null) {
- installDecor(); //如果mContentParent为空,则执行installDecor方法
- } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
- mContentParent.removeAllViews(); //否则remove掉mContentParent的所有子view
- }
- if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
- final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
- getContext());
- transitionTo(newScene);
- } else {
- mLayoutInflater.inflate(layoutResID, mContentParent); //将activity传入的布局文件加载到mContentParent里
- }
- mContentParent.requestApplyInsets();
- final Callback cb = getCallback();
- if (cb != null && !isDestroyed()) {
- cb.onContentChanged();
- }
- mContentParentExplicitlySet = true;
- }
PhoneWindow.installDecor
- private void installDecor() {
- mForceDecorInstall = false;
- if (mDecor == null) {
- mDecor = generateDecor(-1); //如果之前没有创建,直接创建一个
- mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
- mDecor.setIsRootNamespace(true);
- if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
- mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
- }
- } else {
- mDecor.setWindow(this); //将PhoneWindow传递给DecorView
- }
- if (mContentParent == null) {
- mContentParent = generateLayout(mDecor); //赋值mContentParent
- ...
- }
PhoneWindow.installDecor
- protected DecorView generateDecor(int featureId) {
- // System process doesn't have application context and in that case we need to directly use
- // the context we have. Otherwise we want the application context, so we don't cling to the
- // activity.
- Context context;
- if (mUseDecorContext) {
- Context applicationContext = getContext().getApplicationContext();
- if (applicationContext == null) {
- context = getContext();
- } else {
- context = new DecorContext(applicationContext, getContext());
- if (mTheme != -1) {
- context.setTheme(mTheme);
- }
- }
- } else {
- context = getContext();
- }
- return new DecorView(context, featureId, this, getAttributes()); //创建DecorView
- }
PhoneWindow.generateLayout
这一步是挑选合适的DecorView布局文件并将其添加大盘DecorView,同时给mContentParent赋值。
- protected ViewGroup generateLayout(DecorView decor) {
- // Apply data from current theme.
- TypedArray a = getWindowStyle();
- ......
- // Inflate the window decor.
- //根据不同的features来选择DecorView的布局
- int layoutResource;
- int features = getLocalFeatures();
- // System.out.println("Features: 0x" + Integer.toHexString(features));
- if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
- layoutResource = R.layout.screen_swipe_dismiss;
- setCloseOnSwipeEnabled(true);
- } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
- if (mIsFloating) {
- TypedValue res = new TypedValue();
- getContext().getTheme().resolveAttribute(
- R.attr.dialogTitleIconsDecorLayout, res, true);
- layoutResource = res.resourceId;
- } else {
- layoutResource = R.layout.screen_title_icons;
- }
- // XXX Remove this once action bar supports these features.
- removeFeature(FEATURE_ACTION_BAR);
- // System.out.println("Title Icons!");
- } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
- && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
- // Special case for a window with only a progress bar (and title).
- // XXX Need to have a no-title version of embedded windows.
- layoutResource = R.layout.screen_progress;
- // System.out.println("Progress!");
- } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
- // Special case for a window with a custom title.
- // If the window is floating, we need a dialog layout
- if (mIsFloating) {
- TypedValue res = new TypedValue();
- getContext().getTheme().resolveAttribute(
- R.attr.dialogCustomTitleDecorLayout, res, true);
- layoutResource = res.resourceId;
- } else {
- layoutResource = R.layout.screen_custom_title;
- }
- // XXX Remove this once action bar supports these features.
- removeFeature(FEATURE_ACTION_BAR);
- } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
- // If no other features and not embedded, only need a title.
- // If the window is floating, we need a dialog layout
- if (mIsFloating) {
- TypedValue res = new TypedValue();
- getContext().getTheme().resolveAttribute(
- R.attr.dialogTitleDecorLayout, res, true);
- layoutResource = res.resourceId;
- } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
- layoutResource = a.getResourceId(
- R.styleable.Window_windowActionBarFullscreenDecorLayout,
- R.layout.screen_action_bar);
- } else {
- layoutResource = R.layout.screen_title;
- }
- // System.out.println("Title!");
- } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
- layoutResource = R.layout.screen_simple_overlay_action_mode;
- } else {
- // Embedded, so no decoration is needed.
- layoutResource = R.layout.screen_simple;
- // System.out.println("Simple!");
- }
- mDecor.startChanging();
- mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); //挑选出来的布局添加到DecorView中
- ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); //从DecorView找出id为com.android.internal.R.id.content的容器,提供给Activity使用。
- if (contentParent == null) {
- throw new RuntimeException("Window couldn't find content container view");
- }
- ......
- return contentParent;
- }
R.layout.test
这一步纯粹是为了看下DecorView布局文件长啥样
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:fitsSystemWindows="true"
- android:orientation="vertical">
- <ViewStub android:id="@+id/action_mode_bar_stub"
- android:inflatedId="@+id/action_mode_bar"
- android:layout="@layout/action_mode_bar"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:theme="?attr/actionBarTheme" />
- <!--真正存放Activity布局的容器-->
- <FrameLayout
- android:id="@android:id/content"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:foregroundInsidePadding="false"
- android:foregroundGravity="fill_horizontal|top"
- android:foreground="?android:attr/windowContentOverlay" />
- </LinearLayout>
DecorView.onResourcesLoaded
- void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
- if (mBackdropFrameRenderer != null) {
- loadBackgroundDrawablesIfNeeded();
- mBackdropFrameRenderer.onResourcesLoaded(
- this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
- mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
- getCurrentColor(mNavigationColorViewState));
- }
- mDecorCaptionView = createDecorCaptionView(inflater);
- final View root = inflater.inflate(layoutResource, null); //解析出布局文件
- if (mDecorCaptionView != null) {
- if (mDecorCaptionView.getParent() == null) {
- addView(mDecorCaptionView,
- new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
- }
- mDecorCaptionView.addView(root,
- new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
- } else {
- // Put it below the color views.
- addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); //添加到DecorView
- }
- mContentRoot = (ViewGroup) root; //mContentRoot保存的是整个跟布局容器
- initializeElevation();
- }
二、DecorView绘制到屏幕
Activity 执行到 onCreate 时并不可见,只有执行完 onResume 之后 Activity 中的内容才是屏幕可见状态。onCreate 阶段只是初始化了 Activity 需要显示的内容,而在 onResume 阶段才会将 PhoneWindow 中的 DecorView 真正的绘制到屏幕上。
在ActivityThread的handleResumeActivity方法中,调用WindowManager将decor作为窗口添加到 WMS 。
- @Override
- public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
- String reason) {
- ......
- if (a.mVisibleFromClient) {
- if (!a.mWindowAdded) {
- a.mWindowAdded = true;
- wm.addView(decor, l); //通过WindowManager将decor添加到WMS
- } 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);
- }
- }
- ......
实现WindowManager接口的是WindowManagerImpl类,从WINDOW_SERVICE注册时也能看出来。
- registerService(Context.WINDOW_SERVICE, WindowManager.class, new CachedServiceFetcher<WindowManager>() {
- @Override
- public WindowManager createService(ContextImpl ctx) {
- return new WindowManagerImpl(ctx);
- }});
WindowManagerImpl.addView调用mGlobal.addView方法,mGlobal是WindowManagerGlobal类型变量。
- @Override
- public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
- applyDefaultToken(params);
- mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
- }
WindowManagerGlobal.addView最终调的是ViewRootImpl的setView方法
- 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); //创建ViewRootImpl对象
- 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); //最终,调的是ViewRootImpl的setView方法
- } catch (RuntimeException e) {
- // BadTokenException or InvalidDisplayException, clean up.
- if (index >= 0) {
- removeViewLocked(index, true);
- }
- throw e;
- }
- }
- }
ViewRootImpl.setView
- public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
- synchronized (this) {
- if (mView == null) {
- ......
- 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(); //调用此方法后 ViewRootImpl 所关联的 View 也执行 measure - layout - draw 操作,确保在 View 被添加到 Window 上显示到屏幕之前,已经完成测量和绘制操作。
- ......
- try {
- mOrigWindowType = mWindowAttributes.type;
- mAttachInfo.mRecomputeGlobalAttributes = true;
- collectViewAttributes();
- // mWindowSession 的 addToDisplay 方法将 View 添加到 WMS 中。
- res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
- getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
- mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
- mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
- } 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();
- }
- }
- }
- ......
- }
- }
WindowSession实例获取,是IWindowSession类型,通过Binder机制调用System 进程中的 Session实现。
- 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;
- }
- }
addToDisplay真正实现。
- @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,
- InsetsState outInsetsState) {
- return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
- outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel,
- outInsetsState);
- }
至此,Window 已经成功的被传递给了 WMS。剩下的工作就全部转移到系统进程中的 WMS 来完成最终的添加操作。
三、触摸事件处理
ViewRootImpl 中的 setView 方法中,除了调用 IWindowSession 执行跨进程添加 View 之外,还有一项重要的操作就是设置输入事件的处理:
- public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
- ......
- // 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);
- ......
- }
最终会经过ViewPostImeInputStage的onProcess处理
- final class ViewPostImeInputStage extends InputStage {
- public ViewPostImeInputStage(InputStage next) {
- super(next);
- }
- @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);
- }
- }
- }
processPointerEvent方法,调用mView的dispatchPointerEvent 分发事件。mView是DecorView对象
- private int processPointerEvent(QueuedInputEvent q) {
- ......
- boolean handled = mView.dispatchPointerEvent(event);
- ......
- return handled ? FINISH_HANDLED : FORWARD;
- }
dispatchPointerEvent是View实现的,最终调的是dispatchTouchEvent方法。
- public final boolean dispatchPointerEvent(MotionEvent event) {
- if (event.isTouchEvent()) {
- return dispatchTouchEvent(event);
- } else {
- return dispatchGenericMotionEvent(event);
- }
- }
DecorView.dispatchTouchEvent最终调用PhoneWindow的Callback分发事件。
- @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);
- }
PhoneWindow的Callback是在Activity的attach时设置的
- 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) {
- attachBaseContext(context);
- mFragments.attachHost(null /*parent*/);
- mWindow = new PhoneWindow(this, window, activityConfigCallback);
- mWindow.setWindowControllerCallback(this);
- mWindow.setCallback(this); //将Activity对象传递给PhoneWindow
- ......
- }
下面就回到了我们熟悉的Activity的dispatchTouchEvent方法:
- public boolean dispatchTouchEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- onUserInteraction();
- }
- if (getWindow().superDispatchTouchEvent(ev)) { //调用PhoneWindow的superDispatchTouchEvent方法
- return true;
- }
- return onTouchEvent(ev);
- }
PhoneWindow.superDispatchTouchEvent,归根结底还是调的DecorView的superDispatchTouchEvent方法
- @Override
- public boolean superDispatchTouchEvent(MotionEvent event) {
- return mDecor.superDispatchTouchEvent(event);
- }
DecorView.superDispatchTouchEvent,调用ViewGroup的dispatchTouchEvent方法。
- public boolean superDispatchTouchEvent(MotionEvent event) {
- return super.dispatchTouchEvent(event);
- }
总结
1.整个过程Activity参与度很低,基本靠PhoneWindow实现。
2.onCreate阶段创建了DecorView,onResume阶段将DecorView添加到WMS并展示,ViewRootImple对象也是onResume阶段创建的,所以也解释了onCreate阶段子线程加载view并不会报错。
3.ViewRootImpl 的 setView 方法中主要完成两件事情:View 渲染(requestLayout)以及接收触屏事件。
4.一个 Activity 中有一个 window,也就是 PhoneWindow 对象,每一个 PhoneWindow 对应一个 ViewRootImple 对象。
本文转载自微信公众号「Android开发编程」