1.performTraversals()的调用链
- ActivityThread 的handleResumeActivity() 里调用 WindowManagerImpl的addView(decor, l)方法,然后调用到WindowManagerGlobal的addView方法
- WindowManagerGlobal的addView方法中创建ViewRootImpl,并调用root.setView(view, wparams, panelParentView);
- View.AttachInfo是在ViewRootImpl的构造方法中创建的
- ViewRootImpl 的 setView()方法里会调用requestLayout(),然后调用scheduleTraversals() -> doTraversal() -> performTraversals()
1 | void scheduleTraversals() { |
2.view/activity的生命周期关系
在MyActivity中布局添加一个CustomView,然后在各个回调中添加log,会有如下调用顺序:
1 | E/MyActivity: onCreate: |
3.自定义view
1. MeasureSpec
模式 | 意义 | 对应 |
---|---|---|
EXACTLY | 精准模式,View需要一个精确值,这个值即为MeasureSpec当中的Size | match_parent |
AT_MOST | 最大模式,View的尺寸有一个最大值,View不可以超过MeasureSpec当中的Size值 | wrap_content |
UNSPECIFIED | 无限制,View对尺寸没有任何限制,View设置为多大就应当为多大 | 一般系统内部使用 |
对于DecorView而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同决定;对于普通的View,它的MeasureSpec由父视图的MeasureSpec和其自身的LayoutParams共同决定。
2.直接继承View
需要重写onMeasure方法,因为View中并没有对AT_MOST和EXACTLY两个模式做出区分,也就是说View在wrap_content和match_parent两个模式下是完全相同的,都会是match_parent
1 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
重写onMeasure方法
1 | /** |
- 判断View在屏幕中是否可见并获取可见区域
1. View.getVisibility()
只能获取当前View的visibility的值,不受他的父View的visibility的影响(如果view的visibility为true,即使父view的View的visibility为false,该方法仍返回true,其实这个View对用户是不可见的)
2. View.isShown()
1 | /** |
- 这个方法递归地去检查这个View以及它的parentView的Visibility属性是不是等于View.VISIBLE
- 另外这个方法还在递归的检查过程中,检查了parentView == null,也就是说所有的parentView都不能为null,否则就说明这个View根本没有被addView过(比如使用Java代码创建界面UI时,可能会先new一个View,然后根据条件动态地把它add到一个ViewGroup中),那肯定是不可能对用户可见的。
3. View.getGlobalVisibleRect
1 | Rect rect = new Rect(); |
- 当这个View只要有一部分仍然在屏幕中(没有被父View遮挡),那么将把没有被遮挡的那部分区域保存在rect对象中返回,且方法的返回值是true,即visibility=true。此时的rect是以手机屏幕作为坐标系,即==原点是屏幕左上角==;如果它全部被父View遮挡住了或者本身就是不可见的,返回的visibility就为false。
- 这个方法只能检查出这个View在手机屏幕(或者说是相对它的父View)的位置,而不能检查出与其他兄弟View的相对位置。如果两个View是平级关系,其中一个View遮挡了另一个View,那么被遮挡的View调用该方法返回值仍未true,rect对象的返回值也不受遮挡影响。
4. View.getLocalVisibleRect
- 这个方法跟getGlobalVisibleRect(rect)唯一的区别就是:getLocalVisibleRect(rect)获得的rect坐标系的==原点是View自己的左上角==,而不是屏幕左上角。所以只要这个View的左上角在屏幕中,它的LocalVisibleRect的左上角坐标就一定是(0,0),如果View的右下角在屏幕中,它的LocalVisibleRect右下角坐标就一定是(view.getWidth(), view.getHeight())。
5. 判断手机屏幕是否熄灭or是否解锁
检查View的可见性虽然和屏幕的状态看起来没有直接关系,但是在做检查前先对屏幕的状态做一个检查也是很有必要的,如果屏幕都已经关闭了,那这个View当然是对用户不可见的。
- View的坐标
1. View的位置描述
1 | view.width // View的宽度 |
view进行动画时的计算公式:
1 | x = left + translationX; |
2. 触摸点的位置描述
1 | event.x // 触摸点相对于View的X坐标位置 |
3. View相对屏幕的距离
下面介绍三种获取View距离屏幕距离的方法,具体实现方法如下:
1 | // getLocationInWindow |
- View的getWidth()和getMeasuredWidth()有什么区别?
- getMeasuredWidth()和getWidth()分别对应于视图绘制的measure和layout阶段。
- getMeasuredWidth()获取的是View原始的大小,也就是这个View在XML文件中配置或者是代码中设置的大小;getMeasuredWidth()的值是 ==measure阶段结束之后==得到的View的原始的值
- getWidth()获取的是这个View最终显示的大小,这个大小有可能等于原始的大小,也有可能不相等;在==layout结束后调用==getWidth()才能获取到View的宽度
- 在父布局的onLayout()方法或者该View的onDraw()方法里调用measure(0, 0),二者的结果可能会不同
- RelativeLayout会对子View做两次measure。这是为什么呢?
RelativeLayout中子View的排列方式是基于彼此的依赖关系,而这个依赖关系可能和布局中View的顺序并不相同,在确定每个子View的位置的时候,就需要先给所有的子View排序一下。又因为RelativeLayout允许A、B2个子View,横向上B依赖A,纵向上A依赖B。所以需要横向纵向分别进行一次排序测量。
- RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,也会调用子View2次onMeasure
- RelativeLayout的子View如果高度和RelativeLayout不同,则会引发效率问题,当子View很复杂时,这个问题会更加严重。如果可以,尽量使用padding代替margin。
- 在不影响层级深度的情况下,使用LinearLayout和FrameLayout而不是RelativeLayout。
RelativeLayout和LinearLayout性能分析
8.Choreographer
Choreographer 的作用主要是配合 Vsync ,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 Vsync 到来的时候 ,系统通过对 Vsync 信号周期的调整,来控制每一帧绘制操作的时机。目前大部分手机都是 60Hz 的刷新率,也就是 16.6ms 刷新一次,系统为了配合屏幕的刷新频率,将 Vsync 的周期也设置为 16.6 ms,每个 16.6 ms , Vsync 信号唤醒 Choreographer 来做 App 的绘制操作,这就是引入 Choreographer 的主要作用。
Choreographer 的作用
Choreographer原理(含native层)
屏幕刷新机制
9.View状态保存与恢复
1. 触发条件
- 触发Activity 的View 状态保存和恢复只有一种情形:异常销毁。
- 触发Fragment 的View 状态保存和恢复的两种情形:
- 异常销毁
- 事务进出返回栈
2. View状态的保存
无论是对Activity还是Fragment中View状态的保存,本质上都是通过调用Activity /Fragment 中顶级View的saveHierarchyState() 从而实现对Activity /Fragment 中View tree的深度遍历并调用每个View 的onSaveInstanceState() 保存View状态。
除了Fragment 的View状态是保存在Fragment 的一个Field 中外,Activity /Fragment 状态和View状态最终都保存在Activity的一个Field 中。
- 异常销毁:Activity的onSaveInstanceState() 中会调用:
- Activity的顶级View的saveHierarchyState()。
- Activity中所有activity fragment 的onSaveInstanceState() 和它们的顶级View 的saveHierarchyState()。
注意:Fragment 中顶级View 的saveHierarchyState()不是在它的onSaveInstanceState() (是空实现)中被调用的,这与Activity不同。
- 事务进栈:事务通过addToBackStack() 添加到返回栈中会导致所有从前台进入返回栈的Fragment 的顶级View的saveHierarchyState() 被调用。注意:不会触发Activity 和Fragment 的onSaveInstanceState()。
3.View状态的恢复
无论是对Activity还是Fragment中View状态的保存,本质上都是通过调用Activity /Fragment 中顶级View的restoreHierarchyState() 从而实现对Activity /Fragment 中View tree的深度遍历并调用每个View的onRestoreInstanceState() 恢复View状态。
1、异常销毁重建:会触发Activity 和所有active fragment 顶级View的restoreHierarchyState() 。
2、事务出栈:事务出栈会触发该事务涉及到的所有从返回栈出栈转为前台的Fragment 的顶级View的restoreHierarchyState()。
4.总结
- 实现View状态的自动保存,需要为每个View 设置id。TextView 还需要设置android:freezeText=”true”
- 自定义View要实现自身状态的保存和恢复,需要重写onSaveInstanceState() 和onRestoreInstanceState()
参考资料:
Activity 和Fragment 的异常销毁、状态保存和恢复机制
Android屏幕刷新机制—VSync、Choreographer 全面理解!
本文转载自: 掘金