前言

下文中提到的耗时都是大于16ms的耗时,比较低的耗时没有列出。

1.卡顿调查

使用原生设置的Profile GPU Rendering GPU渲染模式分析工具,粗略查看丢帧情况,看哪些界面丢帧严重。

绿色线是16.7ms的界面刷新线

1.1 应用启动

可以看出当聚媒体启动的时候,会有一段事件丢帧特别严重,如下图。

启动丢帧.jpg

1.2 界面滑动

可以看出当界面滑动的时候,资源加载也会出现比较严重的丢帧情况。

加载资源丢帧.jpg

1.3 资源缓存

当界面来回跳转的时候,部分界面会存在没有缓存,或者缓存数据不够的情况,也有丢帧情况。

此类场景需要兼顾考虑内存和丢帧的折中选择。

重复加载资源.jpg

2. Systrace调查丢帧时系统情况

可以看出在滑动的时候,加载的一秒多时间内,有很多丢帧的情况。

滑动丢帧情况.png

查看线程情况,UI线程处于sleeping状态。

当前系统线程状态判断卡顿原因.png

可以看到Render线程在调用Drawframe()正在渲染一帧数据,然而可以看到此次渲染花了很长时间,接下来我们就需要看一下为什么那么耗时了。

Render渲染.png

3.主界面丢帧分析

3.1 启动引发的背景思考

ActivityStart耗时.png

启动activity总共耗时357ms,其中比较耗时的有两部分:

  • 加载主题图片(55ms)w
  • 加载布局资源(171ms)

加载主题.png

加载布局.png

优化建议:

1.去掉重复背景

1
2
3
4
5
6
7
8
9
10
// common/src/main/res/values/styles.xml
<style name="Theme" parent="Theme.MaterialComponents.Light.NoActionBar">
<item name="android:windowAnimationStyle">@style/Animation</item>
<item name="colorPrimary">#0080ff</item> <-- 此行删除掉,因为下面背景图(mediax_theme)已经是全屏
<!-- 设置无标题 -->
<item name="android:windowNoTitle">true</item>
<!--设置透明主题-->
<item name="android:windowBackground">@mipmap/mediax_theme</item>
<!-- <item name="android:windowFullscreen">?android:windowNoTitle</item>-->
</style>

优化后,该页背景加载优化耗时25.86ms, 优化47%。

闪屏页删除背景效果.png

2.小播放器背景加载

关于背景的加载,源代码中是加载的png图片,我们来看一下加载png、加载webp和加载xml的耗时对比。

  • 使用png加载

使用png加载.png

  • 使用webp加载

使用webp加载.png

  • 使用xml加载

使用xml加载.png

使用png图片加载背景耗时最长,webp次之,xml耗时最少。但是经过我多轮测试,png和webp两种方式加载图片耗时优化不是很稳定,有时候webp耗时会少一点,有时候会少比较多。但是xml和以上两种耗时比较稳定,使用xml当背景加载耗时会少20%左右

因此,背景图加载建议如下:

  • 如果采用纯色作为背景,图像比较简单的情况下,建议使用xml作为背景图来加载
  • 虽然webp图片比png图片加载耗时没有明确稳定的优化,但是webp图片体积很小,对于小图片的加载建议都使用webp图片。以小播放器背景图片为例,原png图479Kb,webp图265Kb
小结:

1.从第一点可以看出背景图重复会直接成倍数增加加载耗时,而且会导致UI卡顿。因此自行检查重复背景图情况。目前检查到有嫌疑背景重叠的布局有如下,如果不重复可以忽略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
program/src/main/res/layout/mediax_fragment_dialog_program_select.xml
app/src/main/res/layout/fragment_news_playing.xml //使用xml加载纯黑背景
app/src/main/res/layout/mediax_fragment_dialog_himalaya_login.xml //如果是为了自定义一个dialog可以忽略
app/src/main/res/layout/layout_accout_bind.xml
app/src/main/res/layout/mediax_fragment_dialog_kw_unbind.xml //如果是为了自定义一个dialog可以忽略
app/src/main/res/layout/fragment_program_playing_new.xml // 是否和外层容器的背景重合了?
app/src/main/res/layout/fragment_speaker_dialog.xml // 是否和外层容器的背景重合了?
app/src/main/res/layout/fragment_media.xml //tabIndicatorColor重合了,是否可以取消?
app/src/main/res/layout/fragment_search.xml //RecyclerView背景色是否和外部重合?
app/src/main/res/layout/layout_similar_song_play_list_small_player.xml // 背景是否和外部重合?
app/src/main/res/layout/fragment_player_news.xml // 背景是否和外部重合?
app/src/main/res/layout/fragment_player.xml // 点击播放列表显示出来是否需要隐藏掉后面的界面?
app/src/main/res/layout/fragment_soundhound.xml // 背景是否和最外层背景重复?
app/src/main/res/layout/fragment_music_playing_new.xml // 背景是否重合
app/src/main/res/layout/fragment_player_program_new.xml // 背景是否重合可以复用
app/src/main/res/layout/fragment_music_qq.xml // 外层有白色背景,子空间的背景是否可以取消掉
app/src/main/res/layout/fragment_player_program.xml // 背景是否重合是否可以复用,取消子空间背景
app/src/main/res/layout/fragment_speed_dialog.xml // 背景是否可以重用
app/src/main/res/layout/adapter_music_rank_item.xml // 有图片了,背景是否可以取消
app/src/main/res/layout/fragment_kw_alipay.xml // 是否有必要多一层ConstraintLayout
app/src/main/res/layout/fragment_player_news_new.xml // 背景是否可以重用
app/src/main/res/layout/my_local_radio_item.xml // 背景是否可以重用

上面列出的布局请重点看一下布局的背景是否可以延用外部的背景,尽量避免重绘。

2.控件选择加载了图片就取消背景颜色,能用xml来填充背景的尽量用xml来绘制。

3.2 加载第一帧耗时分析

启动_绘制第一帧耗时.png

启动_第一帧测量耗时.png

从图中可以看到启动主界面Choreographer绘制第一帧耗时637ms,其中测量耗时530ms,占了绝大多数的时间。自检一下自定义view中onMeasure是否有耗时操作。如果确实是空间比较多耗时可以暂时不优化。但是从下图可以看出加载布局只消耗了150ms, 其他370ms需要媒体FO定位哪里耗时了

第一帧measure测量.png

3.3 加载RecycleView布局

启动_RecycleView Item.png

启动_RecycleView Item_topdown.png

在启动阶段加载RecycleView耗时492ms, 其中onMeasure()耗时465ms,创建item和绑定View耗时300ms左右,请FO看此处是否还有优化空间,如果没有可以暂时不优化。

3.4 滑动

主界面滑动分析.png

图中放大可以看到在主界面滑动到最底部的过程中,几乎没有丢帧。原因应该是在加载主界面的时候,将所有数据全部加载出来了,导致在滑动的时候基本不用加载数据,所以没有丢帧。

建议:从上面两个小结看,加载RecycleView界面和数据比较耗时,因此是否可以考虑将上面部分数据的加载实现懒加载,用户看不见的界面数据不加载,或者等启动完成之后再加载。我看主界面RecycleView的下面有两行半是隐藏的,可以将下半部分未显示的布局和数据延迟加载?

3.5 布局优化
3.5.1 冗余view

主界面多余的view.png

建议:此处没有内容多余的view是否可以去掉?

3.5.2 冗余布局

主界面冗余布局.png

建议:此处只有一个view,是否可以取消掉该ConstraintLayout?

4.酷我音乐界面丢帧分析

4.1 进入酷我音乐界面
4.1.1 复盘启动酷我音乐界面

酷我音乐界面复盘.png

调到酷我界面,从Trace文件中可以看到一共分为5个部分加载整个fragment的,分别耗时如下:

  • 第一部分:耗时68ms,测量耗时33ms, 绘制耗时26ms。此部分主要耗时在加载各种titile。
  • 第二部分:耗时118ms,测量耗时99ms, 绘制耗时16ms。此部分主要耗时在加载翻唱``网络这个RecycleView。
  • 第三部分:耗时471ms,测量耗时140ms,布局耗时318ms, 此部分主要耗时在加载音乐歌单这个fagment。
  • 第四部分:耗时197ms,布局耗时186ms, 此部分主要耗时在加载音乐榜单这个fragment。
  • 第五部分:耗时352ms,布局耗时340ms,此部分主要耗时在加载音乐电台这个fragment

以上多个阶段耗时点来看,该界面主要存在懒加载问题。

建议:

使用ViewPager2实现懒加载。此处由于我不清楚该工程内部数据加载逻辑,请FO适配一下数据加载部分的到各fragment的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// mediax\app\src\main\res\layout\fragment_music_qq.xml
<androidx.viewpager2.widget.ViewPager2 <<---- 修改成ViewPager2即可
android:id="@+id/view_pager"
associatedViewId="@{R.id.music_tab_layout}"
fragmentList="@{vm.fragmentList}"
fragmentManager="@{vm.fragmentManagerLiveData}"
initPageIndex="@{vm.initPageIndex}"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="@dimen/x20"
android:fadingEdgeLength="50dp"
android:requiresFadingEdge="horizontal"
android:scrollbars="none"
app:layout_constraintTop_toBottomOf="@+id/music_tab_layout" />
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// mediax\app\src\main\java\com\iflytek\autofly\mediax\ui\page\QQMusicFragment.java

FragmentMusicQqBinding fragmentMusicQqBinding = (FragmentMusicQqBinding) getBinding();
fragmentMusicQqBinding.viewPager.setOffscreenPageLimit(1); // 增加此行
// 修改注册回调方法
fragmentMusicQqBinding.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

}

@Override
public void onPageSelected(int position) {
for (int i = 0; i < fragments.size(); i++) {
if (i == position) {
getChildFragmentManager().beginTransaction().show(fragments.get(i)).commit();
} else {
getChildFragmentManager().beginTransaction().hide(fragments.get(i)).commit();
}
}
}

@Override
public void onPageScrollStateChanged(int state) {
}
});

优化后的启动时间:

酷我音乐界面复盘_优化后.png

从图中可以看到使用ViewPager2,将setOffscreenPageLimit()为1之后,只会加载一个界面的内容,耗时44ms。因此从该界面的大方向来说,可以优化掉音乐榜单``音乐电台这两个fragment的加载耗时,此处可以优化550ms左右。

4.1.2 界面布局优化

4.1.2.1 内嵌ViewPager优化

该界面从大方向优化之后,针对音乐歌单,音乐榜单,音乐电台这三个Fragment, 在布局方面还有优化空间。见如下分析。

音乐歌单界面懒加载.png

音乐歌单界面还内嵌了两个ViewPager,优化方向参照4.1.1小节使用ViewPager2懒加载布局,此处有不少tab, 启动耗时在471ms左右,应该还有很大优化空间空间。

4.1.2.2 空间使用合理性

酷我音乐_viewpager_refresh布局.png

建议:

  • 此处需要考虑一个界面是否使用ViewPager是否合理?
  • 此处需要考虑一下界面是否确有刷新功能需求?
4.2 滑动RecycleView加载

酷我音乐滑动优化.png

滑动音乐歌单界面,可以看出滑动两次,界面几乎没有丢帧,说明数据早已加载,此处我们重点思考的方向是是否数据缓存过多,导致启动该界面变慢。

建议:由于当前项目音乐歌单界面用户一次性可见的item默认是10个item, 滑动过程中会有15个item,因此是否可以设置RecycleView加载item的个数,将加载item内容分散到各个滑动环节,减少首次加载过多item引起的耗时。

4.3 滑动RecycleView回收复用

酷我音乐界面RecycleView复用调查.png

上图的操作步骤:在音乐歌单界面向下滑动三次,然后向上滑动两次。从该图可以看出向上滑动几乎没有丢帧,说明RecycleView默认缓存了以上几次滑动的所有数据。

建议:我们知道缓存bitmap对象一方面很吃内存资源,另一方面缓存过多item也会影响首次加载耗时。此处的优化建议如上一项,合理利用RecycleView的回收复用机制,均衡一下缓存item的量。

5.喜马拉雅丢帧分析

5.1 启动

喜马拉雅界面_启动.png

从上图中可以看到喜马拉雅界面还是和酷我界面存在同样的问题,喜马拉雅有分类节目``在线节目两个fragment,问题是在启动的时候,两个fragment的布局和数据都加载了,导致启动该界面加载分类节目界面耗时689ms,加载在线节目耗时396ms。

建议:

使用ViewPager2实现懒加载,只加载一个界面的内容。

5.2 RecycleView缓存

喜马拉雅界面_缓存.png

优化建议见4.2小节,合理控制RecycleView缓存的item数目。

6.整体性能参数分析

说明:

  • 测试场景:测试同学正常测试各个功能。

  • 以下监控的性能数据是测试2750个采集周期,每个采集周期10s,7.64小时的数据。

  • 内存图横坐标是采集周期,单位:个;纵坐标是内存占用,单位:M

  • CPU图横坐标是采集周期,单位:个;纵坐标是CPU占用,单位:%

  • View图横坐标是采集周期,单位:个;纵坐标是当次采集界面中view的个数,单位:个

6.1 Mediax 内存占用分析

内存.png

从内存方面来看:

  • MediaX应用内存占用,最高内存385M,平均内存295M
  • 在监控周期内存在明显内存抖动
  • 在监控周期内存在内存泄露
6.2 Mediax CPU占用分析

CPU.png

从CPU方面来看:

  • 最大CPU占用46%
  • 平均CPU占用4.4%
6.3 Mediax View占用分析

界面View个数.png

从mediax应用加载View个数来看:

  • 最多View个数:1308个
  • 平均View个数:485个
6.4 整体性能参数小结

从内存和View图可以看出Mediax应用存在大量图片加载和频繁mirror GC的情况。此处数据也应证了第3-5章分析的RecycleView过度加载View的分析。

7.优化方向总结

  • 使用ViewPager2懒加载Fragment,不用到的Fragment不主动加载。
  • 合理使用RecycleView的回收复用机制,控制Item预加载个数。
  • 合理使用布局控件,合理使用ConstraintLayout,减少不必要的布局嵌套。
  • 有图片的控件的View不要设置android:background选项,否则导致界面重叠,重绘造成界面卡顿。