一 卡顿问题
1.1 ListView的ItemView中使用了getViewTreeObserver().addOnPreDrawListener
在实际生产中发现列表快速滑动的掉帧非常厉害,但是无论通过FPS的检测以及method profile的测量此时的主线程都不应该卡顿。后面通过分析发现该列表页面的itemView中有如下一种代码:
viewHolder.textView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
viewHolder.textView.getViewTreeObserver().removeOnPreDrawListener(this);
。。。。。。。。。。
return false;
}
});
当ListView的某个item满足一定条件时就会进入上述这种代码,他会监听ViewTreeObserver的“OnPreDrawListener”,同时可能会返回false。
注意当这个函数返回false的时候,整个页面会skip掉本次的draw动作,注意是整个activity对应的DecoreView都不会在这次绘制时钟周期中绘制。因此当页面快速滑动的过程中,某些时机这个方法返回false页面不绘制,自然让人感觉到掉帧的出现
1.2 ListView中嵌套RecyclerView
在项目有这样的实现ListView中某个item是一个垂直展示的RecyclerView,RecyclerView的高度是WrapContent的,这样实现的话展示上看上去是没有问题的。但是在滑动的过程中我们会发现当滑动到RecyclerView区域时,页面变的很难滑动,滑起来很费力。经过分析这个也不是FPS低造成的,是由于这块区域的RecyclerView的事件处理和ListView时间产生了冲突,造成用户的滑动手势不灵敏。
解决的方案是禁用RecyclerView的VertaicalScroll也就是不让它垂直滑动。这样就不会冲突了。
1.3 共性的优化方案
1.3.1:去除掉主线程中耗时的I/O操作,比如JSON解析等。
1.3.2:在类似ListView或者RecyclerView这种显示数据多的场景下,虽然系统控件提供了整个Item的复用,但是对于每个Item中的子View,特别是数量变化很大的子View,我们可以自己再去做一层缓存,降低此类View的频繁的创建。根本宗旨就是在这种场景下利用复用机制去减少不必要的对象创建,因为创建和销毁这种影响到内存申请和释放的动作是比较耗时的。
1.3.3:在1.3.2的基础上如果我们进一步扩展,可以引进一些比较强大的控件比如VLAYOUT,它是一个开源的基于RecyclerView的控件,支持将不同的LayoutManager的样式同时在RecyclerView的item中使用。利用这个控件我们可以透过自定义LayoutManager在更小的粒度上在不同的ITEM之间复用一些相同样式的子View。
1.3.4:对于一些为了计算View的高宽的代码,比如采用预设值一个初始值然后根据onlayoutListener的回调进行二次计算的代码进行优化。这些用法转化成自定义的控件,跟随系统生命周期去计算,减少重复计算,提高效率。
二 内存泄漏问题
2.1:ListView的HeaderView的ondetachFromWindow事件某种场景下的缺失
当某个有HeaderView的ListView在其HeaderView滑出屏幕后,ListView只会将HeaderView从其自己的ViewGroup中remove掉,但不会调用其ondetachFromWindow的通知事件。此时我们的项目中在headview中有个FlipperView的控件,这个控件内部有一个broadcast,这个broadcast的反注册是通过监听ondetachFromWindow完成的。因此当HeaderView滑出了屏幕,如果此时我们退出该页面,这个broadcast的反注册就会执行不到,造成当前的activity的泄漏。
解决方法,是在ListView的ondetachFromWindow中自己强行调用一下HeaderView的ondetachFromWindow通知。这个可能是由于ListView是比较早期的控件,又有些方面不是考虑太全面。
2.2: WebView内存泄漏 https://www.jianshu.com/p/eada9b652d99
WebView在其自己的onAttachedToWindow,onDetachedFromWindow事件中会在application中注册和反注册自己的一个内部类mComponentCallbacks。理论上这样的调用成对出现是没有问题的。
但是在某些手机上WebView的如下所示,我们可以看到如果此时isDestroyed()为true的话,接下来的unregisterComponentCallbacks就会走不到了,此时就会造成当前activity的泄漏
public void onDetachedFromWindow() {
if (isDestroyed()) return;
………
if (mComponentCallbacks != null) {
mContext.unregisterComponentCallbacks(mComponentCallbacks);
mComponentCallbacks = null;
}
}
解决方案,控制WebView的destory和onDetachedFromWindow两个事件执行的先后顺序。
if (mBannerWebView != null) {
ViewParent parent = mBannerWebView.getParent();
if (parent != null) {
((ViewGroup)parent).removeView(mBannerWebView);//保证onDetachedFromWindow先调用
}
mBannerWebView.destroy();
}
2.3 内存申请的不收敛
recyclerView的Item中也用了recyclerView,同时嵌入的recyclerView的布局属性用了wrapcontent此时嵌入的recyclerView的数据如果很多同时又是类似图片这种占内存很大的数据。在页面上下滑动时嵌入的recyclerView的数据不会复用,也就是说此时的状态跟scroll一样,此时上下滑动,会造成大量的图片内存申请和常驻,最终造成OOM。
解决方法:避免这种嵌套的用法,采取定义相应的layoutmanager的方法,用单个recyclerView去实现。这样上下滑动过程中只有屏幕内的数据会强制占用内存,屏幕外的都是可以释放的,降低内存压力,避免OOM.
2.4:通用问题
2.4.1 Handler造成的activity泄漏,在activity的destory时removeallcallbackandmsg(null)
2.4.2 Bradcast/eventbus 注册和反注册没有做好
2.4.3 子线程在activity退出时没有停止,asynctask
2.4.4 多个服务同时发送时,某些服务没有返回。公共对每个服务都有一个对应的token,同时采用了生产消费者模式,当一个服务完成后,会把该token对应的消费者删除掉。当我们应用层同时连续调用发服务接口发送服务时,由于这个token采用的时取系统事件的毫秒级别作为key因此几个服务token可能一样,当第一个成功时后面成功的服务如果是同样的token则会找不到消费者,从而失败。
三 图片展示问题
3.1 类似2.1中的如果出现View的ondetachFromWindow无法正常执行,Fresco的BitmapMemoryCache的缓存会出现计数泄漏,这些图片无法正常回收
3.2:对于ImageView在ListView中使用时,如果其宽高设置的是MatchParent这种自适应的属性,注意同样的url的图片可能会BitmapMemoryCache的命中率非常低,因为Fresco中是以展示控件的高宽作为BitmapMemoryCache的key’,在上下滑动过程中由于view的复用机制,同一个url的imageview其高宽可能由于被其它view复用过了,而造成新的一次的请求中的高宽变化掉了,因而BitmapMemoryCache命中率降低。
3.3 项目中某种使用ListView的场景,某些ItemView是自己做的缓存,也就说getview时返回的View是业务模块中固定的View对象。而这些position对应返回的ViewType是0.这样对Fresco是会出现问题。问题是这些view里的图片概率性的图片会不加载。
分析了原因后,原来是由于我们这些view返回的ViewType是0,Listview还是会recycle它,但是由于我们自己强制返回的是自己业务模块中固定的view,这样虽然view是能正常显示。但是这个view对象还是会被放入recyclerViewPool中,它可能会被当做convertview给其它item复用,当然此时其它view肯定是不能接受它的,那么这个view的ondetach会被调用,此时这个view中image就不会加载了。解决的方法就是getviewtype直接返回“-1”,这样listview就不会把它放入recyclerViewPool中了,也就不会冲突了。
四 FPS监测工具
4.1 利用Choreographer.FrameCallback来计算FPS
4.2 利用Looper.getMainLooper().setMessageLogging(mPrinter);检测UI线程是否出现卡顿
五 页面跳转
5.1 减少跳转过程中主线程耗时事情,比如json解析,
5.2 精简Protobuf的契约Model,层级和Model个数
5.3 将主流程中不必要的事情后置,先加载和渲染第一屏数据
5.4 压缩XML层级