Android底层Android系统框架

《深入理解Android:卷三》深入理解控件系统读书笔记(上)

2018-08-10  本文已影响6人  affyzh

以下是阅读《深入理解Android:卷三》第6章 深入理解控件系统时的阅读要点,按照章节做下标记,以供后续查阅。

6.2 深入理解WindowManager

WindowManager的主要作用是封装API供开发者使用,以便将控件作为一个窗口添加到系统中。

6.2.1 WindowManager的创建与体系结构

WindowManager是一个继承于ViewManager的接口(其实现为WindowManagerImpl,而WindowManagerImpl内部则通过调用WindowManagerGlobal实现相应逻辑),后者的另一个实现者是ViewGroup。因此可以将两者进行类比:设想WindowManager是一个ViewGroup,其区域为整块屏幕,而其中的各个窗口就是一个一个View。WindowManager通过WMS的帮助,将这些View按照布局参数(LayoutParams)显示到屏幕的特定位置。两者的工作核心是一样的,因此都继承自ViewManager。开发者可以通过调用Context.getSystemService(Context.WINDOW_SERVICE)来获取WindowManager的实例。其原理是ContextImpl在其静态构造函数中初始化了一系列的ServiceFetch而来响应getSystemService的调用并创建对应的服务实例。

/** 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
{
    /**
     * Assign the passed LayoutParams to the passed View and add the view to the window.
     * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
     * errors, such as adding a second view to a window without removing the first view.
     * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
     * secondary {@link Display} and the specified display can't be found
     * (see {@link android.app.Presentation}).
     * @param view The view to be added to this window.
     * @param params The LayoutParams to assign to view.
     */
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}
WindowManager结构体系.png

6.2.2 通过WindowManagerGlobal添加窗口

6.2.3 更新窗口布局

窗口的布局参数发生变化时,需要通知WMS进行相应的调整,该工作在WindowManagerGlobal中由updateViewLayout()函数完成。其逻辑不过是保存新的布局参数,然后调用ViewRootImpl.setLayoutParams()进行更新

6.2.4 删除窗口

ViewRootImpl的生命从setView()开始,到die()结束。WindowManagerGlobal将窗口的创建、销毁、布局更新等任务都交给ViewRootImpl完成

6.3 深入理解ViewRootImpl

6.3.1 ViewRootImpl的创建及其重要的成员


对于ViewRootImpl.setView(),它是创建窗口,建立输入事件接收机制的场所。同时,触发第一次“遍历”操作的消息已经发送给主线程,在随后的第一次“遍历”完成之后,ViewRootImpl将会完成对控件树的第一次测量、布局,并从WMS获取窗口的Surface以进行控件树的初次绘制工作:

6.3.2 控件系统的心跳:performTraversals()

View类及其子类的onMeasure()、onLayout()、onDraw()等回调都是在performTravelsals()的执行过程中直接或间接地引发。

1.performTraversals()的工作阶段

2. 预测量与测量原理
2.1 测量参数的候选

MeasureSpec的结构
其中1-30位给出了父控件建议尺寸。该值对测量结果的影响依照SPEC_MODE的不同而不同。SPEC_MODE取值取决于控件的LayoutParams.width/height的设置:

ViewRootImpldesiredWindowWidth/Height为候选,为控件树的根mView选取其MeasureSpec

2.2 测量协商
  measureHierarchy()用户测量整个控件树。该方法有协商机制,先使用该方法所期望的尺寸(该期望值定义为一个系统资源。可以在system/framework/base/core/res/res/values/config.xml找到它的定义)限制尝试对控件树进行测量,并用测量结果检查控件树是否能够在此限制下满足其充分显示内容的要求。如果无法满足,则进行让步,放宽限制,然后再次进行测量,再做检查。倘若仍然不满足则再度进行让步。二次测量后仍然不满意的话,放弃所有限制做最终测量。最后一次将不再检查控件树是否满意,因为即使不满意也没有更多空间供其使用了。
特别地,对于非悬浮窗口(LayoutParams.width被设置为MATCH_PARENT),不存在协商过程,直接使用给定的desiredWindowWidth/Height进行测量即可。否则,measureHierarchy()可以连续进行两次让步。

2.3 测量原理
  在perfomeMeasure()中体现,它也是直接调用了mView.measure()

onMeasure()算法的一些实现原则:

2.4 确定是否需要改变窗口尺寸
必要条件:

在满足上述两个条件的情况下,以下满足其一

3. 布局窗口与最终测量

3.1 布局窗口的条件
  倘若不需要进行窗口布局,则WMS不会再预测量之后修改窗口的尺寸,也就不需要进行最终测量。以下4大条件满足其一即进入布局窗口阶段:

3.2 布局窗口前的准备工作

3.3 布局窗口
  ViewRootImpl使用'relayoutWindow()'进行窗口布局

private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {

        float appScale = mAttachInfo.mApplicationScale;
       ········
        int relayoutResult = mWindowSession.relayout(
                mWindow, mSeq, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f),
                viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
                mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
                mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame,
                mPendingMergedConfiguration, mSurface);

       ········
        return relayoutResult;
    }

appScale用于在兼容模式下显示一个窗口。当窗口在设备的屏幕尺寸下显示异常时,Android会尝试使用兼容尺寸显示它(例如320x480),此时测量与布局控件树都将以此兼容尺寸为准。

3.4 布局窗口后的处理——Insets
ContentInsets发生变化时,会进行如下动作:

3.5 布局窗口后的处理——Surface
  处理Surface的变化是为了硬件加速而服务的。其原因在于以软件方式进行绘制时可以通过Surface.lockCanvas()函数直接获得,因此仅需在绘制前判断一下Surface.isValid()决定是否绘制即可。而在硬件加速绘制的情况下,将绘制过程委托给HardwareRenderer并且需要将其与一个有效的Surface进行绑定。因此每当Surface的状态变换都需要通知HardwareRenderer

3.6 总结
  布局窗口阶段得意进行的原因是控件系统有修改窗口属性的需求。如第一次“遍历”需要确定窗口的尺寸以及一块Surface,预测量结果与窗口档期啊尺寸不一致需要进行窗口尺寸更改,mView可见性发生变化需要将窗口隐藏或显示,LayoutParams发生变化需要WMS以新的参数进行重新布局。而最终测量阶段得意进行的原因是窗口布局阶段确定的窗口尺寸与控件树的期望尺寸不一致,控件树需要对窗口尺寸进行妥协。

4.布局控件树阶段
  控件的实际位置与尺寸由ViewmLeft、mTop、mRight、mBottom四个成员变量存储的坐标值来表示
布局控件树阶段主要做了两件事:

4.1 控件树布局
View.layout()方法主要做了三件事:

对比测量和布局两个过程:

4.2 窗口透明区域
  该功能主要是为了SurfaceView服务。所谓的透明区域是指Surface上的一块特定区域,在SurfaceFlinger进行混成时,Surface上的这个块区域将会被忽略,就好像在Surface上打了一个洞:

带有透明区域的窗口与其他窗口合成后的效果
  当控件树中存在SurfaceView时,它会通过调用ViewParent.requestTransparentRegion()方法启用这一机制。这个方法的调用会沿着控件树回溯到ViewRootImpl,并沿途将PFLAG_REQUEST_TRANSPARENT_REGIONS标记加入父控件的mPravateFlags字段。此标记会导致ViewRootImpl完成控件树的布局后将进行透明区域的计算与设置。
透明区域的计算由View.gatherTransparentRegion()完成。透明区域的计算采用了挖洞法,及默认整个窗口都是透明区域,在View.gatherTransparentRegion()遍历到一个控件时,如果这个控件有内容需要绘制,则将其所在的区域从当前透明区域中删除,就好似在纸上裁出一个洞一样。当遍历完成后,剩余的区域就是最终的透明区域。
  这个透明区域将会被设置到WMS中,进而被WMS设置给SurfaceFlingerSurfaceFlinger在进行Surface混合时,本窗口的透明区域部分会被忽略,从而用户能够透过这部分区域看到后面窗口(如SurfaceView的窗口)的内容。
  注意,SurfaceView刚好相反,它是将自身并入到透明区。

5. 绘制阶段
  整个控件树的绘制阶段在performDraw()中执行,同其他阶段一样,绘制也是有可能被跳过的:

6. 总结

performTraversals()的5个工作阶段的工作流程

由于篇幅限制,深入了解控件树的绘制将放到下一篇继续。

上一篇 下一篇

猜你喜欢

热点阅读