Android窗口机制

framework

Posted by Cc1over on January 10, 2019

Android窗口机制

窗口结构

  • 每一个Activity都包含一个Window对象,Window对象通常由PhoneWindow实现
  • PhoneWindow:将Decoriew设置为整个应用窗口的根View。是Window的实现类。它是Android中的最基本的窗口系统,每个Activity 均会创建一个PhoneWindow对象,是Activity和整个View系统交互的接口
  • DecorView:顶层视图,将要显示的具体内容呈现在PhoneWindow上. DecorView是当前Activity所有View的祖先,它并不会向用户呈现任何东西

Window、PhoneWindow、DecorView、setContentView理解

Window
/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window {
    ...
    @Nullable
    public View findViewById(@IdRes int id) {
        return getDecorView().findViewById(id);
    }

/**
 * Convenience for * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}
 * to set the screen content from a layout resource.  The resource will be * inflated, adding all top-level views to the screen. * * @param layoutResID Resource ID to be inflated.
 * @see #setContentView(View, android.view.ViewGroup.LayoutParams)
 */
     public abstract void setContentView(@LayoutRes int layoutResID);
     ...
}

Window是一个抽象类,它主要定义了一套标准的UI方法,比如添加背景,标题等等,而它的唯一实现类则是PhoneWindow

PhoneWindow
public class PhoneWindow extends Window implements MenuBuilder.Callback {

    private final static String TAG = "PhoneWindow";

    ...

    // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;

    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    private ViewGroup mContentParent;

    private ViewGroup mContentRoot;
    ...
}

可以看到,在PhoneWindow里面,出现了成员变量DecorView的而这里,DecorView则是PhoneWindow里面的一个内部类,它是继承与FrameLayout

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {

        /* package */int mDefaultOpacity = PixelFormat.OPAQUE;

        /** The feature ID of the panel, or -1 if this is the application's DecorView */
        private final int mFeatureId;

        private final Rect mDrawingBounds = new Rect();

        private final Rect mBackgroundPadding = new Rect();

        private final Rect mFramePadding = new Rect();

        private final Rect mFrameOffsets = new Rect();
        ....
 }

既然是FrameLayout,也就可以加载布局文件,也就是说,我们那些标题栏,内容栏,顶级上看是加载在DecorView上的。而DecorView则是由PhoneWindow负责添加

@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,并添加到mContentParent上
            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 {
            //将要加载的资源添加到mContentParent上
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            //回调通知表示完成界面加载
            cb.onContentChanged();
        }
    }

在我们熟悉的这个setContentView方法中,我们得到了几个讯息:

  • 首次通过installDecor初始化DecorView

  • 通过LayoutInflater将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中

  • 通过callback的形式表示界面完成加载

暂时在这段代码里面看不到Window的引用也看不到DecorView和Window之间的关系,但其实玄机就藏在这个callback里面

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback { ... }
 public void onContentChanged() {
 }

从上面的两段代码可以看到setContentView方法回调的Callback其实就是Activity本身,里面声明了当界面更改触摸时调用的各种方法,而onContentChanged方法是一个空实现,也就是说我们可以通过 重写这个方法监听布局内容的改变

自此,已经把Window,PhoneWindow,decorView之间的关系理清了,在这个小标题的最后小总结一波

  • Window是一个抽象类,提供了各种窗口操作的方法,比如设置背景标题ContentView等等
  • PhoneWindow则是Window的唯一实现类,它里面实现了各种添加背景主题ContentView的方法,内部通过DecorView来添加顶级视图
  • DecorView,顶级视图,继承与FramentLayout,setContentView则是添加在它里面的@id/content里

Window和WindowManager的创建与Activity

Window上面也有了一定的介绍,这里唯一比较陌生的名词就是WindowManager了,WindowManager是一个接口类,继承自接口ViewManager,从名称就知道它是用来管理Window的,它的实现类为WindowManagerImpl。如果我们想要对Window进行添加和删除就可以使用WindowManager,具体的工作都是由WMS来处理的,WindowManager和WMS通过Binder来进行跨进程通信,WMS作为系统服务有很多API是不会暴露给WindowManager的,这一点与ActivityManager和AMS的关系有些类似

Window包含了View并对View进行管理,Window用虚线来表示是因为Window是一个抽象概念,并不是真实存在,Window的实体其实也是View。WindowManager用来管理Window,而WindowManager所提供的功能最终会由WMS来进行处理。WindowManager和Window的关系已经明确了,除了上面提到的callback,Activity与他们之间的关联,我们需要从Activity的启动过程说起,在Activity启动过程中会调用ActivityThread的performLaunchActivity方法,performLaunchActivity方法中又会调用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) {
        attachBaseContext(context);
        mFragments.attachHost(null /*parent*/);
        mWindow = new PhoneWindow(this, window);//1
        ...
         /**
         *2
         */
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
      ...

Window通过setWindowManager与WindowManager构成关联

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

创建WindowManagerImpl时将创建它的Window作为参数传了进来,这样WindowManagerImpl就持有了Window的引用,就可以对Window进行操作,而WindowManager继承自ViewManager,ViewManager的实现类还有ViewGroup,因此,就可以通过这套接口向Window中添加View

/** Interface to let you add and remove child views to an Activity. To get an instance
  * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
  */
public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

而其实在WindowManagerImp的实现中,它使用了桥接模式,把这些工作委托给了WindowManagerGlobal,而这个类也是在Activity启动的过程中初始化,而WindowManagerGlobal则是以工厂的形式向外提供自己的实例

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

这个WindowManagerGlobal的addView方法的实现在下一个小标题在阐述,这里先做个小结

通过如上的源码分析,Window和WindowManager的关系如下图所示

ViewRootImp与View与WindowManager

接着看上面WindowManagerGlobal的addView方法

public void addView(View view, ViewGroup.LayoutParams params,
          Display display, Window parentWindow) {
    ...//参数检查
      final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
      if (parentWindow != null) {
          parentWindow.adjustLayoutParamsForSubWindow(wparams);
      } else {
      ...
      }

      ViewRootImpl root;
      View panelParentView = null;
       ...
          root = new ViewRootImpl(view.getContext(), display);
          view.setLayoutParams(wparams);
          mViews.add(view);
          mRoots.add(root);//3
          mParams.add(wparams);
      }
      try {
          root.setView(view, wparams, panelParentView);
      } catch (RuntimeException e) {
         ...
      }
  }

在WindowManagerGlobal的addView中,最后调用了ViewRootImp的setView方法,而这个ViewRootImp是一个视图层次结结构的顶部,它实现了View和WindowManager之间的协议,所以其实在WindowManagerGlobal中的方法都会找到ViewRootImp的身影,所以实际的调用顺序其实是WindowManagerImpl -> WindowManagerGlobal -> ViewRootImpl

除此之外,ViewRootImp的职责还有

  • View树的根并管理View树
  • 触发View的测量、布局和绘制
  • 输入事件的中转站
  • 管理Surface
  • 负责与WMS进行进程间通信

而WindowManagerGlobal调用到ViewRootImp中之后,其实剩下的工作就是ViewRootImp通过Binder与WMS通信的过程了

梳理总结

WindowManager是继承于ViewManager接口的,而ViewManager提供了添加View,删除View,更新View的方法。就拿setContentView来说,当Activity的onCreate调用到了setContentView后,其实View并没有真正被绘制,setContentView只是把需要添加的View的结构添加保存在DecorView中。此时的DecorView还并没有被绘制

DecorView真正的绘制显示是在activity.handleResumeActivity方法中(也就是Activity生命周期的onResume方法中)DecorView被添加到WindowManager时候,也就是调用到windowManager.addView(decorView)。而在windowManager.addView方法中调用到windowManagerGlobal.addView,开始创建初始化ViewRootImpl,再调用到viewRootImpl.setView,最后是调用到viewRootImpl的performTraversals来进行view的绘制(measure,layout,draw),这个时候View才真正被绘制出来

这也就是为什么我们在onCreate方法中调用view.getMeasureHeight() = 0的原因,我们知道activity.handleResumeActivity最后调用到的是activity的onResume方法,但是按上面所说在onResume方法中调用就可以得到了吗,答案肯定是否定的,因为ViewRootImpl绘制View并非是同步的,而是异步(Handler)

参考资料:

  • https://www.jianshu.com/p/6afb0c17df43
  • http://liuwangshu.cn/framework/wm/1-windowmanager.html