Activity 性能优化方案UI 卡顿原理UI卡顿常见原因优化手段
UI 卡顿原理
人类大脑与眼睛对一个画面的连贯性感知其实是有一个界限的,其帧率通常为 24fps;那么,用手机当然也需要感知屏幕操作的连贯性(尤其是动画过渡),所以在手机领域 Android/iOS 索性就把达到这种流畅的帧率规定为 60fps。
基于上面的背景,我们开发 App 的帧率性能目标就是保持在 60fps(16ms/帧),即我们在进行 App 性能优化时:
尽量保证每帧在 16ms 内处理完所有的 CPU 与 GPU 计算、绘制、渲染等操作,否则会造成丢帧卡顿问题。
基于上面的卡顿原理,我们知道所谓的卡顿其实是可以量化的,每次是否能够成功渲染是非常重要的问题,即 16ms 能否完整的做完一次操作直接决定了卡顿性能问题。
UI卡顿常见原因
引起 UI 卡顿的常见原因有如下几种:
主线程做了阻塞 UI 的耗时操作。
同一时刻动画执行多次导致 GPU 和 CPU 过度绘制。
View 过度绘制导致 GPU 和 CPU 过度绘制。
频繁地进行布局绘制、文本计算等操作导致 View 需要重新渲染。
频繁的对象创建和销毁。
过度复杂的业务逻辑,耗时函数。
线程泛滥使用,导致cpu一直处于调度状态。
优化手段
优化布局层级
通过删除无用的层级,或者对整个布局进行改造使用RealtiveLayout替换LinerLayout减少布局层级。
使用Merge标签或ViewStub标签来优化整个布局性能,比如一些显示错误界面、加载提示框界面等,不是必须显示的这些布局可以使用ViewStub标签来提升性能。
使用工具: 我们借助系统自带的 HirectViewer、Dump UI Hierarchy for UI Automator 等工具去分析系统 UI 的层级。
加快界面加载
除了从 XML Layout 文件里面角度减少布局层级,还通过提前加载布局,即在线程中做一些必要的 inflate 等来提前初始化布局,减少实际显示时的耗时。对于一些复杂的布局,我们还会自己做View对象复用池,减少 inflate 带来的性能损耗,特别是在列表控件中。
通过 TraceView 工具找出主线程的耗时操作和其他耗时的线程并作优化,另外减少主线程的 GC 停顿。因为即使并行 GC,也会对 heap 加锁,如果主线程请求分配内存的话,也会被挂起,所以尽量避免在主线程分配较多对象和较大的对象,特别是在onDraw等函数中,以减少被挂起的时间。
利用本地缓存,主要界面缓存上次的数据,并配合增量的更新和删除,能做到数据和服务端同步,这样可以直接展示本地数据,不用等到网络返回数据。
减少系统GC次数
减少对象分配,找出不必要的对象分配,如可以使用非包装类型时,使用了包装类型,避免 Autoboxing -> unboxing 的过程,同时避免大量对象字符串的+号操作,如果不考虑线程安全引起的问题时,优先使用 StringBuilder,而非StringBuffer去进行字符串操作。
尽早释放无用对象的引用,特别是大对象和集合对象,通过置为,及时回收。
减少内存泄漏。
合理选择容器,在性能上优先考虑。首先是扩容开销,HashMap 扩容时重新 Hash 的开销较大。其次是内存开销,HashMap 需要额外的 Map.Entry 对象分配,需要额外内存,也容易产生更多的内存碎片。SparseArray 和 ArrayList 等在内存方面更有优势。再次是遍历,对于实现了RandomAccess接口的容器如 ArryList 的遍历,不应该使用foreach循环。在移动设备中,尽量避免使用枚举。
用工具监控和精雕细琢:在页面滑动过程中,通过 Android Studio 自带的 Memory Monitor 工具查看内存波动和 GC 情况,还可通过 Allocation Tracker 工具观察分析内存的分配,发现很多小对象的分配问题以及是否存在内存泄露问题。
列表多布局显示
ListView:
getItemViewType(int position)
getViewTypeCount()
getView() 根据不同的类型来加载不同的布局,生成不同的ViewHolder,来生成多类型布局。
RecyclerView
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
public int getItemViewType(int position)
在getItemViewType()获取对应位置的类型, 在onCreateViewHolder中根据viewType值来生成不同的布局, 在onBindViewHolder 判断holder的类型来绑定不同的类型。
Activity超过500ms优化方案补充说明
PushMsgShowActivity 列表多布局显示( 完成 )
优化点:主要拆分getView方法:
根据列表的布局不同 编写 不同的ViewHolder。
复写getViewTypeCount方法 返回布局类型个数
复写getItemViewType方法 根据position返回不同的布局类型
复写getView方法 根据布局类型来加载不同的布局 并对布局做相应的数据填充。
GoldWebActivity
优化点:
网页开启 默认缓存。
开启硬件加速模式 加速网页渲染速度。
采用new WebView(getApplicationContext()) 采用addView()动态添加到布局,在OnDestory()方法将WebView所在的父布局移除所有子布局 并将WebView = null来 防止WebView内存泄漏。
SlideAboutActivity优化前 启动时间将近500ms以上 优化后启动显示时间缩短到200ms左右 。优化点:
将ScrollView中的第一个LinearLayout的高度修改为match_parent。因为ScrollView的布局中的高度如果设置为wrap_content 需要遍历容器内的所有的子布局的高度后确认,这个过程需要多次调用的OnMeasure()方法。将ScrollView的布局设置为Match_Parent 则高度自动修改为满屏,不会多次调用View的OnMeasure()
修改图片大小和存放的位置,ImageView src 属性 默认是在UI线程绘制图片 在SlideAboutActivity界面图片尺寸过大 且存放的位置不应该放置为drawable文件,修改图片尺寸大小 并且将图片放置xxhdpi文件夹下。
OnRoadDetailActivity OnRoadTravelDetailActivity
性能耗时主要在于 加载头布局耗时 层级过深复杂,业务逻辑代码复杂。
优化点:
优化布局层级 对于不显示的布局采用 ViewStub,懒加载需要显示的布局。
清理业务逻辑代码, 考虑是否能减少对服务器的多次网络请求。
OnRoadCategoryView 涉及页面 在路上那个 最新 关注 类型列表
头布局耗时 层级过深复杂,业务逻辑代码复杂。
优化点:
将不同类型的布局 拆分,按照多布局类型 加载相对于的布局。参考 PushMsgShowActivity 优化。