Android 列表(ListView、RecyclerView)不断刷新最佳实践
本文微信公众号「AndroidTraveler」首发。
背景
在 Android 列表开发过程中,有时候我们的 Item 会有一些组件,比如倒计时。这类组件要求不断刷新,这个时候由于列表复用的机制,因此会有一些坑。那么我们本篇文章就给大家讲两个主题。
第一个是列表复用是否一定有问题。
第二个是出现问题有哪些解决方案可供我们选择。
小 Demo
由于我们的主题重点是为了解决不断刷新问题,因此关于 RecyclerView 的基本使用就不再赘述,不清楚的小伙伴可以看下我之前的文章:
RecyclerView基本使用
首先我们看下效果图:
很简单,就是一个 RecyclerView 列表,列表项有两个组件。分别代表第几项和剩余秒数。
这里就是通过倒计时来演示刷新可能存在的问题。
重点代码是 Adapter 里面的显示逻辑,初始为:
@Override
public void onBindViewHolder(RecyclerViewViewHolder holder, int position) {
holder.mTvNum.setText(String.valueOf(position + 1));
updateTime(holder, itemList.get(position));
}
private void updateTime(final RecyclerViewViewHolder holder, final long time) {
String content;
long remainTime = time - System.currentTimeMillis();
remainTime /= 1000;
if (remainTime <= 0) {
content = "Time up";
holder.mTxtTitle.setText(content);
return;
}
content = "剩下"+remainTime+"秒";
holder.mTxtTitle.setText(content);
}
全部代码见:https://github.com/nesger/RecyclerView/tree/feature/refresh
接下来我们增加刷新方法,有很多种,我们一一说明。
1. 使用 handler 来实现倒计时刷新
修改显示代码,如下:
@Override
public void onBindViewHolder(RecyclerViewViewHolder holder, int position) {
holder.mTvNum.setText(String.valueOf(position + 1));
updateTime(holder, itemList.get(position));
}
private void updateTime(final RecyclerViewViewHolder holder, final long time) {
String content;
long remainTime = time - System.currentTimeMillis();
remainTime /= 1000;
if (remainTime <= 0) {
content = "Time up";
holder.mTxtTitle.setText(content);
return;
}
content = "剩下"+remainTime+"秒";
holder.mTxtTitle.setText(content);
holder.mTxtTitle.postDelayed(new Runnable() {
@Override
public void run() {
updateTime(holder, time);
}
}, 1000);
}
可以看到通过 handler 延时一秒,然后每次更新时间也是减少一秒。
我们看下效果图:
可以看到没滚动之前还好,滚动之后会发现,倒计时都乱了。
当然有时候可能不会暴露出来,比如滚动数目少,或者只有部分组件有倒计时,不像我们这个例子,所有项目都有倒计时,但是这也间接留下了可能的坑。
出现这个问题的原因在于组件的复用,如果你用 ListView 演示,并且不用复用,那么是不会错乱的。
当然列表不复用这个肯定是不推荐的。
因此,该方式不推荐。
全部代码见:https://github.com/nesger/RecyclerView/tree/feature/refresh_1
2. 使用 Timer 来实现倒计时刷新
@Override
public void onBindViewHolder(RecyclerViewViewHolder holder, int position) {
holder.mTvNum.setText(String.valueOf(position + 1));
updateTime(holder, itemList.get(position));
}
private void updateTime(final RecyclerViewViewHolder holder, final long time) {
String content;
long remainTime = time - System.currentTimeMillis();
remainTime /= 1000;
if (remainTime <= 0) {
content = "Time up";
holder.mTxtTitle.setText(content);
return;
}
content = "剩下"+remainTime+"秒";
holder.mTxtTitle.setText(content);
}
一样不行,不推荐。
全部代码见:https://github.com/nesger/RecyclerView/tree/feature/refresh_2
3. 使用 Timer + View 集合
其实我们简单分析一下就知道,出现上面错乱情况的原因大致是两个:一个是复用,一个是代码多次调用。
所以如果能够解决这两个问题,那么这个问题就解决了。
因为我们这里的业务是倒计时监听,所有 View 都是一样的,就是一秒更新一次。
所以我们的定时器不需要 N 个,只需要一个,在构造函数初始化即可。
另外为了避免复用和代码多次调用问题,我们将 View 通过一个集合保存起来。
最后修改的代码如下:
private Timer mTimer;
private Set<RecyclerViewViewHolder> mHolders;
public RecyclerViewAdapter(Activity activity, List<Long> itemList) {
if (activity == null || itemList == null) {
throw new IllegalArgumentException("params can't be null");
}
this.activity = activity;
this.itemList = itemList;
mHolders = new HashSet<>();
mTimer = new Timer();
mTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
for (RecyclerViewViewHolder holder : mHolders) {
updateTime(holder, holder.getTime());
}
}
}, 0, 1000);
}
@Override
public void onBindViewHolder(RecyclerViewViewHolder holder, int position) {
holder.setTime(itemList.get(position));
mHolders.add(holder);
holder.mTvNum.setText(String.valueOf(position + 1));
updateTime(holder, itemList.get(position));
}
效果图如下:
可以看到没问题了。
当然这里有些优化还没处理,因为本篇主要是思路分析,这里就不添加了。
待优化点:定时器的启动和关闭跟生命周期关联,无数据源不启用定时器等。
全部代码见:https://github.com/nesger/RecyclerView/tree/feature/refresh_3
该方法来自与一名朋友的分享。
4. 使用 ScheduledExecutorService + View 集合
这边 AndroidStudio 有安装阿里巴巴提供的一个代码检测插件,链接为:https://plugins.jetbrains.com/plugin/10046-alibaba-java-coding-guidelines
在 AndroidStudio 输入插件名字 Alibaba Java Coding Guidelines 查找安装即可。
在方法 3 使用 Timer 时提示下面信息:
Run multiple TimeTask by using ScheduledExecutorService rather than Timer because Timer will kill all running threads in case of failing to catch exceptions.
//org.apache.commons.lang3.concurrent.BasicThreadFactory
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
//do something
}
},initialDelay,period, TimeUnit.HOURS);
所以我们这里修改 Timer 为 ScheduledExecutorService:
private ScheduledExecutorService mExecutorService;
public RecyclerViewAdapter(Activity activity, List<Long> itemList) {
if (activity == null || itemList == null) {
throw new IllegalArgumentException("params can't be null");
}
this.activity = activity;
this.itemList = itemList;
mHolders = new HashSet<>();
mExecutorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
@Override
public Thread newThread(@NonNull Runnable r) {
Thread thread = new Thread(r);
thread.setName("countdown");
return thread;
}
});
mExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
for (RecyclerViewViewHolder holder : mHolders) {
updateTime(holder, holder.getTime());
}
}
}, 0, 1000, TimeUnit.MILLISECONDS);
}
全部代码见:https://github.com/nesger/RecyclerView/tree/feature/refresh_4
有更多方法欢迎到上面的 GitHub 链接提 PR,可以基于 feature/refresh 分支新建分支。
有另外一位朋友提出了自定义 View 的处理方式,将倒计时的功能放到 View 里面去处理,这个感兴趣的小伙伴可以实现然后提 PR 哈,这里提供额外一种思路。
Android 列表(ListView、RecyclerView)不断刷新最佳实践的更多相关文章
- Android 6.0 权限管理最佳实践
博客: Android 6.0 运行时权限管理最佳实践 github: https://github.com/yanzhenjie/AndPermission
- [转]Android ORM系列之GreenDao最佳实践
GreenDAO是一个可以帮助Android开发者快速将Java对象映射到SQLite数据库的表单中的ORM解决方案,通过使用一个简单的面向对象API,开发者可以对Java对象进行存储.更新.删除和查 ...
- Android 加载GIF图最佳实践
转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/75578109 本文出自[赵彦军的博客] 起因 最近在项目中遇到需要在界面上显示一个 ...
- Android学习之活动的最佳实践
•问题的起源 先来模拟一个场景:打开一个 App,最先映入眼帘的是主活动(MainActivity),在该活动中给用户提供了一个 Button, 用户点击该 Button 实现由 MainActivi ...
- [转]Android开发最佳实践
——欢迎转载,请注明出处 http://blog.csdn.net/asce1885 ,未经本人同意请勿用于商业用途,谢谢—— 原文链接:https://github.com/futurice/and ...
- fir.im Weekly - 2016 年 Android 最佳实践列表
2016 年已经过去一半,你在年初制定的成长计划都实现了吗? 学海无涯,技术成长不是一簇而就的事情.本期 fir.im Weekly 推荐 王下邀月熊_Chevalier的 我的编程之路--知识管理与 ...
- Google Developing for Android 三 - Performance最佳实践
Google Developing for Android 三 - Performance最佳实践 发表于 2015-06-07 | 分类于 Android最佳实践 原文 Developing ...
- Android开发之漫漫长途 XIII——Fragment最佳实践
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...
- Android学习之基础知识七—碎片的最佳实践
一.Android碎片(Fragment)的最佳实践——简易版新闻应用 第一步:新建FragmentBestPractice项目,在app/build.gradle当中添加:RecyclerView ...
随机推荐
- ThreadLocal的认知与见解
ThreadLocal:提高一个线程的局部变量,访问某个线程拥有自己局部变量(很难理解.看看下面这句话,顺便再讲个例子). 当使用ThreadLocal维护变量时,ThreadLocal为每一个使用该 ...
- Hbase 统计表行数的3种方式总结
有些时候需要我们去统计某一个Hbase表的行数,由于hbase本身不支持SQL语言,只能通过其他方式实现.可以通过一下几种方式实现hbase表的行数统计工作: 1.count命令 最直接的方式是在hb ...
- 常用 JS 函数
各种业务开发都离不开对数据的处理,然而遇到的很多数据都是不好处理的.这个时候就需要寻求搜索引擎的帮助.这种方法效率是非常低下的,而且根据作者的个性不能保证其对自己的口味.因此这篇文字包含了一份 JS ...
- Java中创建对象的5种方法
将会列举5种方法去创建 Java 对象,以及他们如何与构造函数交互,并且会有介绍如何去使用这些方法的示例. 作为一个 Java 开发人员,我们每天都会创建大量的 Java 对象,但是我们通常会使用依赖 ...
- vue2.0生成二维码图片并且下载图片到本地兼容写法
vue生成二维码图片,这里使用的是qrcode.js 这个插件(亲测写法,兼容没有问题) 第一步,下载插件 需要注意,这里下载的是qrcodejs2 cnpm install --save qrcod ...
- java教程系列二:Java JDK,JRE和JVM分别是什么?
多情只有春庭月,犹为离人照落花. 概述 本章主要了解JDK,JRE和JVM之间的区别.JVM是如何工作的?什么是类加载器,解释器和JIT编译器.还有一些面试问题. Java程序执行过程 在深入了解Ja ...
- Java面试-interrupt
我们都知道,Java中停止一个线程不能用stop,因为stop会瞬间强行停止一个线程,且该线程持有的锁并不能释放.大家多习惯于用interrupt,那么使用它又有什么需要注意的呢? interrupt ...
- node连接数据库
一.在package.json依赖模块添加: "mysql" : "latest",执行npm install: 二.module目录下新建mysql.js: ...
- html常见的块元素与内联(行内)元素用法说明(一)
html平时常见的块元素有:div, p, h1, h2, h3等,内联元素有:span, a, img等. 块元素的属性:无论内容是什么,都会独占一整行.主要用于页面布局. 内联元素的属性:只占自身 ...
- APP功能测试要点
1.功能性测试 根据产品需求文档编写测试用例而进行测试,包括客户端的单个功能模块以及功能业务逻辑(功能交互)如:涉及输入的地方需要考虑等价类,边界值,异常或非法等 1.1 安装与卸载测试 >软件 ...