Android Espresso是如何获取View? (二)—— Android源码篇

616

上期回顾

看完上篇文章 Android Espresso是如何获取View? (一)—— Espresso源码篇 我们知道了Espresso通过隐藏的API android.view.WindowManagerGlobal#getInstance().mViews 来获取所有的RootView,但是Android源码WindowManagerGlobal这部分是如何实现的呢?
本文基于 AOSP android-7.1.2_r28 的代码进行分析,虽然和后续Android源码稍微有些出入,但是基本一致。

Dialog

我们先从简单的入手,AlertDialog是如何将RootView添加到window,显示View的。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mAlert.installContent();
}

我们看到AlertDialog 在onCreate生命周期的时候初始化了ContentView,调用了AlertControllerinstallContent方法。

public void installContent() {
    int contentView = selectContentView();
    mWindow.setContentView(contentView);
    setupView();
}

从Window class的注释

The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.

我们可以了解到,PhoneWindow是唯一的实现类,我们看看PhoneWindowsetContentView实现方法

@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) {
        // 初始化DecorView,这个就是我们之前一直提到的RootView
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
    ...
}

最后调用Dialog#show()方法显示

public void show() {
    ...
    // 将DecorView显示到到Window
    mWindowManager.addView(mDecor, l);
    mShowing = true;

    sendShowMessage();
}

这个mWindowManager获取方式就是大家耳熟能详的mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);,那他对应的是哪一个Service呢?我们看看WINDOW_SERVICE注册的地方

registerService(Context.WINDOW_SERVICE, WindowManager.class,
        new CachedServiceFetcher<WindowManager>() {
    @Override
    public WindowManager createService(ContextImpl ctx) {
        return new WindowManagerImpl(ctx);
    }});

原来他的实现类是WindowManagerImpl,我们直接看看他的addView方法

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

这个mGlobal是啥呢?我们定睛一看

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

卧槽,终于看到了上篇文章中熟悉的WindowManagerGlobal,到这里我们离真相只有一步之遥了,最后我们看看WindowManagerGlobal#addView方法

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
    ...
    synchronized (mLock) {
        ....
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
    }
   ....
}

果然如上篇文章描述,最后将DecorView (RootView) 添加到 mViews集合中去。

Activity

我们一般设置是通过Activity#setContentView设置Activity的view

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

实际上还是调用了Window#setContentView,上文中已经解析,这里不在描述。那View是啥时候被添加到window的呢?
我们知道ActivityonResume生命周期开始处理view,且Activity生命周期都是由UI线程触发,UI线程又是在ActivityThread中初始化和分发消息,这块代码我们不是我们这次解析的重点,我们直接看ActivityonResume生命周期处理逻辑

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    ...
    if (a.mVisibleFromClient && !a.mWindowAdded) {
        a.mWindowAdded = true;
        wm.addView(decor, l);
    }
}

最后也是通过WindowManager将view添加到window中,后面的逻辑上文已经解析了,大家应该都知道了,我在这里不就在重复了。

总结

看上去是很简单的过程,但是要在庞大的Android源码中抽丝剥茧出我们想要的东西还是有点难度的。

引申

俗话说学以致用,通过阅读源码我们知道了这个获取所有RootView的过程.但是这个有什么用呢?我们实际的开发过程中会有用到的地方吗?其实这个还是很有用的,比如Android的全埋点方案、APP的截图都需要获取所有RootView。
同学们有没有其他的场景可以使用到呢?欢迎在评论区畅所欲言!