關於Paging + Room,RecyclerView刷新時的空指針異常

最近開了一個新項目,使用的是Google 2018 IO大會 推薦的新的app架構,以下: android

官方地址

這裏主要講Paging + Room遇到的問題:
基礎的參考官方樣例: github.com/googlesampl…
git

PagedList的建立:

徹底照搬官方的樣例,傳入自定義的BoundaryCallBack:

PagedList的DataSource.Factory: github

實現交由子類:
Dao:

OK,到這裏,基本上都是照搬的官方的樣例了,接下來就看運行結果了:
數據能正確獲取,並正確刷UI。可是,當數據超過 30條(後面交代爲何是30)時,具體效果以下:
PagedList的長度爲50,可是隻有30item是有數據的,其餘都用null來佔位了,
這會形成一個問題,當我須要刪除一條數據時,刪除後刷新UI,adapter的getItem()方法是會返回null,而我之因此用Paging + Room的形式,就是爲了刪除,由於PagedList不支持刪除,233333。聲淚俱下啊。。。。。。。。

解題思路:

爲何PagedList裏的其餘數據是null,能不能把null去除

嘗試一、發現PagedList的Config中有enablePlaceholders(支持佔位)屬性,默認爲true,支持null,能不能改成false,這樣PagedList中就不會有null 數據庫

解決方案:
結果:
設置PagedList的配置enablePlaceholders爲false後,PagedList的Observer接收到的數據並不完整,等因而PagedList將null數據過濾了。

只能往Room的源碼挖了,爲何會返回null:
一、將Room與PagedList聯繫起來的是新建PagedList傳入的Room產生的DataSource.Factory bash

二、尋找具體的DataSource
從FavorVideoDao的具體實現類中,能夠看出,getAll()方法返回的是LimitOffsetDataSource,而其中只有恰巧只有一個返回集合爲List< Object>的方法,參數爲Cursor,因此應該能判定是從數據庫中查出數據後處理返回給PagedList的方法。

三、Debug後發現,Cursor的長度與PagedList的Observer接收到的數據長度一致,因此由表入裏,看看這個方法的上游是哪,爲何cursor中會有null的數據 架構

LimitOffsetDataSource.loadRange() app

從這個方法,咱們能發現Cursor的由來, mDb.query(sqLiteQuery);,Sqlite語句sqLiteQuery的由來就有意思了,
limit ? offset ?,查詢多少個,偏移多少(從第幾個開始),到這裏其實就有點眉目了
而這兩個值其實來自與方法的參數:
四、查找limit和offset的由來 LimitOffsetDataSource.loadInitial():
PositionalDataSource.computeInitialLoadPosition(): offset:

public static int computeInitialLoadPosition(@NonNull LoadInitialParams params,
            int totalCount) {
        int position = params.requestedStartPosition;
        int initialLoadSize = params.requestedLoadSize;
        int pageSize = params.pageSize;

        int roundedPageStart = Math.round(position / pageSize) * pageSize;  // 這裏確定是大於0的

        // maximum start pos is that which will encompass end of list
        int maximumLoadPage = ((totalCount - initialLoadSize + pageSize - 1) / pageSize) * pageSize; // 因此必須保證maximumLoadPage小於等於0,因此必須保證 initialLoadSize必須足夠大
        roundedPageStart = Math.min(maximumLoadPage, roundedPageStart);

        // minimum start position is 0
        roundedPageStart = Math.max(0, roundedPageStart);  // 因此,roundedPageStart必須小於等於0

        return roundedPageStart;
    }
複製代碼

若是想要將全部數據都加載出來,offset必須的保證爲0,具體看上面代碼註釋,因此關鍵在於params.requestedLoadSizeide

PositionalDataSource.computeInitialLoadSize(): limit:性能

public static int computeInitialLoadSize(@NonNull LoadInitialParams params,
            int initialLoadPosition, int totalCount) {
        return Math.min(totalCount - initialLoadPosition, params.requestedLoadSize); // 總的個數減去加載的起始位置  params.requestedLoadSize 兩數去最小值,爲加載的總個數
    }
複製代碼

因此當params.requestedLoadSize足夠大時,數據庫中的全部數據都會被取出
Room加上limit邏輯,也是爲了效率更高,可是由於有刪除的業務需求,致使異常,因此仍是每次所有都取出,RecyclerView刷新時,頁只會刷新可見的Item,因此性能上仍是OK的 ui

五、查找params.requestedLoadSize的由來
PositionalDataSource.dispatchLoadInitial(),來源於該方法的參數,仍是得往上尋找:

TiledPagedList的構造方法:
來源於Config的initialLoadSizeHint屬性,因此最後回到了PagedList的Config的initialLoadSizeHint屬性。 此處正好回答前面爲何只保存30個數據了:
當未設置Config的initialLoadSizeHint屬性時,默認爲PageSize的3倍。

解決方案

初始化PagedList時,將initialLoadSizeHint屬性設置的足夠大:

相關文章
相關標籤/搜索