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()
scss
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
private void performTraversals() {
final View host = mView;
...
if (mFirst) {
host.dispatchAttachedToWindow(mAttachInfo, 0);
} else {
...
}
mFirst = false;
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, mWidth, mHeight);
performDraw();
}
2.view/activity的生命周期关系
在MyActivity中布局添加一个CustomView,然后在各个回调中添加log,会有如下调用顺序:
yaml
E/MyActivity: onCreate:
E/CustomView: onFinishInflate:
E/MyActivity: onResume:
E/MyActivity: onAttachedToWindow:
E/CustomView: onAttachedToWindow:
E/CustomView: onMeasure:
E/CustomView: onMeasure:
E/CustomView: onMeasure:
E/CustomView: onSizeChanged:
E/CustomView: onLayout:
E/CustomView: onDraw:
E/MainActivity: onWindowFocusChanged: true
E/CustomView: onWindowFocusChanged: true
//接下来退出Activity
E/MainActivity: onWindowFocusChanged: false
E/CustomView: onWindowFocusChanged: false
E/MyActivity: onDestroy:
E/CustomView: onDetachedFromWindow:
E/MyActivity: onDetachedFromWindow:
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
arduino
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
重写onMeasure方法
ini
/**
* 重写onMeasure方法
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//处理wrap_contentde情况
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(300, 300);
} else if (widthMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(300, heightSize);
} else if (heightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSize, 300);
}
}
4. 判断View在屏幕中是否可见并获取可见区域
1. View.getVisibility()
只能获取当前View的visibility的值,不受他的父View的visibility的影响(如果view的visibility为true,即使父view的View的visibility为false,该方法仍返回true,其实这个View对用户是不可见的)
2. View.isShown()
java
/**
* Returns the visibility of this view and all of its ancestors
*
* @return True if this view and all of its ancestors are {@link #VISIBLE}
*/
public boolean isShown() {
View current = this;
//noinspection ConstantConditions
do {
if ((current.mViewFlags & VISIBILITY_MASK) != VISIBLE) {
return false;
}
ViewParent parent = current.mParent;
if (parent == null) {
return false; // We are not attached to the view root
}
if (!(parent instanceof View)) {
return true;
}
current = (View) parent;
} while (current != null);
return false;
}
- 这个方法递归地去检查这个View以及它的parentView的Visibility属性是不是等于View.VISIBLE
- 另外这个方法还在递归的检查过程中,检查了parentView == null,也就是说所有的parentView都不能为null,否则就说明这个View根本没有被addView过(比如使用Java代码创建界面UI时,可能会先new一个View,然后根据条件动态地把它add到一个ViewGroup中),那肯定是不可能对用户可见的。
3. View.getGlobalVisibleRect
ini
Rect rect = new Rect();
boolean visibility = bottom.getGlobalVisibleRect(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当然是对用户不可见的。
5. View的坐标
1. View的位置描述
arduino
view.width // View的宽度
view.height // View的高度
view.x // View左上角相对于ViewGroup左上角的X坐标位置
view.y // View左上角相对于ViewGroup左上角的Y坐标位置
view.z // 相对于屏幕的Z位置
view.left // View左边相对于ViewGroup左边的距离
view.top // View上边相对于VIewGroup上边的距离
view.right // View右边相对于ViewGroup左边的距离
view.bottom // View底边相对于ViewGroup上边的距离
view.elevation // View的高度
view.translationX // 在滑动过程中,View当前位置的最左边和这个View原始位置的最左边的距离
view.translationY // 在滑动过程中,View当前位置的最上边和这个View原始位置的最上边的距离
view.translationZ // 在动画过程中,View当前位置的Z轴高度和这个View原始Z轴高度的距离
view进行动画时的计算公式:
ini
x = left + translationX;
y = top + translationY;
z = elevation + translationZ;
2. 触摸点的位置描述
csharp
event.x // 触摸点相对于View的X坐标位置
event.y // 触摸点相对于View的Y坐标位置
event.rawX // 触摸点相对于屏幕最左侧的距离
event.rawY // 触摸点相对于屏幕最上侧的距离
3. View相对屏幕的距离
下面介绍三种获取View距离屏幕距离的方法,具体实现方法如下:
arduino
// getLocationInWindow
val position = IntArray(2)
viewGroup.getLocationInWindow(position)
Log.d("coordinate-screen", "x: " + position[0])
Log.d("coordinate-screen", "y: " + position[1])
// getLocationOnScreen
val position = IntArray(2)
viewGroup.getLocationOnScreen(position)
Log.d("coordinate-screen", "x: " + position[0])
Log.d("coordinate-screen", "y: " + position[1])
// getGlobalVisibleRect
val rect = Rect()
viewGroup.getGlobalVisibleRect(rect)
Log.d("coordinate-screen", "x: " + rect.left.toString())
Log.d("coordinate-screen", "y: " + rect.top.toString())
6. 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),二者的结果可能会不同
7. 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 的异常销毁、状态保存和恢复机制