之前我们的项目开发周期,从两周发一个版本,变成一周发一版本,这种快速迭代的节奏持续了将近一年半。平时开发,重心都放在了业务之上,很难有很多的时间去分析一些复杂业务多带来的性能问题,导致代码越来越沉重(比如:一个Fragment页面的代码到了3千多行。),而且页面渲染速度和帧率都大大下降,出现卡顿,严重影响用户的体验。当时这种问题已经发展到了很严重的地步,如果再一直这样持续下去,将可能导致用户流失,所以性能问题是必须解决的。
大概半年之前,我们开始对业务和架构进行重新梳理,对项目做一些重构和优化。我参与到了这个优化项目中,主要负责对过度渲染,过度绘制,方法耗时,内存优化方面的优化,下面将对这几点进行一些总结。
去除过度绘制主要从三方面入手:
具体如何分析和去除过度绘制,可以查看我之前的博客关于过度绘制和渲染的介绍。
关于背景重叠引起的过度绘制,可以从统计View背景重叠的次数,来做具体的优化。View是一个树形结构,可以对View树进行遍历,得到过度绘制的View路径:
/**
* 最小次数
*/
private static final int NUM = 3;
/**
* 测试过度绘制的View路径
*
* @param view 根View节点
*/
public static void testBackgroundOverdraw(View view) {
Map, Integer> result = new LinkedHashMap<>();
ArrayList list = new ArrayList<>();
findBackgroundOverdrawPath(view, result, list, NUM);
Log.d(TAG, "背景过度绘制大于" + NUM + "的布局的个数:" + result.size());
Iterator, Integer>> iterator = result.entrySet().iterator();
int index = 0;
while (iterator.hasNext()) {
Map.Entry, Integer> entry = iterator.next();
ArrayList path = entry.getKey();
Integer num = entry.getValue();
Log.d(TAG, "布局[" + index++ + "]深度:" + path.size() + ";次数:" + num + ";布局视图:" + path.toString());
}
}
/**
* @param node 根View节点
* @param result 过度绘制路径集合
* @param list 存储临时路径
* @param target 过度绘制次数
*/
private static void findBackgroundOverdrawPath(View node, Map, Integer> result, ArrayList list, int target) {
if (node == null) return;
list.add(node);
if (!(node instanceof ViewGroup)) {
int count = 0;
for (View view : list) {
if (view.getBackground() != null) count++;
}
if (count >= target)
result.put(list, count);
} else {
ViewGroup viewGroup = (ViewGroup) node;
for (int i = 0, count = viewGroup.getChildCount(); i < count; i++) {
findBackgroundOverdrawPath(viewGroup.getChildAt(i), result, new ArrayList(list), target);
}
}
}
例如,某个Activity的布局:
统计结果:
11-04 21:12:40.371 21520-21520/com.example.wangjiang.after D/UITestUtil: 背景过度绘制大于3的布局的个数:3
11-04 21:12:40.371 21520-21520/com.example.wangjiang.after D/UITestUtil: 布局[0]深度:7;次数:3;布局视图:[com.android.internal.policy.PhoneWindow$DecorView{dc28997 V.E...... R.....ID 0,0-720,1280}, android.widget.LinearLayout{38ac784 V.E...... ......ID 0,0-720,1280}, android.widget.FrameLayout{ef95b6d V.E...... ......ID 0,36-720,1280}, android.support.v7.widget.ActionBarOverlayLayout{d5553a2 V.E...... ......ID 0,0-720,1244 #7f070030 app:id/decor_content_parent}, android.support.v7.widget.ContentFrameLayout{e1c2833 V.E...... ......ID 0,112-720,1244 #1020002 android:id/content}, android.support.constraint.ConstraintLayout{aed7f0 V.E...... ......ID 0,0-720,1132}, android.support.v7.widget.AppCompatTextView{3dd4169 V.ED..... ......ID 286,0-435,38}]
11-04 21:12:40.371 21520-21520/com.example.wangjiang.after D/UITestUtil: 布局[1]深度:8;次数:4;布局视图:[com.android.internal.policy.PhoneWindow$DecorView{dc28997 V.E...... R.....ID 0,0-720,1280}, android.widget.LinearLayout{38ac784 V.E...... ......ID 0,0-720,1280}, android.widget.FrameLayout{ef95b6d V.E...... ......ID 0,36-720,1280}, android.support.v7.widget.ActionBarOverlayLayout{d5553a2 V.E...... ......ID 0,0-720,1244 #7f070030 app:id/decor_content_parent}, android.support.v7.widget.ContentFrameLayout{e1c2833 V.E...... ......ID 0,112-720,1244 #1020002 android:id/content}, android.support.constraint.ConstraintLayout{aed7f0 V.E...... ......ID 0,0-720,1132}, android.widget.LinearLayout{f5907ee V.E...... ......ID 0,528-720,604}, android.support.v7.widget.AppCompatTextView{967148f V.ED..... ......ID 285,0-434,38}]
11-04 21:12:40.371 21520-21520/com.example.wangjiang.after D/UITestUtil: 布局[2]深度:9;次数:4;布局视图:[com.android.internal.policy.PhoneWindow$DecorView{dc28997 V.E...... R.....ID 0,0-720,1280}, android.widget.LinearLayout{38ac784 V.E...... ......ID 0,0-720,1280}, android.widget.FrameLayout{ef95b6d V.E...... ......ID 0,36-720,1280}, android.support.v7.widget.ActionBarOverlayLayout{d5553a2 V.E...... ......ID 0,0-720,1244 #7f070030 app:id/decor_content_parent}, android.support.v7.widget.ContentFrameLayout{e1c2833 V.E...... ......ID 0,112-720,1244 #1020002 android:id/content}, android.support.constraint.ConstraintLayout{aed7f0 V.E...... ......ID 0,0-720,1132}, android.widget.LinearLayout{f5907ee V.E...... ......ID 0,528-720,604}, android.widget.FrameLayout{ec1831c V.E...... ......ID 0,38-720,76}, android.support.v7.widget.AppCompatTextView{1ff8b25 V.ED..... ......ID 285,0-434,38}]
上面统计了布局背景重叠超3次以上的布局路径,3次一个,4次两个,根节点是从DecorView开始的。当然,也可以根据个人需要来收集相应的信息。
同理,布局层级嵌套次数的统计,也可以通过同样的方式:
/**
* 测试布局层级的View路径
*
* @param view 根View节点
*/
public static void testLayoutHierarchy(View view) {
ArrayList> result = new ArrayList<>();
ArrayList list = new ArrayList<>();
findLayoutHierarchyPath(view, result, list, NUM);
Log.d(TAG, "布局层级嵌套大于" + NUM + "的布局的个数:" + result.size());
int index = 0;
for (ArrayList path : result)
Log.d(TAG, "布局[" + index++ + "]深度:" + path.size() + ";嵌套次数:" + (path.size() - 1) + ";布局视图:" + path.toString());
}
/**
* @param node 根View节点
* @param result 布局层级路径集合
* @param list 存储临时路径
* @param target 布局层级嵌套的次数
*/
private static void findLayoutHierarchyPath(View node, ArrayList> result, ArrayList list, int target) {
if (node == null) return;
list.add(node);
if (!(node instanceof ViewGroup)) {
int count = list.size();
if (--count >= target)
result.add(list);
} else {
ViewGroup viewGroup = (ViewGroup) node;
for (int i = 0, count = viewGroup.getChildCount(); i < count; i++) {
findLayoutHierarchyPath(viewGroup.getChildAt(i), result, new ArrayList(list), target);
}
}
}
统计结果:
11-04 21:43:53.786 23207-23207/com.example.wangjiang.after D/UITestUtil: 布局层级嵌套大于3的布局的个数:2
11-04 21:43:53.786 23207-23207/com.example.wangjiang.after D/UITestUtil: 布局[0]深度:4;嵌套次数:3;布局视图:[android.support.v7.widget.ContentFrameLayout{e1c2833 V.E...... ......ID 0,112-720,1244 #1020002 android:id/content}, android.support.constraint.ConstraintLayout{aed7f0 V.E...... ......ID 0,0-720,1132}, android.widget.LinearLayout{f5907ee V.E...... ......ID 0,528-720,604}, android.support.v7.widget.AppCompatTextView{967148f V.ED..... ......ID 285,0-434,38}]
11-04 21:43:53.786 23207-23207/com.example.wangjiang.after D/UITestUtil: 布局[1]深度:5;嵌套次数:4;布局视图:[android.support.v7.widget.ContentFrameLayout{e1c2833 V.E...... ......ID 0,112-720,1244 #1020002 android:id/content}, android.support.constraint.ConstraintLayout{aed7f0 V.E...... ......ID 0,0-720,1132}, android.widget.LinearLayout{f5907ee V.E...... ......ID 0,528-720,604}, android.widget.FrameLayout{ec1831c V.E...... ......ID 0,38-720,76}, android.support.v7.widget.AppCompatTextView{1ff8b25 V.ED..... ......ID 285,0-434,38}]
布局也是上面的布局,统计到布局层级嵌套超过3层的有2个,根节点是从android.R.id.content 开始的。
Hierarchy Viewer可以很直接的呈现布局的层次关系,视图组件的各种属性。 我们可以通过红,黄,绿三种不同的颜色来区分布局的Measure,Layout,Executive的相对性能表现如何。
注意:Hierarchy Viewer已经被抛弃了。如果您使用的是Android Studio 3.1或更高版本,那么应该使用Layout Inspector在运行时检查应用程序的视图层次结构。要了解应用程序布局的渲染速度,请使用此博客帖子中描述的Window.OnFrameMetricsAvailableListener。
Canvas类提供了clipRect 和 quickReject 方法:
clipRect 和 quickReject 方法主要是用来避免在自定义View中绘制重叠的区域。