怎麼學習谷歌最新的組件--Jetpack?一文介紹paging的基本使用


怎麼學習谷歌最新的組件--Jetpack?一文介紹paging的基本使用

樓主最近在學習Google爸爸最新的組件--Jetpack,說實話,看文檔誰都看得懂,但是動起手來就徹底懵逼。就比如說,今天要介紹的paging的基本使用,樓主就踩了兩天的坑。這個過程中比較麻煩的一點就是,官方文檔和網上大部分資料都是用kotlin語言寫的,可惜我不懂kotlin,只會寫一點小Java。  在閱讀本文之前,我默認大家都瞭解jetpack是什麼。因此本文不再介紹jetpack,注重介紹paging怎麼使用,本文會大量的貼出Demo代碼,供大家理解和參考,同時也會介紹這其中我踩的坑。  本文參考資料:

Paging library overview Part of Android JetpackPaging library data components and considerations

在閱讀本文之前,最好是閱讀過上面兩篇官方文檔。其中第一篇介紹的是paging的基本使用,第二篇是對第一篇的補充,主要介紹怎麼自定義DataSource。

1. Demo概述

在正式介紹paging 的使用之前,我們先來看看最終的效果是怎麼樣的。

怎麼學習谷歌最新的組件--Jetpack?一文介紹paging的基本使用

效果是非常的簡單,就是有一個RecyclerView在顯示數據,然後我們向下滑動,滑動到一定位置,paging會自動請求數據,這也就是分頁請求的效果.paging的目的就是實現分頁加載,之前我們都是自己實現分頁,這次是Google爸爸為我們實現。不得不說,jetpack真香!

2. 準備components

要想使用paging,我們得準備幾個東西(當然,導依賴這種基本操作肯定是必須的,我就不介紹了哈)。

DataSource:顧名思義,數據源,獲取數據是通過它實現的。PagedList: 數據源獲取的數據最終靠PagedList來承載。對於PagedList,我們可以這樣來理解,它就是一頁數據的集合。每請求一頁,就是新的一個PagedList對象。PagedListAdapter:這個Adapter就是一個RecyclerView的Adapter。不過我們在使用paging實現RecyclerView的分頁加載效果,不能直接繼承RecyclerView的Adapter,而是需要繼承PagedListAdapter。

3. 自定義DataSource

說實話,自定義DataSource沒有什麼難度,但是我為什麼要單獨提出來呢?首先官方文檔上,實現的是ItemKeyedDataSource,而我這裡實現的是PositionalDataSource。雖然差別不是很大,但是還是有點區別,因為在這裡我踩過兩個坑。我相信,通過這兩個坑,以後遇到類似的問題,會找到解決思路。  在貼代碼之前,我們先來看看實現PositionalDataSource需要注意的地方。  首先,我們得了解一下需要實現的兩個方法:

方法名含義loadInitial加載初始化數據,可以這麼來理解,加載的是第一頁的數據。形象的說,當我們第一次打開頁面,需要回調此方法來獲取數據。loadRange當有了初始化數據之後,滑動的時候如果需要加載數據的話,會調用此方法。

現在,我們來看看最終實現的代碼。

<code>  public class ConcertDataSource extends PositionalDataSource<concert> {

private final Retrofit mRetrofit = new Retrofit.Builder()
.baseUrl("http://api.apiopen.top/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();

private final Service mService;

public ConcertDataSource() {
mService = mRetrofit.create(Service.class);
}

@Override
public void loadInitial(@NonNull LoadInitialParams params, @NonNull final LoadInitialCallback<concert> callback) {
// fetchItem(0, params.pageSize, new Callback() {
// @Override
// public void onResult(Bean value) {
// callback.onResult(value.getResult(), 0, 2000);
// }
// });
callback.onResult(fetchItems(0, 20), 0, 2000);
}

@Override
public void loadRange(@NonNull LoadRangeParams params, @NonNull final LoadRangeCallback<concert> callback) {
// fetchItem(params.startPosition, params.loadSize, new Callback() {
// @Override
// public void onResult(Bean value) {
// callback.onResult(value.getResult());
// }

// });
callback.onResult(fetchItems(params.startPosition, params.loadSize));
}

private List<concert> fetchItems(int startPosition, int pageSize) {
List<concert> list = new ArrayList<>();
for (int i = startPosition; i < startPosition + pageSize; i++) {
Concert concert = new Concert();
concert.setAuthor("author = " + i);
concert.setContent("content = " + i);
concert.setTitle("title = " + i);
list.add(concert);
}
return list;
}

private void fetchItem(final int startPosition, int size, final Callback callback) {
mService.getCall(startPosition, size).subscribe(new Observer<bean>() {
@Override
public void onSubscribe(Disposable d) {

}

@Override
public void onNext(Bean value) {
callback.onResult(value);
}

@Override
public void onError(Throwable e) {

}

@Override
public void onComplete() {

}
});
}

private interface Callback {

void onResult(Bean value);
}
}
/<bean>/<concert>/<concert>/<concert>/<concert>/<concert>/<code>

然後,我結合上面的代碼來介紹一下我踩過的兩個坑(其實這兩個坑可以歸併為一個坑,因為都是一個原因導致)。  我先來描述我之前我遇到的問題,在第一次打開界面時,我發現,RecyclerView沒有顯示任何的數據,同時經過debug,發現loadInitial方法被回調到的,但是最終Adapter裡面的itemCount為0。這是什麼原因導致的呢?我們來看看現在的loadInitial方法的實現:

<code>      @Override
public void loadInitial(@NonNull LoadInitialParams params, @NonNull final LoadInitialCallback<concert> callback) {
// fetchItem(0, params.pageSize, new Callback() {
// @Override
// public void onResult(Bean value) {
// callback.onResult(value.getResult(), 0, 2000);
// }
// });
callback.onResult(fetchItems(0, 20), 0, 2000);
}
/<concert>/<code>

我們通過LoadInitialCallback的onResult方法一共傳遞了三個參數,這三個參數分別表示什麼意思?我們先來LoadInitialCallback這個類吧。

<code>    public abstract static class LoadInitialCallback {
public abstract void onResult(@NonNull List data, int position, int totalCount);
public abstract void onResult(@NonNull List data, int position);
}
/<code>

LoadInitialCallback一共有兩個onResult方法。而我這裡出現了RecyclerView不顯示任何數據,是因為我調用得是兩個參數的onResult方法,導致了totalCount為0,所以顯示不出任何數據。  第二個問題就是,數據顯示出來了,但是始終顯示一頁的數據,也就是說loadRange方法不會調用。其實也是totalCount導致的,當時我解決了第一個的問題時,直接給totalCount傳遞的是List的size(假設為N條)。也就是說,PositionalDataSource最終一共只會獲取N條的數據,因為第一頁的大小恰好就等於N,所以不會調用loadRange方法去加載更多的數據。

3. 創建ViewModel

相信比較熟悉JetPack的同學都知道,在Jetpack的架構裡面,推薦每個頁面持有一個ViewModel對象,以保證數據的正確性和避免其他的問題。本文就不介紹ViewModel,不太瞭解的同學可以看一下的官方文檔,這是鏈接: ViewModel Overview Part of Android Jetpack。  之前,我們已經成功的自定義一個DataSource,現在我們來看看怎麼使用ViewModel跟DataSource連接起來。  首先,我們得創建工廠類,用來創建DataSource對象,這在Jetpack架構裡面是基本需要。

<code>  public class ConcertFactory extends DataSource.Factory<integer> {

private MutableLiveData<concertdatasource> mSourceLiveData =
new MutableLiveData<>();

@Override
public DataSource<integer> create() {
ConcertDataSource concertDataSource = new ConcertDataSource();
mSourceLiveData.postValue(concertDataSource);
return concertDataSource;
}
}
/<integer>/<concertdatasource>/<integer>/<code>

至於為什麼需要一個MutableLiveData將DataSource包裝一下,這個我也不是很瞭解,官方文檔就是這麼寫的(可能是讓DataSource監聽Activity或者Fragment的生命週期,以保證只在頁面活躍狀態(onStart或者onResume)獲取數據)。  其次,就是創建對應的ViewModel:

<code>  public class ConcertViewModel extends ViewModel {

private final LiveData<pagedlist>> convertList;
private DataSource<integer> concertDataSource;

public ConcertViewModel() {
ConcertFactory concertFactory = new ConcertFactory();
concertDataSource = concertFactory.create();

convertList = new LivePagedListBuilder<>(concertFactory, 20).build();
}

public void invalidateDataSource() {
concertDataSource.invalidate();
}

public LiveData<pagedlist>> getConvertList() {
return convertList;
}
}
/<pagedlist>/<integer>/<pagedlist>/<code>

4. RecyclerView

首先,我們來看一下Adapter的定義:

<code>  public class RecyclerAdapter extends PagedListAdapter<concert> {

protected RecyclerAdapter() {
super(DIFF_CALLBACK);
}

@NonNull
@Override
public RecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recyclerview, parent, false);
return new RecyclerViewHolder(view);
}

@Override
public void onBindViewHolder(@NonNull RecyclerViewHolder holder, int position) {
Concert concert = getItem(position);
if (concert != null) {
holder.mTitleTextView.setText(concert.getTitle());
holder.mAuthorTextView.setText(concert.getAuthor());
holder.mContentTextView.setText(concert.getContent());
}
}

public static class RecyclerViewHolder extends RecyclerView.ViewHolder {

TextView mTitleTextView;
TextView mAuthorTextView;
TextView mContentTextView;

public RecyclerViewHolder(View itemView) {
super(itemView);
mTitleTextView = itemView.findViewById(R.id.title);

mAuthorTextView = itemView.findViewById(R.id.author);
mContentTextView = itemView.findViewById(R.id.content);
}
}

private static DiffUtil.ItemCallback<concert> DIFF_CALLBACK =
new DiffUtil.ItemCallback<concert>() {
@Override
public boolean areItemsTheSame(Concert oldConcert, Concert newConcert) {
return oldConcert.getTitle().equals(newConcert.getTitle());
}

@Override
public boolean areContentsTheSame(Concert oldConcert,
Concert newConcert) {
return oldConcert.equals(newConcert);
}
};
}
/<concert>/<concert>/<concert>/<code>

在使用PagedListAdapter時,我們需要注意的是,PagedListAdapter內部默認實現DiffUtil來進行數據的差量計算,所以我們在構造方法裡面傳遞一個DiffUtil.ItemCallback。至於DiffUtil是什麼,以及它內部的算法實現,我們這裡就不介紹,有興趣的同學可以關注樓主的RecyclerView源碼分析系列文章,在不久的將來,我會詳細的分析它的內部實現。  然後,我們再來看看Activity裡面是怎麼使用的:

<code>  public class MainActivity extends AppCompatActivity {

private RecyclerView mRecyclerView;
private RecyclerAdapter adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = findViewById(R.id.recyclerView);
adapter = new RecyclerAdapter();
ConcertViewModel viewModel =
ViewModelProviders.of(this).get(ConcertViewModel.class);
viewModel.getConvertList().observe(this, concerts -> adapter.submitList(concerts));

mRecyclerView.setAdapter(adapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
mRecyclerView.addItemDecoration(new SimpleDividerDecoration(this));
}
}
/<code>

整個結構就這樣的,是不是非常簡單?不過,到這裡可能有人可能有疑問,我就實現了一個RecyclerView加載更多的數據,你就讓我定義這麼多的類,是不是有點小題大做呢?  其實不然,Jetpack是Google官方推薦的一種App架構方案,在這種結構下,會避免很多的問題,比如說內存洩漏和空指針異常等等。之所以使用這種方案來架構App,不是我們吃飽撐的沒事幹,而是為了解決傳統架構方案下不能解決的問題。當然可能還有其他的第三方解決方案,但是肯定不比Jetpack權威,畢竟Google爸爸親自設計的。

5. 源碼

為了讓大家更好理解paging的使用,我將我的代碼上傳到github,供大家參考,鏈接:PagingDemo。

6.最後

附上我的Android核心技術學習大綱

怎麼學習谷歌最新的組件--Jetpack?一文介紹paging的基本使用

最近不是面試季嘛,再給大家一個福利:【2017-2019字節跳動面試真題解析&簡歷製作PDF模板】

針對字節跳動的面試題整理的,進行了分類,循序漸進,由基礎到深入,由易到簡。

將內容整理成了五個章節、計算機基礎面試題、數據結構和算法面試題、Java面試題、Android面試題、其他擴展面試題、非技術面試題總共五個章節354頁。

怎麼學習谷歌最新的組件--Jetpack?一文介紹paging的基本使用

每個問題都附上1個標準參考答案,都是反覆摸索消化(真心花了很多時間),覺得寫的比較好的文章作為答案。

來節省大家自己去搜索的時間,把時間用在正確的東西上。。

還整理了全套簡歷製作、春招困惑、HR面試等問題解析參考建議,都是福利分享給大家。

怎麼學習谷歌最新的組件--Jetpack?一文介紹paging的基本使用

獲取方式:關注+轉發,私信我【字節跳動】領取


分享到:


相關文章: