公司App的相册加载速度太慢了,之前提过一次,然后一个同事就去做这个优化工作了,这两天突然想起来了,就看一下他优化的效果,发现加载速度还是太慢。以我的HUAWEI ANE-LX2 Android 8.0.0 API 26测试机为例,8000多张图片首次加载时间大约4s左右,而且如果切换排序,这个时间也挺长,总之,我认为体验效果非常不好,最好优化到1s左右。
我先看了下他优化的代码,就是用了IntentService开一个线程去请求系统数据库;然后cursor.moveToNext()读取字段并构造我们的ImageInfo结构,添加到List列表(会同时维护All、Video、Image三个列表)中;遍历结束后,通过EventBus发送消息到主线程,然后调用RecyclerView的Adapter来添加数据更新界面。虽然将耗时任务放在了子线程,但是由于8000多条遍历及ImageInfo的创建,消耗了大量内存和时间。
现在分析下功能需求:我们的App是视频编辑类App,用户使用相册不会选择全部,或者说进入的时候不需要看到全部内容,所以只需要加载一屏的图片数据就好了,其他的没有显示的部分,等用户切换卡片或者下滑时候加载更多。换句话说,优化成recyclerview下拉加载更多样式。
先说下我优化后的结果:保证功能效果的同时,从点击功能点到显示列表,用时300多ms,和之前的4s左右相比,用户体验提升非常明显。
优化步骤:
1、将All、Video、Image三个Fragment改为懒加载模式,具体方法google一下。
2、将RecyclerView改为分段加载模式。
3、分页请求数据。由于每次查询60条数据库时间都在几十毫秒,时间可以说是很及时,所以不对查询的结果及构建的ImageInfo列表进行存储,减少内存的长期占用。构造的查询字段,参数是文件夹名和修改时间(时间倒序排列),构建完ImageInfo数据后EventBus发送到指定Fragment页面进行页面更新处理。
4、预加载一定数量的RecyclerView子视图。这个页面,我们的设计是一排四个子视图,每个子视图都是正方形,那么一屏的子视图数就在(ScreenHeight/(ScreenWidth/4) + 2) * 4个(多创建一排,防止不够用)。其实一个布局文件inflate耗时也就35ms左右,但是顶不住数量多啊,我这个测试手机一次要建28个,那总时间就变成了35 * 28 = 980ms,加上其他时间的耗费,单个Fragment显示的用时在1s多,所以把大量子视图在子线程创建以免阻塞main线程非常有必要。
使用的时候就简单判断下:
按以上方法到这里,你运行一下,其实就发现相册加载性能提升已经非常明显了,但是有个问题就是再次执行以上onCreateViewHolder时候会报异常:java.lang.IllegalStateException: ViewHolder views must not be attached when created. Ensure that you are not passing 'true' to the attachToRoot parameter of LayoutInflater.inflate(..., boolean attachToRoot)。看提示意思就是view已经有了一个parent了,再使用时候添加一个parent就会出错,因为view只能有一个直接parent视图。那解决方案就是清理一下view的parent,你搜一下会发现view中没有setParent的方法,有一个assignParent还是私有的,所以重置不了view的视图的parent,那就没法重复利用。好在突然想到view是在recyclerview中存在,在在fragment要销毁时候,RecyclerView可以清理一下所有子视图来达到重置view的parent的目的。看下RecyclerView的removeAllViewsInLayout源码:
public voidremoveAllViewsInLayout() {
for(
inti = count - 1; i >= 0; i--) {
finalView view = children[i];
……
view.mParent = null; //重置view的parent。……
}
……
}
好了,优化到此已经结束,希望你的相册进入时间也进入毫秒级。