Window、Activity、View之间的关系

187 阅读6分钟

在之前的Activity的启动流程(api29)中提到,WindowManager会在Activity启动过程中初始化并与Activity关联,代码如下:

public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {
    // ...省略中间代码
    // 初始化Activity的WindowManager,每个Activity对应一个Window
    WindowManagerGlobal.initialize();
    
    GraphicsEnvironment.hintActivityLaunch();
    
    // 创建并显示Activity
    final Activity a = performLaunchActivity(r, customIntent);
    // ...略略略
}

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    // ...省略中间代码
    try {
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);

        // ...省略中间代码
        // 调用Activity的attach方法关联Activity和Context,创建PhoneWindow并关联Activity,这个后续文章将会分析
        activity.attach(appContext, this, getInstrumentation(), r.token,
                r.ident, app, r.intent, r.activityInfo, title, r.parent,
                r.embeddedID, r.lastNonConfigurationInstances, config,
                r.referrer, r.voiceInteractor, window, r.configCallback,
                r.assistToken);
        
        // ...略略略
    } // ...略略略
    return activity;
}

其中WindowManagerGlobal.initialize()会通过AIDL的方式创建WindowManagerService,其代码如下:

public static IWindowManager getWindowManagerService() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowManagerService == null) {
            sWindowManagerService = IWindowManager.Stub.asInterface(
                    ServiceManager.getService("window"));
            try {
                if (sWindowManagerService != null) {
                    ValueAnimator.setDurationScale(
                            sWindowManagerService.getCurrentAnimatorScale());
                }
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        return sWindowManagerService;
    }
}

现在我们再来关注一下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, IBinder assistToken) {
    // 关联Context
    attachBaseContext(context);

    // 关联Fragment
    mFragments.attachHost(null /*parent*/);

    // 创建PhoneWindow,Window的唯一子类
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    
    // 根据AndroidManifest给Activity设置的SoftInpuMode赋值
    if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
        mWindow.setSoftInputMode(info.softInputMode);
    }
    
    // ...省略中间代码

    // 获取WindowManager并赋值给创建的Window
    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;

    mWindow.setColorMode(info.colorMode);

    setAutofillOptions(application.getAutofillOptions());
    setContentCaptureOptions(application.getContentCaptureOptions());
}

可以看到在attach方法里面创建了一个PhoneWindow实例,PhoneWindow是抽象类Window的唯一子类。然后给当前Window设置SoftInputMode,再通过Window.setWindowManager把WindowManager赋值给Window,代码如下:

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

至此,Window、WindowManager、Activity就关联起来了。那么Window、Activity和View之间又有什么联系呢?我们都知道每一个Activity都要在onCreate方法中调用setContentView去给它设置一个布局,那就看看它的代码是怎么实现的吧:

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

可以看到,Activity本身没做太多事情,它把主要事务都交给了Window去做。而前面已经说过Activity启动的时候会创建并绑定一个PhoneWindow,所以这些事情实际上是由PhoneWindow完成的。

PhoneWindow.setContentView

@Override
    public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        // 初始化DecorView
        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 {
        // XML解析,反射创建View
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

从上面可以看到,如果mContentParent为空,则调用installDecor方法初始化DecorView(实际上是一个FrameLayout)。初始化完DecorView后将其与PhoneWindow关联起来,并在DecorView填充了系统主题和我们在Activity传入的布局。此时View还没有被绘制到界面上显示出来,因为生命周期走到onCreate时界面并不可见,只有执行完onResume之后屏幕的内容才会显示出来。

在 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;
            // 添加DecorView
            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);
        }
    }
    
    // ...略略略
}

WindowManager完成addView之后DecorView被绘制到屏幕上并可以接收屏幕触摸事件。

WindowManager.addView

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

WindowManager 是接口类型,上文中我们也了解到它真正的实现者是 WindowManagerImpl 类,它的addView方法实际上是调用了WindowManagerGlobal的addView方法,看一看它的代码如下:

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    // ...省略中间代码
    
    ViewRootImpl root;
    View panelParentView = null;
    
    synchronized (mLock) {
        // ...省略中间代码
        
        // 创建ViewRootImpl
        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 {
            // 添加View
            root.setView(view, wparams, panelParentView);
        } 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) {
            // ...省略中间代码
            
            // 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.
            // 1.刷新布局,开始View的绘制流程
            requestLayout();
            
            // ...省略中间代码
            try {
                mOrigWindowType = mWindowAttributes.type;
                mAttachInfo.mRecomputeGlobalAttributes = true;
                collectViewAttributes();
                // 2.将View添加到WMS中
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                        mTempInsets);
                setFrame(mTmpFrame);
            }
            // ...略略略
        }
    }
}

解释:

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

WindowSession是WindowManagerGlobal中的单例对象,在ViewRootImpl的构造方法中会调用WindowManagerGlobal.getWindowSession方法,代码如下:

public static IWindowSession getWindowSession() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowSession == null) {
            try {
                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。其addToDisplay方法调用了WindowManagerService的addWindow方法,至此Window已经成功的被传递给了WMS。剩下的工作就全部转移到系统进程中的WMS来完成最终的添加操作。

再看ViewRootImpl.setView

在ViewRootImpl.setView方法调用完WindowSession.addToDisplay后还有一系列设置输入事件管道的操作:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            // ...省略中间代码
            
            // 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;
        }
    }
}

InputStage是一个链表结构,事件处理完后可以决定是继续分发事件还是结束事件。当某一个屏幕触摸事件到达其中的 ViewPostImeInputState 时,会经过 onProcess 来处理:

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

可以看到processPointerEvent里面调用了mView.dispatchPointerEvent进行点击事件的分发,而这里的mView就是刚刚在setView方法里面传入的DecorView。其代码如下:

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

// DecorView.java
@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方法。Window.Callback是一个接口,Activity实现了这个接口,并且在启动Activity阶段,创建Activity对象并调用attach方法创建关联PhoneWindow的时候把它本身传给了PhoneWindow。因此最终调用的就是Activity里面的dispatchTouchEvent方法,把点击事件传递给Activity:

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

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

可以看到,Activity又调用了PhoneWindow的superDispatchTouchEvent方法,把点击事件传递回给DecorView处理。接下来就是从DecorView开始将事件层层传递到目标View中。

总结

这篇文章主要分析了Activity、Window、View之间的关系:

  1. 一个Activity对应一个PhoneWindow,PhoneWindow有一个DecorView,在调用setContentView方法后会将layout填充到DecorView中。
  2. 一个应用进程中只有一个WindowManagerGlobal对象,WindowManagerGlobal通过调用ViewRootImpl的setView方法完成Window的添加过程。
  3. 每个Phonewin对应一个ViewRootImpl对象,通过ViewRootImpl的setView方法主要完成布局绘制和触摸事件的接收处理。