天天看點

Android Architecture Components指南

推薦使用Android architecture components 來建構你的應用。

假設我們正在建構一個顯示使用者資訊的UI,使用者資訊通過REST API 從私人後端擷取。

建構使用者界面 這個使用者界面由UserProfileFragment.java Fragment 和相應的Layout 檔案user_profile_layout.xml組成。 驅動使用者界面,我們的資料模型需要儲存兩個資料元素。

  • 使用者的ID
  • 使用者的對象

我們将建立一個基于ViewModel類的UserProfileViewModel來儲存這些資訊。 ViewModel為特定的UI元件提供資料,比如Fragment或者Activity,并且負責資料處理業務的通信,比如調用其他元件加載資料或者轉發使用者變更。ViewModel與View無關,不受配置變更的影響,比如Activity的重建或者旋轉。

現在我們有3個檔案:

  • user_profile.xml:UI布局檔案。
  • UserProfileViewModel.java:UI資料類。
  • UserProfileFragment.java: 顯示ViewModel中的資料并響應使用者互動的UI控制器。

下面是我們的實作,簡單起見,Layout檔案省略。

public class UserProfileViewModel extends ViewModel {
    private String userId;
    private User user;

    public void init(String userId) {
        this.userId = userId;
    }
    public User getUser() {
        return user;
    }
}
           
public class UserProfileFragment extends Fragment {
    private static final String UID_KEY = "uid";
    private UserProfileViewModel viewModel;

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        String userId = getArguments().getString(UID_KEY);
        viewModel = ViewModelProviders.of(this).get(UserProfileViewModel.class);
        viewModel.init(userId);
    }

    @Override
    public View onCreateView(LayoutInflater inflater,
                @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.user_profile, container, false);
    }
}
           

現在,我們有了三個子產品的代碼,如何把他們關聯起來呢?畢竟,當ViewModel中的user被指派後,我們需要一個方法來通知使用者界面重新整理。這時候就要用到LiveData了。 LiveData是一個可被觀察的資料持有者。它允許APP中的元件觀察LiveData對象的改變,而不會在他們之間建立明确的或者嚴格的依賴路徑。LiveData也會遵循APP元件的生命周期狀态,并做出正确的響應來防止記憶體洩漏,以降低記憶體的使用。

Note:如果你已經在使用RxJava或者Agera等類庫,你可以繼續使用他們來替代LiveData。

現在我們使用LiveData<User>替換UserProfileViewModel中的User,這樣當資料更新的時候Fragment中就可以被通知到。LiveData最棒的在于它具有生命周期感覺能力,當不再需要的時候會自動清除引用。

public class UserProfileViewModel extends ViewModel {
    ...
    private User user;
    private LiveData<User> user;
    public LiveData<User> getUser() {
        return user;
    }
}
           

修改UserProfileFragment來觀察資料和更新UI。

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    viewModel.getUser().observe(this, user -> {
      // update UI
    });
}
           

每次user資料被更新的時候,onChange回調方法就會被調用來重新整理UI。

如果你熟悉其他使用可觀察回調的庫,你可能已經意識到我們不需要重載onStop()函數來停止觀察資料。對于LiveData來說這并不需要,因為它能自動感覺生命周期,也就是說除非Fragment處于活動狀态(收到onStart(),但是還沒有收到onStop()),否則他不會調用Callback。LiveData會在收到onDestory()時自動移除觀察者。 我們對配置的變化(如螢幕旋轉)并沒有做特殊處理。當配置改變時,ViewModel會自動恢複。是以一旦新的Fragment激活,它就會收到同一個ViewModel的執行個體,并且會立即回調最新資料。這就是ViewModel不能直接引用Views的原因;他們存活比View的生命周期更長。請參閱ViewModel的生命周期。

Fetching Data 擷取資料

現在我們已經把ViewModel連接配接到Fragment,但是ViewModel是如何擷取資料的呢?在這個例子裡,我們假設我們後端提供一個REST API。我們使用Retrofit庫通路後端,當然你可以自由的使用其他庫來實作。

這是基于Retrofit的 WebService,用來與後端通信。

public interface Webservice {
    /**
     * @GET declares an HTTP GET request
     * @Path("user") annotation on the userId parameter marks it as a
     * replacement for the {user} placeholder in the @GET path
     */
    @GET("/users/{user}")
    Call<User> getUser(@Path("user") String userId);
}
           

直接在ViewModel中調用WebService來擷取資料并配置設定資料給user對象,是一種幼稚的ViewModel實作。雖然他是可行的,但是随着業務的增長APP會變得難于管理。在ViewModel上職責太多,這也違背了之前提到的關注點分離原則。此外,ViewModel的作用域與Activity或者Fragment的生命周期關聯,是以當生命周期結束的時候丢失全部資料是不好的使用者體驗,相反,我們的ViewModel将這部分工作委托給一個新的Repository子產品。 Repository子產品負責處理資料操作。它為應用程式的REST部分提供了幹淨的api。他們知道從哪裡擷取資料以及在資料更新時調用什麼API。你可以将他們看作是不同資料源(persistent model,web service, cache,etc...)的中介。

UserRepository 類使用WebService 來擷取user資料

public class UserRepository {
    private Webservice webservice;
    // ...
    public LiveData<User> getUser(int userId) {
        // This is not an optimal implementation, we'll fix it below
        final MutableLiveData<User> data = new MutableLiveData<>();
        webservice.getUser(userId).enqueue(new Callback<User>() {
            @Override
            public void onResponse(Call<User> call, Response<User> response) {
                // error case is left out for brevity
                data.setValue(response.body());
            }
        });
        return data;
    }
}
           

雖然Repository子產品看上去不是必須的,使用他有一個重要的目的:他從應用的REST中抽象了資料源。現在我們的ViewModel并不知道資料是從Webservice中擷取的,意味着在需要的時候我們可以更換資料源。 Note:為了簡單起見,我們忽略了網絡錯誤的情況。有關網絡加載錯誤和加載狀态的顯示實作,請查閱附錄:顯示網絡狀态。

管理元件依賴

上面的UserRepository類需要一個Webservice的執行個體來完成他的工作。它可以簡單建立,但是他也需要Webservice類的依賴關系來構造它。這會使代碼變得複雜和備援(比如,每個需要Webservice執行個體的類都需要知道如何利用它的依賴關系來構造它)。此外,UserRepository可能不是唯一需要Webservice的類,如果每個地方都建立一個新的Webservice,這将會是非常沉重的資源。

有兩種模式你可以用來解決這個問題: 依賴注入:依賴注入允許類在不構造的情況下定義他們的依賴關系。在運作時,另外一個類負責提供這些依賴關系。我們推薦google 的Dagger2類庫來在anroid應用中實作依賴注入。Dagger2通過周遊依賴關系樹來自動構造對象,并為依賴關系提供編譯時間的保證。 服務定位器: Service Locator提供一個系統資料庫,類可以擷取他們的依賴關系而不是建構它們。比依賴注入(DI)實作起來更容易,是以,如果你不熟悉DI,使用Service Locator。

這些模式允許你擴充你的代碼,因為他們提供了清晰的模式來管理依賴關系,無需增加代碼的備援和複雜性。他們兩者之間也可以為測試切換實作方式;這是使用他們的主要好處之一。

在這個例子中,我們将使用Dagger2來關系依賴關系。

連接配接ViewModel和Repository

現在用repository來修改我們的UserProfileViewModel。

public class UserProfileViewModel extends ViewModel {
    private LiveData<User> user;
    private UserRepository userRepo;

    @Inject // UserRepository parameter is provided by Dagger 2
    public UserProfileViewModel(UserRepository userRepo) {
        this.userRepo = userRepo;
    }

    public void init(String userId) {
        if (this.user != null) {
            // ViewModel is created per Fragment so
            // we know the userId won't change
            return;
        }
        user = userRepo.getUser(userId);
    }

    public LiveData<User> getUser() {
        return this.user;
    }
}
           

資料緩存

上述Repository的是實作,對Webservice的調用是非常好的抽象,但是因為他隻依賴于一個資料源,是以他不是非常實用。 上述UserRepository的實作的問題在于,在擷取資料之後,沒有在任何地方保留。如果使用者離開了UserProfileFragment并又傳回來,應用需要重新擷取資料。這是不好的體驗,有兩個原因:它浪費了寶貴的帶寬并強制讓使用者等待新的查詢完成。為了解決這,我們添加一個新的資料源到UserRepository中,用來緩存User對象到記憶體中。

@Singleton  // informs Dagger that this class should be constructed once
public class UserRepository {
    private Webservice webservice;
    // simple in memory cache, details omitted for brevity
    private UserCache userCache;
    public LiveData<User> getUser(String userId) {
        LiveData<User> cached = userCache.get(userId);
        if (cached != null) {
            return cached;
        }

        final MutableLiveData<User> data = new MutableLiveData<>();
        userCache.put(userId, data);
        // this is still suboptimal but better than before.
        // a complete implementation must also handle the error cases.
        webservice.getUser(userId).enqueue(new Callback<User>() {
            @Override
            public void onResponse(Call<User> call, Response<User> response) {
                data.setValue(response.body());
            }
        });
        return data;
    }
}
           

持久化資料 在我們目前實作中,如果使用者旋轉螢幕或者離開并傳回目前app,目前的UI可以立即可得因為Repository會從記憶體緩存中恢複資料。但是,如果使用者退出APP并在數小時後又回來,或者在Android系統殺死該程序後回來,會發生什麼?

在目前的實作中,我們需要從網絡上重新擷取資料。這不僅是一個糟糕的使用者體驗,而且會浪費移動資料流量來重新擷取相同的資料。你可以通過緩存Web請求來簡單的解決這個問題,但是這會帶來新的問題。如果相同的使用者資料顯示來自另外一種類型的請求(比如,擷取朋友清單),會發生什麼情況?那麼你的APP可能會顯示不一緻的資料,這是一個讓使用者混亂的體驗。為此,相同的使用者資料可能會顯示不一緻,是因為使用者對朋友清單的請求可以在不同的時間執行。APP需要合并他們避免顯示不一緻的資料。

用持久化模式來處理這個問題是一個正确的方式。這就到了使用Room持久化庫的時候了。 Room是一個對象映射庫,使用非常少量的代碼實作本地資料的持久化。在編譯時,它會根據schema來檢查驗證每個查詢,用編譯時的SQL查詢錯誤中斷來替換運作時失敗。Room抽象出一些使用原始的SQL表和查詢的底層實作細節。他也允許對資料庫資料(包括集合和連接配接查詢)的變更進行觀察,并通過LiveData對象來暴露這些變更。另外,他明确的定義了線程限制,來解決常見的問題,比如在主線程上通路存儲。 Note:如果你的APP已經使用了另外的持久化解決方案,比如Sqlite對象關系映射(ORM),你不必用Room替換目前的方案。但是,如果你重新寫一個APP或者重構目前的APP,我們推薦你使用Room來持久化你的App資料。這樣,你可以體驗到Room庫的抽象化和查詢驗證帶來的好處。

使用Room,我們需要定義本地的schema。首先,用@Entity注釋User類,讓作為資料庫的一個表。

@Entity
class User {
  @PrimaryKey
  private int id;
  private String name;
  private String lastName;
  // getters and setters for fields
}
           

然後,用RoomDatabase為你的APP建立一個資料庫:

@Database(entities = {User.class}, version = 1)
public abstract class MyDatabase extends RoomDatabase {
}
           

注意,MyDatabase是abstract的。Room自動提供它的實作。詳細參閱Room文檔。

現在我們需要一個插入user資料的到資料庫的方法。為此,我們建立了一個資料通路對象(DAO)。

@Dao
public interface UserDao {
    @Insert(onConflict = REPLACE)
    void save(User user);
    @Query("SELECT * FROM user WHERE id = :userId")
    LiveData<User> load(String userId);
}
           

然後,從我們的database類引用DAO。

@Database(entities = {User.class}, version = 1)
public abstract class MyDatabase extends RoomDatabase {
    public abstract UserDao userDao();
}
           

注意,load方法傳回的是LiveData<User>。當資料發生改變的時,Room知道什麼時候資料庫被修改并且會自動的通知所有活躍狀态的觀察者。因為它使用了LiveData,會非常高效,而且隻有目前至少有一個活躍的觀察者時才會更新資料。 Note:Room的無效檢查是基于表格的修改,這意味着它可能發出錯誤的通知。

現在我們可以修改我們的UserRository來合并Room資料源。

@Singleton
public class UserRepository {
    private final Webservice webservice;
    private final UserDao userDao;
    private final Executor executor;

    @Inject
    public UserRepository(Webservice webservice, UserDao userDao, Executor executor) {
        this.webservice = webservice;
        this.userDao = userDao;
        this.executor = executor;
    }

    public LiveData<User> getUser(String userId) {
        refreshUser(userId);
        // return a LiveData directly from the database.
        return userDao.load(userId);
    }

    private void refreshUser(final String userId) {
        executor.execute(() -> {
            // running in a background thread
            // check if user was fetched recently
            boolean userExists = userDao.hasUser(FRESH_TIMEOUT);
            if (!userExists) {
                // refresh the data
                Response response = webservice.getUser(userId).execute();
                // TODO check for error etc.
                // Update the database.The LiveData will automatically refresh so
                // we don't need to do anything else here besides updating the database
                userDao.save(response.body());
            }
        });
    }
}
           

留意,雖然我們在UserRepository修改了資料的來源,但是我們不需要修改我們的UserProfileViewModel或UserProfileFragment.這就是抽象的靈活性。這樣也更有益于測試,因為你可以構造一個UserRepository來測試你的UserProfileModel。

至此我們的代碼已經完成。如果使用者在幾天後傳回到相同的界面,他們會立即看到使用者資訊,因為我們持久化了資料。同時,如果資料已經過期,我們的Repository會在背景更新資料。當然,根據你的情況,如果資料太舊你可能不希望顯示持久化的資料了。

在一些使用的情況中,比如下拉重新整理,如果目前正在進行網絡操作,給使用者互動的UI是非常重要的。将UI操作和實際資料分開是一種很好的實踐,因為資料可能因為各種原因被更新(例如,我們擷取朋友清單,同一使用者可能再次被擷取,觸發LiveData<User>的更新)。從使用者互動的角度來看,有一個請求正在進行的是情況是另外一個資料點,類似于其他資料塊(比如使用者對象)。

這種情況有2種常見的解決方案: 修改getUser來傳回一個包括網絡操作狀态的LiveData。在   Addendum: exposing network status  章節提供了一個實作的例子。 在repository類中提供另外一個公共的功能,可以傳回User的重新整理狀态。這個選項更好一些,如果你隻想在UI中顯示網絡狀态來明确響應使用者操作(比如下拉重新整理)。

單一資料來源 經常不同的REST API終端傳回相同的資料。例如,假如我們的後端有另外一個傳回朋友清單的入口,同一個user對象可能來自不同的兩個API入口,也可能來自不同的粒度。如果UserRepository原樣傳回來自Webservice請求的響應,那麼我們的UI可能會顯示不一樣的資料,因為可能這些資料在服務端不同的請求之間發生了更改。這就是為什麼在UserRepository實作中,web服務回調隻儲存資料到資料庫中。然後,資料庫的改變觸發活動的LiveData對象上的回調。

在這個模式中,資料庫充當單一資料來源,APP的其他部分通過repository通路它。無論你是使用磁盤緩存,我們都建議你的repository将資料源指定為單一來源,作為你的應用的REST。

測試 我們已經提到分離的好處之一就是可測試性。讓我們看看我們如何測試每個代碼子產品。 使用者界面和互動:這将是你唯一需要Android UI Instrumentation的測試。測試UI 代碼最好的方式是建立一個Espresso測試。你可以建立Fragment并為其提供一個模拟的ViewModel。由于這個Fragment僅僅同這個ViewModel互動,模拟它足于完全測試這個UI。 ViewModel:可以使用JUnit來測試ViewModel。你隻需要模拟UserRepository來測試它。 UserRepository: 你同樣可以用JUnit來測試UserRepository。你需要模拟Webservice和DAO。你可以測試它是否做出正确的web服務調用、結果儲存到資料庫和不做任何不必要的請求假如資料已經緩存且更新。既然Webservice和UserDao都是接口,你可以模拟他們或者建立副本來實作更複雜的測試用例。 UserDao: 測試DAO類的推薦方法是使用instrumentation 測試。由于這些instrumentation測試不要求任何UI,他們會運作的很快。對于每個測試,你可以建立一個在記憶體中的資料庫來確定測試沒有任何邊界效應影響(如更改磁盤上的資料庫檔案)。 Room還允許制定資料庫實作,以便你可以通過提供SupportSQLiteOpenHelper的JUnit實作來測試它。通常不推薦這種方式,因為裝置的SQlite的版本可能與主機上的SQLite版本不一緻。 Webservice:讓測試獨立于外部是非常重要的,是以即使是你的Webservice測試,也應該避免對你的後端進行網絡調用。有很多的庫可以幫你做這些事。比如,MockWebServer是一個非常棒的庫,可以幫助你的測試建立一個本地模拟伺服器。 Testing Artifacts Architecture Components 提供一個maven 生成來控制它的背景線程。在android.arc.core:core-testing artifcat中,有2個JUnit規則: InstantTaskExecutorRule:此規則可用于強制Architecture Compononets在調用的線程上立即執行任何背景操作。 CountingTaskExecutorRule:此規則可用于instrumentation test,來等待Architecture Components的背景 操作或者作為待機資源連接配接到Espresso。

最終架構 下圖顯示了我們推薦的架構的全部子產品以及他們之間的互動。

Android Architecture Components指南

使用原則

程式設計是一個創造性領域,建構Andorid 應用也不例外。解決問題的方式有很多,在多個activity或者fragment之間通信資料,恢複遠端資料并且持久化到本地用于離線模式,或者許多其他常見應用程式碰到的情況。 盡管下面的建議不是強制性的,但是我們的經驗是,遵循這些建議将使你的代碼基礎更加健壯、可測試以及可維護。 你在終端的manifest中定義的元件(activitys,services,broadcast receivers,等)不是資料來源。相反,他們隻是配合資料子集的終端響應。因為每個應用的元件存活非常短,取決于使用者與裝置的互動和目前運作時的整體情況,你不希望任何這些入口點作為資料源。 在應用中建立時,明确界定各個子產品的職責。比如,不要将從網絡加載資料的代碼分散在多個類或者包中。同樣,不要把不相關額職責放到同一個類中。比如資料緩存和資料綁定。 每個子產品盡可能少的暴露。不要視圖建立“僅有那個”的便捷路徑去暴露子產品的内部實作細節。你可能短期内節省一些時間,但是随着代碼的發展,你就需要償還更多的技術債務。 當你在多個子產品中定義互動時,請考慮如何讓每個子產品獨立可測試。例如,有一個定義良好的從網絡擷取資料的API,将資料持久化在本地會使得子產品測試更容易。反過來,如果你将兩個子產品的邏輯混淆在一起,或者将你的網絡部分代碼遍布于整個代碼庫,那麼測試就更加困難,甚至不可能測試。 你的APP的核心是什麼因素讓它能夠脫穎而出。不要浪費時間去重複造輪子,或者一次次地寫雷同的樣闆代碼。相反,應該集中精力在讓你的APP的特色上,讓Android Architecture Components和其他推薦庫來處理重複樣闆的問題。 堅持盡可能多的掌握新的資料,這樣當你的裝置離線時你的應用也是可用的。雖然你可以享用穩定高速的網絡連接配接,但是你的使用者可能沒有。 你的repository應該指定一個資料源作為單一資料源。無論什麼時候APP需要通路這些資料的,都應該從單一資料源發起。更多請參閱   Single source of truth 。

附錄:顯示網絡狀态 在前面的recommend app architecture章節中,為了使例子看起來更簡潔,我們故意省略了網絡請求錯誤和加載狀态。在這個章節,我們示範如何使用一個封裝了網絡資料和它的狀态的Resource類來顯示網絡狀态。

實作如下:

//a generic class that describes a data with a status
public class Resource<T> {
    @NonNull public final Status status;
    @Nullable public final T data;
    @Nullable public final String message;
    private Resource(@NonNull Status status, @Nullable T data, @Nullable String message) {
        this.status = status;
        this.data = data;
        this.message = message;
    }

    public static <T> Resource<T> success(@NonNull T data) {
        return new Resource<>(SUCCESS, data, null);
    }

    public static <T> Resource<T> error(String msg, @Nullable T data) {
        return new Resource<>(ERROR, data, msg);
    }

    public static <T> Resource<T> loading(@Nullable T data) {
        return new Resource<>(LOADING, data, null);
    }
}
           

由于從網絡上加載資料到磁盤上顯示是一個常見的例子,是以我們要建立一個可以在多個地方複用的幫助類NetworkBoundResource。NetworkBoundResoutce的決策樹如下:

Android Architecture Components指南

從資源觀察的資料庫開始,當實體第一次從資料庫加載時,NetworkBoundResource會檢查結果是否足夠好用于分發,還是從網絡重新擷取,注意,如果你希望在擷取網絡資料的時候顯示緩存資料,這兩種情況可能同時發生。 如果網絡調用成功完成,将響應資料儲存到資料庫并且重新開始資料流。如果網絡請求失敗,直接發送失敗。 Note:把新資料儲存到磁盤後,我們從資料庫重新初始化資料流。但是通常我們不需要這樣做,因為資料庫會分發資料的變更。另一方面,依賴資料庫來分發排程變更會有不好的依賴副作用,因為在資料沒有改變的時候資料庫可以避免排程分發,這樣流程會被中斷。我們也不希望分發網絡擷取的結果,因為這違背了我們單一資料源原則(也許在資料庫中有觸發器會改變儲存的數值)。如果沒有新的資料,我們也不想分發SUCCESS消息,它會向用戶端發送錯誤的資訊。

下面是NetworkBoundResource類為其子類提供的public API。

// ResultType: Type for the Resource data
// RequestType: Type for the API response
public abstract class NetworkBoundResource<ResultType, RequestType> {
    // Called to save the result of the API response into the database
    @WorkerThread
    protected abstract void saveCallResult(@NonNull RequestType item);

    // Called with the data in the database to decide whether it should be
    // fetched from the network.
    @MainThread
    protected abstract boolean shouldFetch(@Nullable ResultType data);

    // Called to get the cached data from the database
    @NonNull @MainThread
    protected abstract LiveData<ResultType> loadFromDb();

    // Called to create the API call.
    @NonNull @MainThread
    protected abstract LiveData<ApiResponse<RequestType>> createCall();

    // Called when the fetch fails. The child class may want to reset components
    // like rate limiter.
    @MainThread
    protected void onFetchFailed() {
    }

    // returns a LiveData that represents the resource, implemented
    // in the base class.
    public final LiveData<Resource<ResultType>> getAsLiveData();
}
           

請注意上面的類定義了2種類型的參數(ResultType,RequestType)因為從API傳回的資料類型可能與本地使用的資料類型不比對。

另外也需要注意上面的代碼使用ApiResponse作為網絡請求。ApiResponse是Retrofit2.Call的一個簡單的封裝類,調用他将response轉換成一個LiveData。

以下是NoteworkBoundResource的其餘實作:

public abstract class NetworkBoundResource<ResultType, RequestType> {
    private final MediatorLiveData<Resource<ResultType>> result = new MediatorLiveData<>();

    @MainThread
    NetworkBoundResource() {
        result.setValue(Resource.loading(null));
        LiveData<ResultType> dbSource = loadFromDb();
        result.addSource(dbSource, data -> {
            result.removeSource(dbSource);
            if (shouldFetch(data)) {
                fetchFromNetwork(dbSource);
            } else {
                result.addSource(dbSource,
                        newData -> result.setValue(Resource.success(newData)));
            }
        });
    }

    private void fetchFromNetwork(final LiveData<ResultType> dbSource) {
        LiveData<ApiResponse<RequestType>> apiResponse = createCall();
        // we re-attach dbSource as a new source,
        // it will dispatch its latest value quickly
        result.addSource(dbSource,
                newData -> result.setValue(Resource.loading(newData)));
        result.addSource(apiResponse, response -> {
            result.removeSource(apiResponse);
            result.removeSource(dbSource);
            //noinspection ConstantConditions
            if (response.isSuccessful()) {
                saveResultAndReInit(response);
            } else {
                onFetchFailed();
                result.addSource(dbSource,
                        newData -> result.setValue(
                                Resource.error(response.errorMessage, newData)));
            }
        });
    }

    @MainThread
    private void saveResultAndReInit(ApiResponse<RequestType> response) {
        new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... voids) {
                saveCallResult(response.body);
                return null;
            }

            @Override
            protected void onPostExecute(Void aVoid) {
                // we specially request a new live data,
                // otherwise we will get immediately last cached value,
                // which may not be updated with latest results received from network.
                result.addSource(loadFromDb(),
                        newData -> result.setValue(Resource.success(newData)));
            }
        }.execute();
    }

    public final LiveData<Resource<ResultType>> getAsLiveData() {
        return result;
    }
}
           

現在我們可以在repository的實作中使用NetworkBoundResource來寫入資料到我們的磁盤和網絡狀态綁定。如下:

class UserRepository {
    Webservice webservice;
    UserDao userDao;

    public LiveData<Resource<User>> loadUser(final String userId) {
        return new NetworkBoundResource<User,User>() {
            @Override
            protected void saveCallResult(@NonNull User item) {
                userDao.insert(item);
            }

            @Override
            protected boolean shouldFetch(@Nullable User data) {
                return rateLimiter.canFetch(userId) && (data == null || !isFresh(data));
            }

            @NonNull @Override
            protected LiveData<User> loadFromDb() {
                return userDao.load(userId);
            }

            @NonNull @Override
            protected LiveData<ApiResponse<User>> createCall() {
                return webservice.getUser(userId);
            }
        }.getAsLiveData();
    }
}
           

譯自 Android Developer。 End.