底层剖析 Window 、Activity、 View 三者关系

无论工做几年的 Android 工程师,或多或少都据说过 Window 的概念,而且隐隐约约感受它在 Activity 与 View 之间应该发挥着某种链接的做用。可是若是须要说出这 3 者之间的关系,多数工程师不知道从何下手。java

Activity 的 setContentViewapp

Activity 是 Android 开发人员使用最频繁的 API 之一,最初在接触 Android 开发时,我始终认为它就是负责将 layout 布局中的控件渲染绘制出来的。缘由很简单,每当咱们想显示一个新的界面时,都是经过 start 一个新的 Activity 方式;对于想显示的内容或者布局,也只须要在 Activity 中添加一行 setContentView 便可,剩下的 Activity 都自动帮咱们搞定。可是咱们历来没有去建立一个 Window 来绑定 UI 或者 View 元素。ide

直到我点开 setContentView 源码的那一刻:布局

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

显然 Activity 几乎什么都没作,将操做直接交给了一个 Window 来处理。getWindow 返回的是 Activity 中的全局变量 mWindow,它是 Window 窗口类型。那么它是何时赋值的呢?post

记得上篇文章中分析 startActivity 的过程,最终代码会调用到 ActivityThread 中的 performLaunchActivity 方法,经过反射建立 Activity 对象,并执行其 attach 方法。Window 就是在这个方法中被建立,详细代码以下:学习

@UnsupportedAppUsage
    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, IBinder assistToken) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();

        mMainThread = aThread;
        ...

        mWindow.setWindowManager(
                (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();
        mCurrentConfig = config;

在 Activity 的 attach 方法中将 mWindow 赋值给一个 PhoneWindow 对象,实际上整个 Android 系统中 Window 只有一个实现类,就是 PhoneWindow。优化

接下来调用 setWindowManager 方法,将系统 WindowManager 传给 PhoneWindow,以下所示:ui

 public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated;
        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 的引用。this

PhoneWindow 的 setContentViewspa

Activity 将 setContentView 的操做交给了 PhoneWindow,看下PhoneWindow的setContentView方法实现过程:

@Override
    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();
        } 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;
    }

解释说明:

  • 标红的1 处调用若是 mContentParent 为 null,则调用 installDecor 初始化 DecorView 和 mContentParent。
  • 标红的2处将咱们调用 setContentView 传入的布局添加到 mContentParent 中。

 

能够看出在 PhoneWindow 中默认有一个 DecorView(其实是一个 FrameLayout),在 DecorView 中默认自带一个 mContentParent(其实是一个 ViewGroup)。咱们本身实现的布局是被添加到 mContentParent 中的,所以通过 setContentView 以后,PhoneWindow 内部的 View 关系以下所示:

 

 

 

目前为止 PhoneWindow 中只是建立出了一个 DecorView,并在 DecorView 中填充了咱们在 Activity 中传入的 layoutId 布局,但是 DecorView 尚未跟 Activity 创建任何联系,也没有被绘制到界面上显示。那 DecorView 是什么时候被绘制到屏幕上的呢?

刚接触 Android,学习生命周期时,常常会看到相关文档介绍 Activity 执行到 onCreate 时并不可见,只有执行完 onResume 以后 Activity 中的内容才是屏幕可见状态。形成这种现象的缘由就是,onCreate 阶段只是初始化了 Activity 须要显示的内容,而在 onResume 阶段才会将 PhoneWindow 中的 DecorView 真正的绘制到屏幕上。

在 ActivityThread 的 handleResumeActivity 中,会调用 WindowManager 的 addView 方法将 DecorView 添加到 WMS(WindowManagerService) 上,以下所示:

@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);
                } 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);
                }
            }

WindowManger 的 addView 结果有两个:

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

WindowManager 的 addView

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

WindowManager 是接口类型,它真正的实现者是 WindowManagerImpl 类,看一下它的 addView 方法以下:

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

  ...

 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;
            }

WindowManagerImpl 也是一个空壳,它调用了 WindowManagerGlobal 的 addView 方法。

WindowMangerGlobal 是一个单例,每个进程中只有一个实例对象。如上图红框中所示,在其 addView 方法中,建立了一个最关键的 ViewRootImpl 对象,而后经过 ViewRootImpl 的 setView 方法将 view 添加到 WMS 中。

ViewRootImpl 的 setView

/**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;

                mAttachInfo.mDisplayState = mDisplay.getState();
                mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);

                mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
                mFallbackEventHandler.setView(view);
                mWindowAttributes.copyFrom(attrs);
                if (mWindowAttributes.packageName == null) {
                    mWindowAttributes.packageName = mBasePackageName;
                }
                attrs = mWindowAttributes;
                setTag();

                ...
                mAdded = true;
                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();
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel, mTempInsets);
                    setFrame(mTmpFrame);
                } 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();
                    }
                }

                ...
            }
        }
    }

解释说明:

  • 标红的 1 处的 requestLayout 是刷新布局的操做,调用此方法后 ViewRootImpl 所关联的 View 也执行 measure - layout - draw 操做,确保在 View 被添加到 Window 上显示到屏幕以前,已经完成测量和绘制操做。
  • 标红的2 处调用 mWindowSession 的 addToDisplay 方法将 View 添加到 WMS 中。

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

 

@UnsupportedAppUsage
    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    // Emulate the legacy behavior.  The global instance of InputMethodManager
                    // was instantiated here.
                    // TODO(b/116157766): Remove this hack after cleaning up @UnsupportedAppUsage
                    InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) { ValueAnimator.setDurationScale(scale); } });
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }

sWindowSession 其实是 IWindowSession 类型,是一个 Binder 类型,真正的实现类是 System 进程中的 Session。上面代码中标红的就是用 AIDL 获取 System 进程中 Session 的对象。其 addToDisplay 方法以下:

 

 

 

 图中的 mService 就是 WMS。至此,Window 已经成功的被传递给了 WMS。剩下的工做就所有转移到系统进程中的 WMS 来完成最终的添加操做。

 

再看 Activity

以前提到 addView 成功有一个标志就是可以接收触屏事件,经过对 setContentView 流程的分析,能够看出添加 View 的操做实质上是 PhoneWindow 在全盘操做,背后负责人是 WMS,反之 Activity 自始至终没什么参与感。可是咱们也知道当触屏事件发生以后,Touch 事件首先是被传入到 Activity,而后才被下发到布局中的 ViewGroup 或者 View。那么 Touch 事件是如何传递到 Activity 上的呢?

ViewRootImpl 中的 setView 方法中,除了调用 IWindowSession 执行跨进程添加 View 以外,还有一项重要的操做就是设置输入事件的处理:

// 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);

如上所示,设置了一系列的输入通道。一个触屏事件的发生是由屏幕发起,而后通过驱动层一系列的优化计算经过 Socket 跨进程通知 Android Framework 层(实际上就是 WMS),最终屏幕的触摸事件会被发送到上图中的输入管道中。

这些输入管道其实是一个链表结构,当某一个屏幕触摸事件到达其中的 ViewPostImeInputState 时,会通过 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);
                }
            }
        }
 private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;

            mAttachInfo.mUnbufferedDispatchRequested = false;
            mAttachInfo.mHandlingPointerEvent = true;
            boolean handled = mView.dispatchPointerEvent(event);
            maybeUpdatePointerIcon(event);
            maybeUpdateTooltip(event);
            mAttachInfo.mHandlingPointerEvent = false;
            if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
                mUnbufferedInputDispatch = true;
                if (mConsumeBatchedInputScheduled) {
                    scheduleConsumeBatchedInputImmediately();
                }
            }
            return handled ? FINISH_HANDLED : FORWARD;
        }

能够看到在 onProcess 中最终调用了一个 mView的dispatchPointerEvent 方法,mView 实际上就是 PhoneWindow 中的 DecorView,而 dispatchPointerEvent 是被 View.java 实现的,以下View中dispatchPointerEvent方法所示:

@UnsupportedAppUsage
    public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }

DecorView中的dispatchTouchEvent方法代码:

@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的dispatchTouchEvent 方法,那这个 Callback 是否是 Activity 呢?

在启动 Activity 阶段,建立 Activity 对象并调用 attach 方法时,有以下一段代码:

@UnsupportedAppUsage
    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, IBinder assistToken) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

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

果真将 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);
    }

PhoneWindow中

@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

Touch 事件在 Activity 中只是绕了一圈最后仍是回到了 PhoneWindow 中的 DecorView 来处理。剩下的就是从 DecorView 开始将事件层层传递给内部的子 View 中了。

 

总结

文中主要经过 setContentView 的流程,分析了 Activity、Window、View 之间的关系。整个过程 Activity 表面上参与度比较低,大部分 View 的添加操做都被封装到 Window 中实现。而 Activity 就至关于 Android 提供给开发人员的一个管理类,经过它可以更简单的实现 Window 和 View 的操做逻辑。

最后再简单列一下整个流程须要注意的点:

1》一个 Activity 中有一个 window,也就是 PhoneWindow 对象,在 PhoneWindow 中有一个 DecorView,在 setContentView 中会将 layout 填充到此 DecorView 中。
2》一个应用进程中只有一个 WindowManagerGlobal 对象,由于在 ViewRootImpl 中它是 static 静态类型。
3》每个 PhoneWindow 对应一个 ViewRootImple 对象。
4》WindowMangerGlobal 经过调用 ViewRootImpl 的 setView 方法,完成 window 的添加过程。
5》ViewRootImpl 的 setView 方法中主要完成两件事情:View 渲染(requestLayout)以及接收触屏事件。

 

————来自拉勾教育笔记

相关文章
相关标签/搜索