理解Android Architecture Components系列之Paging Library(七)

Paging Library(分頁加載庫)用于逐步從數(shù)據(jù)源加載信息,而不會(huì)耗費(fèi)過多的設(shè)備資源或者等待太長的時(shí)間。

總體概覽

一個(gè)常見的需求是獲取很多數(shù)據(jù),但是同時(shí)也只展示其中的一小部分?jǐn)?shù)據(jù)。這就會(huì)引起很多問題,比如浪費(fèi)資源和流量等。
現(xiàn)有的Android APIs可以提供分頁加載的功能,但是也帶來了顯著的限制和缺點(diǎn):

  • CursorAdapter,使得從數(shù)據(jù)庫加載數(shù)據(jù)到ListView變得非常容易。但是這是在主線程中查詢數(shù)據(jù)庫,并且分頁的內(nèi)容使用低效的 Cursor返回。更多使用CursorAdapter帶來的問題參考Large Database Queries on Android。
  • AsyncListUtil提供基于位置的( position-based)分頁加載到 RecyclerView中,但是無法使用不基于位置(non-positional)的分頁加載,而且還強(qiáng)制把null作為占位符。

paging library就是為了解決上述的問題而提出的。

提供的類

DataSource
數(shù)據(jù)源。根據(jù)你想要訪問數(shù)據(jù)的方式,可以有兩種子類可供選擇:

例如使用 Room persistence library 就可以自動(dòng)創(chuàng)建返回 TiledDataSource類型的數(shù)據(jù):

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

PagedList

DataSource獲取指定數(shù)量的數(shù)據(jù),并且可以制定預(yù)取多少數(shù)據(jù)。這樣可以最大程度減少加載數(shù)據(jù)的時(shí)間。這個(gè)類可以提供更新信息給其他類比如RecyclerView.Adapter來更新 RecyclerView的UI。

PagedListAdapter

這個(gè)類是 RecyclerView.Adapter的一個(gè)實(shí)現(xiàn)類,用于當(dāng)數(shù)據(jù)加載完畢時(shí),通知 RecyclerView數(shù)據(jù)已經(jīng)到達(dá)。 RecyclerView就可以把數(shù)據(jù)填充進(jìn)來,取代原來的占位元素。
PagedListAdapter使用后臺(tái)線程來計(jì)算 PagedList 的改變。

LivePagedListProvider

從數(shù)據(jù)源中產(chǎn)生 LiveData<PagedList>。此外如果使用的是 Room persistence library,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);

通過上面的類的配合使用,paging library從后臺(tái)線程獲取數(shù)據(jù)流,再在UI線程中展示。例如,當(dāng)新的item別插入到數(shù)據(jù)庫, DataSource被更新, LivePagedListProvider在后臺(tái)線程產(chǎn)生了新的 PagedList

詳細(xì)的流程如下圖:


paging-threading.gif

新生成的 PagedList在主線程中被發(fā)送到PagedListAdapter, PagedListAdapter在后臺(tái)線程使用 DiffUtil計(jì)算新的list和原來的list的差距。當(dāng)比較完畢, PagedListAdapter調(diào)用RecyclerView.Adapter.notifyItemInserted()來通知數(shù)據(jù)刷新。
下面的示例代碼展示了這些類如何配合工作:

@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(
                /* initial load position */ 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 defines a placeholder item - PagedListAdapter will automatically invalidate
            // this row when the actual object is loaded from the database
            holder.clear();
        }
    }
    public static final DiffCallback<User> DIFF_CALLBACK = new DiffCallback<User>() {
        @Override
        public boolean areItemsTheSame(@NonNull User oldUser, @NonNull User newUser) {
            // User properties may have changed if reloaded from the DB, but ID is fixed
            return oldUser.getId() == newUser.getId();
        }
        @Override
        public boolean areContentsTheSame(@NonNull User oldUser, @NonNull User newUser) {
            // NOTE: if you use equals, your object must properly override Object#equals()
            // Incorrectly returning false here will result in too many animations.
            return oldUser.equals(newUser);
        }
    }
}

好了,整個(gè)Android Architecture Components系列都介紹完了(并沒有,還有一篇WorkManager相關(guān)的文章,感興趣的可以看看),感謝各位大佬的閱讀。

整個(gè)系列的后兩篇文章相對(duì)介紹的比較粗糙一點(diǎn),主要還是因?yàn)闀r(shí)間比較緊張,還有其實(shí)Room和Paging Library更像是一個(gè)第三方庫,主要還是需要大家去寫一遍感受可能會(huì)更清晰一點(diǎn)。

PS.鑒于大家的都建議給一個(gè)整體框架的demo,這里可以提供一個(gè)更好的方案:Google Android Architecture Components,這是Google官方提供的樣例可以用來參考。

相關(guān)文章:
理解Android Architecture Components系列(一)
理解Android Architecture Components系列(二)
理解Android Architecture Components系列之Lifecycle(三)
理解Android Architecture Components系列之LiveData(四)
理解Android Architecture Components系列之ViewModel(五)
理解Android Architecture Components系列之Room(六)
理解Android Architecture Components系列之Paging Library(七)
理解Android Architecture Components系列之WorkManager(八)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,319評(píng)論 25 708
  • afinalAfinal是一個(gè)android的ioc,orm框架 https://github.com/yangf...
    passiontim閱讀 15,896評(píng)論 2 45
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 47,176評(píng)論 22 665
  • 其實(shí)很早之前也寫了一篇Gradle的基礎(chǔ)博客,但是時(shí)間很久了,現(xiàn)在Gradle已經(jīng)更新了很多,所以暫且結(jié)合Stdu...
    Ten_Minutes閱讀 1,088評(píng)論 2 5
  • 一、經(jīng)過上一節(jié),我們已經(jīng)簡單的把該準(zhǔn)備的步驟完成了,現(xiàn)在就要進(jìn)入實(shí)際操作步驟。首先我們?cè)趺慈?duì)他進(jìn)行使用呢? 二、...
    大笨象與小賴皮閱讀 8,465評(píng)論 0 1

友情鏈接更多精彩內(nèi)容