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 哈,这里提供额外一种思路。

原文地址:https://www.cnblogs.com/nesger/p/11715796.html

时间: 2024-10-09 06:36:09

Android 列表(ListView、RecyclerView)不断刷新最佳实践的相关文章

[转]Android ORM系列之GreenDao最佳实践

GreenDAO是一个可以帮助Android开发者快速将Java对象映射到SQLite数据库的表单中的ORM解决方案,通过使用一个简单的面向对象API,开发者可以对Java对象进行存储.更新.删除和查询. GreenDao有两个项目,一个是生成dao和model的generator的项目,该项目是java项目,一个是用于android的核心jar包.在使用前,我们必须先生成dao和model. 首先加入依赖. compile 'de.greenrobot:greendao:2.0.0' comp

Android ORM系列之GreenDao最佳实践

GreenDAO是一个可以帮助Android开发者快速将Java对象映射到SQLite数据库的表单中的ORM解决方案,通过使用一个简单的面向对象API,开发者可以对Java对象进行存储.更新.删除和查询. GreenDao有两个项目,一个是生成dao和model的generator的项目,该项目是java项目,一个是用于android的核心jar包.在使用前,我们必须先生成dao和model. 首先加入依赖. compile 'de.greenrobot:greendao:2.0.0' comp

产品待办列表的几个最佳实践

产品待办列表是什么 产品待办列表对应的英文是project backlog,也有翻译为"产品待办事项列表",是指为开发完善产品而待办的事项列表. 在Scrum Guide中,产品待办列表是一个排序的列表,包含所有产品需要的东西,也是产品需求变动的唯一来源.产品负责人负责产品待办事项列表的内容.可用性和优先级.产品待办事项列表永远是不完全的,最初的版本只列出最初始的和众所周知的需求.产品待办事项列表根据产品和开发环境的变化而演进.待办事项列表是动态的,它经常发生变化以识别使产品合理.有竞

Android 最佳实践

从事android开发两年有余,但是自己的代码自己却不太敢恭维.于是我不得不来改善我的Android程序,为了使它变得更加的稳固,更加的专业.本文旁征博引,多处观点都是Google之,结合自己的经验总结了Android开发中的一些最佳实践,厚积方能博发,多积累多学习才能多进步. Android Activity 的生命周期 生命周期(Lifecycle)描述的是Android一个页面从开始创建到消亡的整个过程.伴随着一系列对象的创建及消失,涵盖着整个页面所包含的业务逻辑.以及与用户之间的交互,接

Android开发最佳实践---Futurice之见

原文链接:https://github.com/futurice/android-best-practices 本文是Futurice公司的Android开发人员总结的最佳实践,遵循这些准则可以避免重复制造轮子.如果你对iOS或者WindowsPhone开发感兴趣,那么也请看看iOS最佳实践和Windows客户端开发最佳实践. 第一版翻译自:http://blog.csdn.net/asce1885 Android开发技术日新月异, Github上也有较大更新, 故对原文有增删 CSDN的mar

[转]Android开发最佳实践

——欢迎转载,请注明出处 http://blog.csdn.net/asce1885 ,未经本人同意请勿用于商业用途,谢谢—— 原文链接:https://github.com/futurice/android-best-practices 本文是Futurice公司的Android开发人员总结的最佳实践,遵循这些准则可以避免重复制造轮子.如果你对iOS或者Windows Phone开发感兴趣,那么也请看看iOS最佳实践和Windows客户端开发最佳实践. 概要 使用Gradle和推荐的工程结构

Android在滚动列表中实现视频的播放 ListView RecyclerView

英文原文:Implementing video playback in a scrolled list (ListView & RecyclerView) 本文将讲解如何在列表中实现视频播放.类似于诸如 Facebook, Instagram 或者 Magisto这些热门应用的效果: Facebook: Magisto: Instagram: 这片文章基于开源项目: VideoPlayerManager. 所有的代码和示例都在那里.本文将跳过许多东西.因此如果你要真正理解它是如何工作的,最好下载

Android最佳实践之性能 - 提升Layout性能

优化布局结构 参考地址:http://developer.android.com/training/improving-layouts/optimizing-layout.html 布局是Android应用程序的关键部分,直接影响到用户体验.如果实现的不好,布局会消耗大量内存,应用程序UI会变得缓慢.Android SDK包含工具来帮助你识别布局性能问题,结合最佳实践,你将能够实现流畅的滚动体验和一个最低内存的占用. 我们有一个误解,就是使用基本的布局结构,可以实现最有效率的布局.加到App中的

Android最佳实践之高效的应用导航

设计(一)- 规划Screens和他们之间的关系 原文地址:http://developer.android.com/training/design-navigation/screen-planning.html#beyond-simplistic-design 设计和开发Android应用程序的第一个步骤是确定用户能够查看和处理应用.一旦你知道用户与之交互的应用程序之间交互什么数据,下一步就是设计交互,允许用户导航到app的不同部分,进入和退出应用程序中的界面. 这篇文章开始向你展示如何规划高