【翻译】安卓架构组件(7)-分页库

相关文章:

分页库使你的app能够在需要从一个数据源逐步加载数据时更为轻松,避免了过度加载以及为了一个大型数据的查询等待太多时间。

综述

许多app伴随着大量的数据,但是在任意时刻往往只需要加载和显示其中的一小部分。一个app可能存在数以万计的数据以供显示,但在某一时刻仅仅需要访问其中的几十条。如果app没有认真处理这个这个问题,会请求实际上并不需要的数据,并导致不必要的性能负担和网络带宽。如果数据并没有被存储起来或同步到远程数据库,这同样会减慢app的运行速度以及浪费用户的流量。

当然现有的安卓API允许对内容进行分页,它们伴有明显的约束和缺点:

  • CursorAdapter使得将数据库查询结果映射到ListView列表项更为轻松,但它会在UI线程执行数据库查询,并且使用Cursor的分页并不高效。对于使用CursorAdapter的更多缺陷,请查询这里
  • AsyncListUtil允许将基于位置的分页数据加载至RecyclerView,但是并不允许无位置的分页,而且它会强迫使用空白占用位。

新的分页库致力于解决这些问题。这个类库包含一些类,以流水线的方式处理你所需数据的请求过程。这些类也可以和已经存在的架构类组件无缝结合,如Room

分页库提供以下的类以及额外的支持类:

DataSource

使用这个类定义一个你用来分页拉取的数据源。根据如何访问你的数据,你可以集成以下两个子类之一:

  • KeyedDataSource 如果你需要从第N个数据项获取第N+1个数据项。例如,如果你的线程在一个讨论的app获取评论,你可能需要传递一个评论的id来获取下一个评论。
  • TiledDataSource 如果你需要从你的数据源中获取任意指定位置的分页数据。这个类支持从你选择的任何位置请求数据,例如:“返回从位置1200开始的20条数据”。

如果你使用Room来管理你的数据,它会自动创建一个TiledDataSource,例如:

@Query("select * from users WHERE age > :age order by name DESC, id ASC")
TiledDataSource<User> usersOlderThan(int age);

PagedList

这个类从DataSource加载数据。你可以配置在任何时刻该获取多少数据,以及需要预加载多少数据,以最小化用户等待数据被加载时等待的时间。这个类可以给其他类提供更新信号,例如RecyclerView.Adapter,允许当数据分页加载时更新你的RecyclerView

PagedListAdapter

这个类是RecyclerView.Adapter的子类,使得数据从PagedList中展现。例如,当新的一页数据被加载时,PagedListAdapter通知RecyclerView数据已经到达,这让RecyclerView替换任何占位项,并展示合适的动画效果。

PagedListAdapter使用后台线程计算从一个PagedList到另一个的变化(例如,当数据库的变更导致数据的更新),并调用notifyItem...()方法,并在需要时更新列表数据内容。之后RecyclerView展现必要的变化。例如,当一个数据项在不同的PagedList版本间变更位置时,RecyclerView动画会展示数据项移动到新的位置。

LivePagedListProvider

这个生成从你提供的DataSource生成一个LiveData<PagedList>。此外,如果你使用Room持久化类库来管理你的数据库,DAO可以使用TiledDataSource生成LivePagedListProvider,例如:

@Query("SELECT * from users order WHERE age > :age order by name DESC, id ASC")
public abstract LivePagedListProvider<Integer, User> usersOlderThan(int age);

Integer参数告知Room基于位置加载的TiledDataSource

分页库组件在后台线程组织了数据流,并在UI线程展示。例如:当一个新的数据项被插入到你的数据库时,DataSource会失效, 后台线程产生一个新的PagedList

图1 分页库组件在后台线程执行大部分工作,因此并不会影响UI线程

新创建的PagedList在UI线程被发送到PagedListAdapter。之后PagedListAdapter使用DiffUtil在后台线程计算当前列表和新列表的不同。当比较结束时,PagedListAdapter使用比较得到的差异信息适当地调用RecyclerView.Adapter.notifyItemInserted()来通知新的数据项被插入。

RecyclerView在UI线程知道仅仅需要绑定一个新的数据项,并使用动画展示在屏幕上。

下面的代码示例显示了所有相关部分的工作。当数据库添加、删除或者更新时,RecyclerView的内容自动且高效地更新:

@Dao
interface UserDao {
    @Query("SELECT * FROM user ORDER BY lastName ASC")
    public abstract LivePagedListProvider<Integer, User> usersByLastName();
}

class MyViewModel extends ViewModel {
    public final LiveData<PagedList<User>> usersList;
    public MyViewModel(UserDao userDao) {
        usersList = userDao.usersByLastName().create(
                /* 初始化加载位置 */ 0,
                new PagedList.Config.Builder()
                        .setPageSize(50)
                        .setPrefetchDistance(50)
                        .build());
    }
}

class MyActivity extends AppCompatActivity {
    @Override
    public void onCreate(Bundle savedState) {
        super.onCreate(savedState);
        MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
        RecyclerView recyclerView = findViewById(R.id.user_list);
        UserAdapter<User> adapter = new UserAdapter();
        viewModel.usersList.observe(this, pagedList -> adapter.setList(pagedList));
        recyclerView.setAdapter(adapter);
    }
}

class UserAdapter extends PagedListAdapter<User, UserViewHolder> {
    public UserAdapter() {
        super(DIFF_CALLBACK);
    }
    @Override
    public void onBindViewHolder(UserViewHolder holder, int position) {
        User user = getItem(position);
        if (user != null) {
            holder.bindTo(user);
        } else {
            // NULL时定义了一个占位项——当实际的对象被从数据库加载时,PagedListAdapter会自动失效该行
            holder.clear();
        }
    }
    public static final DiffCallback<User> DIFF_CALLBACK = new DiffCallback<User>() {
        @Override
        public boolean areItemsTheSame(@NonNull User oldUser, @NonNull User newUser) {
            // 用户属性可能会在重新加载时变化,但id是固定的。
            return oldUser.getId() == newUser.getId();
        }
        @Override
        public boolean areContentsTheSame(@NonNull User oldUser, @NonNull User newUser) {
            // 注意:如果你使用equals,你的对象会重载Object.equals()方法
            // 如果不正确地返回false会导致许多动画效果
            return oldUser.equals(newUser);
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,544评论 25 707
  • 相关文章: 【翻译】安卓架构组件(2)-添加组件到你的项目中 【翻译】安卓架构组件(3)-处理生命周期 【翻译】安...
    Chuckiefan阅读 4,352评论 16 66
  • 感情像一杯酒 第一个人碰洒了 还剩一半 我把杯子扶起来兑满 留给第二个人 他又碰洒了 我还是扶起兑满 留给第三个人...
    Bnana_阅读 170评论 0 1
  • “妈,那怎么多了两个坟头?”我指着前方。 “那个啊,是你李大爷和王老爷的坟头。”妈妈叹息的说,“最近的一个月我们村...
    唯忆你阅读 225评论 0 1