百科.dev
全部条目AI 编程趋势榜开源项目技术资讯提交条目
登录
< 返回工具列表
G

Graywater

> 数据库
开源

一个用于分解 RecyclerView 布局以提高滚动性能的 Android 库。

1.2K stars0 点赞0 次浏览
访问官网GitHub

工具介绍

一个用于分解 RecyclerView 布局以提高滚动性能的 Android 库。

Graywater: an android library for performant lists

Graywater is a RecyclerView adapter that facilitates the performant decomposition of complex and varied list items. It does this by mapping large data models to multiple viewholders, splitting the work needed to create a complex list item over multiple frames.

The concept is based off of Facebook's post on a faster news feed and Components for Android, which have been realized as Litho.

Tumblr developed Graywater to improve scroll performance, reduce memory usage, and lay the foundation for a more modular codebase.

The name "Graywater" comes from the process of recycling water.

  • What is it?
  • How do you use it?
  • How does it work?
  • Other features
  • An addendum on binders and generics

What is it?

An adapter basically takes a list of models (of type T) and maps them to a list of viewholders (of type VH extends RecyclerView.ViewHolder).

One naive solution is to map models directly to viewholders. For example, a list of "posts" can have a viewholder for each post. But this architecture quickly becomes slow and unwieldy if there is either a large variety of posts or if individual posts are complex.

So to improve performance, the parts of a post that are offscreen can be recycled.

   model       views
+---------+   +------+ 
|         |   | head |  | Binder | --> | ViewHolder |
+-------+     +--------+     +------------+

We no longer desire the one-to-one relationship between models and viewholders that, because monolithic models result in monolithic viewholders. For example, a video post (VideoPost) used to have a corresponding VideoPostViewHolder. Instead, we want VideoPost to be composed of a header, body, and footer.

                           +--------+     +------------+
                      /--> | Binder | --> | ViewHolder |
+-------+     +---+  /     +--------+     +------------+
| Model | --> | ? | *----> | Binder | --> | ViewHolder |
+-------+     +---+  \     +--------+     +------------+
                      \--> | Binder | --> | ViewHolder |
                           +--------+     +------------+

To manage this relationship, we introduce the concept of an ItemBinder, which aggregates the binders needed to display a post. It takes a model (T) and returns a list of binders, each of which bind the model to a specific viewholder.

                 +------------+
         /-----> | ItemBinder |
        /        +------------+
       /               v
      /            +--------+     +------------+
     /      /----> | Binder | --> | ViewHolder |
+-------+  /       +--------+     +------------+
| Model | *------> | Binder | --> | ViewHolder |
+-------+  \       +--------+     +------------+
            \----> | Binder | --> | ViewHolder |
                   +--------+     +------------+
  • ItemBinder takes a model T and maps it to a list of binders of type Binder.
  • Binder takes a model of type T and maps it to a ViewHolder of type VH

A minor design point is that RecyclerView.Adapter#onCreate() creates the viewholders, so some sort of mechanism for creating viewholders is necessary. This is where ViewHolderCreator comes in - it is a model-independent way of creating viewholders (in other libraries with a one-to-one relationship between models and viewholders, this code would live in the model - e.g. Epoxy).

…

Binders ItemBinders Items Screen
+--------+ +------------+ +-----------+ +--------+ | Photo | -------- | | /- | TextPost | | Header | +--------+ /---- | Photo Post | -\ / +-----------+ +--------+ | Footer | --x /--- | | ----- | PhotoPost | | | +--------+ x +------------+ / +-----------+ | | | Header | --x --- | | --/ /- | TextPost | | Text | +--------+ ---- | Text Post | / +-----------+ | | | Text | -------- | | ----/ | | +--------+ +------------+ +--------+


But on-screen, only the first item is visible, and out of the first item, only two components are visible. So in the above example, there is no need to load the "Footer" binder. This is what `List>` facilitates.

Binders ItemBinders Items Screen
+--------+ +------------+ +-----------+ +--------+ | Photo | | | /-- | TextPost | -x--- | Header | +--------+ | Photo Post | / +-----------+ \ +--------+ | Footer | | | / | PhotoPost | - | | +--------+ +------------+ / +-----------+ | | | Header | ---\ | | -/ | TextPost | | Text | +--------+ --- | Text Post | +-----------+ | | | Text | -------- | | | | +--------+ +------------+ +--------+


This is very useful for improving initialization performance when loading long cached lists by deferring binder creation until the binder is nearly on screen.

## How do you use it?

Graywater relies heavily on generics for type safety - here are the major type parameters:

  * `T` is the base model type.
  * `VH` is the base viewholder type.
  * `MT` is the type of the model type (e.g. `Class`).

Although this may seem overly generic, it is convenient if your base model or viewholder type has methods you need to access.

Add a model that subclasses `T`.

```java
class Text {
  String text;
}

Create the viewholder(s).

class TextViewHolder extends RecyclerView.ViewHolder {

  TextView textView;

  public TextViewHolder(View view) {
    super(view);
    textView = (TextView) view.findViewById(R.id.text);
  }
}

Create the corresponding ViewHolderCreator implementations.

class TextViewHolderCreator implements GraywaterAdapter.ViewHolderCreator {

  public TextViewHolder create(final ViewGroup parent) {
    return new TextViewHolder(GraywaterAdapter.inflate(parent, R.layout.item_text));
  }

  public int getViewType() {
    return R.layout.item_text;
  }
}

Create the Binder implementations for each ViewHolder.

class TextBinder implements GraywaterAdapter.Binder {

  public Class getViewHolderType() {
    return TextViewHolder.class;
  }

  public void prepare(final Text model, 
                      final List> binders, 
                      final int binderIndex) {
    
  }

  public void bind(final Text model, 
                   final TextViewHolder holder, 
                   final List> binders, 
                   final int binderIndex, 
                   final GraywaterAdapter.ActionListener actionListener) {
    holder.textView.setText(model.text);
  }

  public void unbind(final TextViewHolder holder) {
    holder.textView.setText(null);
  }
}

Create the ItemBinder that returns the list of binders for the model.

class TextItemBinder implements GraywaterAdapter.ItemBinder {
    
  TextBinder textBinder;

  public TextItemBinder(TextBinder textBinder) {
    this.textBinder = textBinder;
  }

  public List> getBinderList(
      final Text model, 
      final int position) {
    return new ArrayList>() {{
      add(textBinder);
      add(textBinder);
    }};
  }
}

Lastly, subclass GraywaterAdapter and register the created classes!

private static class TextAdapter extends GraywaterAdapter> {

  public TextAdapter() {
    register(new TextViewHolderCreator(), TextViewHolder.class);

    final TextBinder textBinder = new TextBinder();

    register(String.class, new TextItemBinder(textBinder), null);
  }

  @Override
  protected Class getModelType(final Text model) {
    return model.getClass();
  }
}

…

java
protected abstract MT getModelType(T model);

Instead of automatically using the class of the model as the model's type, it can be anything (preferably a similar property of the model).

Note that RecyclerView.Adapter has these methods:

abstract class Adapter {
  abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
  abstract void onBindViewHolder(VH holder, int position);
  int getItemViewType(int position);
  abstract int getItemCount();
}

It is important to note that position in the above methods is the viewholder position, not the model position. This distinction is extremely important, because when we are given the viewholder position when we need the model position.

For now, we assume that viewType has a one-to-one correspondence to the viewholder class.

Here is a visualization of the model and viewholder positions:

 model      viewholder
 position   position

 +-----+    +-----+
 |     |    |  0  |
 |     |    +-----+
 |     |    |  1  |
 |  0  |    +-----+
 |     |    |  2  |
 |     |    +-----+
 |     |    |  3  |
 +-----+    +-----+

 +-----+    +-----+
 |     |    |  4  |
 |  1  |    +-----+
 |     |    |  5  |
 +-----+    +-----+

If we are given a viewholder position of 5, we need to arrive at the model position of 1, that way we can grab the model from the backing data store.

The way to do this is to iterate through the models, going to the corresponding ItemBinder and accumulating the size of the list that is returned. Unfortunately, this is slow.

But in order to make it fast, we need to cache a lot of intermediary state. On add(), we compute these two caches, viewHolderToItemPosition and itemPositionToFirstViewHolderPosition. Note that the code uses item to refer to the model position.

 model      viewholder    viewHolderToItemPos   itemPosToFirstViewHolderPos
 position   position

 +-----+    +-----+
 |     |    |  0  |           { 0, 0 }
 |     |    +-----+
 |     |    |  1  |           { 1, 0 }
 |  0  |    +-----+                                    { 0, 0 }
 |     |    |  2  |           { 2, 0 }
 |     |    +-----+
 |     |    |  3  |           { 3, 0 }
 +-----+    +-----+

 +-----+    +-----+
 |     |    |  4  |           { 4, 1 }
 |  1  |    +-----+                                    { 1, 4 }
 |     |    |  5  |           { 5, 1 }
 +-----+    +-----+

viewHolderToItemPositionCache is also used for getItemCount().

itemPositionToFirstViewHolderPosition is primarily used for one purpose: to determine the position of the viewholder and associated binder in the list of viewholders for a given model. In the above example, the viewholder at position 5 is the 2nd viewholder for the 2nd model. This is important when there is more than one instance of a viewholder for a model, such as reblog comments.

getItemViewType() works by tracking the registered ViewHolderCreators, which have this interface:

interface ViewHolderCreator {
  RecyclerView.ViewHolder create(ViewGroup parent);
  int getViewType();
}

When a new ViewHolderCreator is registered, it is added to viewHolderCreatorList, which is of type SparseArray>, and associates the viewType with the correct class. The class is then associated with the ViewHolderCreator via viewHolderCreatorMap, which is of type Map, ViewHolderCreator>.

     viewHolderCreatorList                     viewHolderCreatorMap
+------------------------------+

Issues· 0 开放

查看全部 Issues在 GitHub 打开

暂无开放 Issues,或尚未同步最近议题。

> 标签

Javaandroidandroid-libraryperformancerecyclerview-adapter

暂无评论,来聊聊你的看法吧

> 工具信息

发布日期2026年8月1日
最后更新2026年9月17日
分类数据库
定价开源

> 相关工具

P
PostgreSQL
功能强大的开源关系型数据库
R
Redis
内存数据结构存储,常用作缓存与队列
M
MySQL
广泛使用的开源关系型数据库