App开发架构指南(谷歌官方文档译文)

这篇文章面向的是已经掌握app开发基本知识,想知道如何开发健壮app的读者。html

注:本指南假设读者对 Android Framework 已经很熟悉。若是你仍是app开发的新手,请查看 Getting Started 系列教程,该教程涵盖了本指南的预备知识。java

app开发者面临的常见问题

跟传统的桌面应用开发不一样,Android app的架构要复杂得多。一个典型的Android app是由多个app组件构成的,包括activity,Fragment,service,content provider以及broadcast receiver。而传统的桌面应用每每在一个庞大的单一的进程中就完成了。react

大多数的app组件都声明在app manifest中,Android OS用它来决定如何将你的app与设备整合造成统一的用户体验。虽然就如刚说的,桌面app只运行一个进程,可是一个优秀的Android app却须要更加灵活,由于用户操做在不一样app之间,不断的切换流程和任务。android

好比,当你要在本身最喜欢的社交网络app中分享一张照片的时候,你能够想象一下会发生什么。app触发一个camera intent,而后Android OS启动一个camera app来处理这一动做。此时用户已经离开了社交网络的app,可是用户的操做体验倒是无缝对接的。而 camera app反过来也可能触发另外一个intent,好比启动一个文件选择器,这可能会再次打开另外一个app。最后用户回到社交网络app并分享照片。在这期间的任意时刻用户均可被电话打断,打完电话以后继续回来分享照片。git

在Android中,这种app并行操做的行为是很常见的,所以你的app必须正确处理这些流程。还要记住移动设备的资源是有限的,所以任什么时候候操做系统都有可能杀死某些app,为新运行的app腾出空间。github

总的来讲就是,你的app组件多是单独启动而且是无序的,并且在任什么时候候都有可能被系统或者用户销毁。由于app组件生命的短暂性以及生命周期的不可控制性,任何数据都不该该把存放在app组件中,同时app组件之间也不该该相互依赖。web

通用的架构准则

若是app组件不能存放数据和状态,那么app仍是可架构的吗?数据库

最重要的一个原则就是尽可能在app中作到separation of concerns(关注点分离)。常见的错误就是把全部代码都写在Activity或者Fragment中。任何跟UI和系统交互无关的事情都不该该放在这些类当中。尽量让它们保持简单轻量能够避免不少生命周期方面的问题。别忘了能并不拥有这些类,它们只是链接app和操做系统的桥梁。根据用户的操做和其它因素,好比低内存,Android OS可能在任什么时候候销毁它们。为了提供可靠的用户体验,最好把对它们的依赖最小化。编程

第二个很重要的准则是用。之因此要持久化是基于两个缘由:若是OS销毁app释放资源,用户数据不会丢失;当网络不好或者断网的时候app能够继续工做。Model是负责app数据处理的组件。它们不依赖于View或者app 组件(Activity,Fragment等),所以它们不会受那些组件的生命周期的影响。保持UI代码的简单,于业务逻辑分离可让它更易管理。 后端

在这一小节中,咱们将经过一个用例演示如何使用Architecture Component构建一个app。

注:没有一种适合全部场景的app编写方式。也就是说,这里推荐的架构适合做为大多数用户案例的开端。可是若是你已经有了一种好的架构,没有必要再去修改。

假设咱们在建立一个显示用户简介的UI。用户信息取自咱们本身的私有的后端REST API。

 

建立用户界面

UI由UserProfileFragment.java以及相应的布局文件user_profile_layout.xml组成。

要驱动UI,咱们的data model须要持有两个数据元素。

User ID: 用户的身份识别。最好使用fragment argument来传递这个数据。若是OS杀死了你的进程,这个数据能够被保存下来,因此app再次启动的时候id还是可用的。

User object: 一个持有用户信息数据的POJO对象。

咱们将建立一个继承ViewModel类的UserProfileViewModel来保存这一信息。

一个ViewModel为特定的UI组件提供数据,好比fragment 或者 activity,并负责和数据处理的业务逻辑部分通讯,好比调用其它组件加载数据或者转发用户的修改。ViewModel并不知道View的存在,也不会被configuration change影响。

如今咱们有了三个文件。

user_profile.xml: 定义页面的UI

UserProfileViewModel.java: 为UI准备数据的类

UserProfileFragment.java: 显示ViewModel中的数据与响应用户交互的控制器 

下面咱们开始实现(为简单起见,省略了布局文件):

  1. public class UserProfileViewModel extends ViewModel {
  2.     private String userId;
  3.     private User user;
  4.  
  5.     public void init(String userId) {
  6.         this.userId = userId;
  7.     }
  8.     public User getUser() {
  9.         return user;
  10.     }
  11. }
  1. public class UserProfileFragment extends LifecycleFragment {
  2.     private static final String UID_KEY = "uid";
  3.     private UserProfileViewModel viewModel;
  4.  
  5.     @Override
  6.     public void onActivityCreated(@Nullable Bundle savedInstanceState) {
  7.         super.onActivityCreated(savedInstanceState);
  8.         String userId = getArguments().getString(UID_KEY);
  9.         viewModel = ViewModelProviders.of(this).get(UserProfileViewModel.class);
  10.         viewModel.init(userId);
  11.     }
  12.  
  13.     @Override
  14.     public View onCreateView(LayoutInflater inflater,
  15.                 @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
  16.         return inflater.inflate(R.layout.user_profile, container, false);
  17.     }
  18. }

注:上面的例子中继承的是LifecycleFragment而不是Fragment类。等Architecture Component中的lifecycles API稳定以后,Android Support Library中的Fragment类也将实现LifecycleOwner

如今咱们有了这些代码模块,如何链接它们呢?毕竟当ViewModel的user成员设置以后,咱们还须要把它显示到界面上。这就要用到LiveData了。

LiveData是一个可观察的数据持有者。 无需明确在它与app组件之间建立依赖就能够观察LiveData对象的变化。LiveData还考虑了app组件(activities, fragments, services)的生命周期状态,作了防止对象泄漏的事情。

注:若是你已经在使用RxJava或者Agera这样的库,你能够继续使用它们,而不使用LiveData。可是使用它们的时候要确保正确的处理生命周期的问题,与之相关的LifecycleOwner stopped的时候数据流要中止,LifecycleOwner  destroyed的时候数据流也要销毁。你也可使用android.arch.lifecycle:reactivestreams让LiveData和其它的响应式数据流库一块儿使用(好比, RxJava2)。

如今咱们把UserProfileViewModel中的User成员替换成LiveData,这样当数据发生变化的时候fragment就会接到通知。LiveData的妙处在于它是有生命周期意识的,当它再也不被须要的时候会自动清理引用。

  1. public class UserProfileViewModel extends ViewModel {
  2.     ...
  3.     private User user;
  4.     private LiveData<User> user;
  5.     public LiveData<User> getUser() {
  6.         return user;
  7.     }
  8. }

如今咱们修改UserProfileFragment,让它观察数据并更新UI。

  1. @Override
  2. public void onActivityCreated(@Nullable Bundle savedInstanceState) {
  3.     super.onActivityCreated(savedInstanceState);
  4.     viewModel.getUser().observe(this, user -> {
  5.       // update UI
  6.     });
  7. }

每当User数据更新的时候 onChanged 回调将被触发,而后刷新UI。

若是你熟悉其它library的observable callback的用法,你会意识到咱们不须要重写fragment的onStop()方法中止对数据的观察。由于LiveData是有生命周期意识的,也就是说除非fragment处于活动状态,不然callback不会触发。LiveData还能够在fragmentonDestroy()的时候自动移除observer。

对咱们也没有作任何特殊的操做来处理 configuration changes(好比旋转屏幕)。ViewModel能够在configuration change的时候自动保存下来,一旦新的fragment进入生命周期,它将收到相同的ViewModel实例,而且携带当前数据的callback将当即被调用。这就是为何ViewModel不该该直接引用任何View,它们游离在View的生命周期以外。参见ViewModel的生命周期

获取数据

如今咱们把ViewModel和fragment联系了起来,可是ViewModel该如何获取数据呢?在咱们的例子中,假设后端提供一个REST API,咱们使用Retrofit从后端提取数据。你也可使用任何其它的library来达到相同的目的。

下面是和后端交互的retrofit Webservice:

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

ViewModel的一个简单的实现方式是直接调用Webservice获取数据,而后把它赋值给User对象。虽然这样可行,可是随着app的增大会变得难以维护。ViewModel的职责过多也违背了前面提到的关注点分离(separation of concerns)原则。另外,ViewModel的有效时间是和ActivityFragment的生命周期绑定的,所以当它的生命周期结束便丢失全部数据是一种很差的用户体验。相反,咱们的ViewModel将把这个工做代理给Repository模块。

Repository模块负责处理数据方面的操做。它们为app提供一个简洁的API。它们知道从哪里获得数据以及数据更新的时候调用什么API。你能够把它们当作是不一样数据源(persistent model, web service, cache, 等等)之间的媒介。

下面的UserRepository类使用了WebService来获取用户数据。

  1. public class UserRepository {
  2.     private Webservice webservice;
  3.     // ...
  4.     public LiveData<User> getUser(int userId) {
  5.         // This is not an optimal implementation, we'll fix it below
  6.         final MutableLiveData<User> data = new MutableLiveData<>();
  7.         webservice.getUser(userId).enqueue(new Callback<User>() {
  8.             @Override
  9.             public void onResponse(Call<User> call, Response<User> response) {
  10.                 // error case is left out for brevity
  11.                 data.setValue(response.body());
  12.             }
  13.         });
  14.         return data;
  15.     }
  16. }

虽然repository模块看起来没什么必要,但它其实演扮演着重要的角色;它把数据源从app中抽象出来。如今咱们的ViewModel并不知道数据是由Webservice提供的,意味着有必要的话能够替换成其它的实现方式。

注:为简单起见咱们省略了网络错误出现的状况。实现了暴露网络错误和加载状态的版本见下面的Addendum: exposing network status

管理不一样组件间的依赖:

前面的UserRepository类须要Webservice的实例才能完成它的工做。能够直接建立它就是了,可是为此咱们还须要知道Webservice所依赖的东西才能构建它。这显著的增减了代码的复杂度和偶合度(好比,每一个须要Webservice实例的类都须要知道如何用它的依赖去构建它)。另外,UserRepository极可能不是惟一须要Webservice的类。若是每一个类都建立一个新的WebService,就变得很重了。

有两种模式能够解决这个问题:

依赖注入: 依赖注入容许类在无需构造依赖的状况下定义本身的依赖对象。在运行时由另外一个类来负责提供这些依赖。在Android app中咱们推荐使用谷歌的Dagger 2来实现依赖注入。Dagger 2 经过遍历依赖树自动构建对象,并提供编译时的依赖。

Service Locator:Service Locator 提供一个registry,类能够从这里获得它们的依赖而不是构建它们。相对依赖注入来讲要简单些,因此若是你对依赖注入不熟悉,可使用 Service Locator 。

这些模式容许你扩展本身的代码,由于它们提供了清晰的模式来管理依赖,而不是不断的重复代码。二者均支持替换成mock依赖来测试,这也是使用它们主要优点之一。

 

在这个例子中,咱们将使用 Dagger 2 来管理依赖。

链接ViewModel和repository

如今咱们修改UserProfileViewModel以使用repository。

  1. public class UserProfileViewModel extends ViewModel {
  2.     private LiveData<User> user;
  3.     private UserRepository userRepo;
  4.  
  5.     @Inject // UserRepository parameter is provided by Dagger 2
  6.     public UserProfileViewModel(UserRepository userRepo) {
  7.         this.userRepo = userRepo;
  8.     }
  9.  
  10.     public void init(String userId) {
  11.         if (this.user != null) {
  12.             // ViewModel is created per Fragment so
  13.             // we know the userId won't change
  14.             return;
  15.         }
  16.         user = userRepo.getUser(userId);
  17.     }
  18.  
  19.     public LiveData<User> getUser() {
  20.         return this.user;
  21.     }
  22. }

缓存数据

上面的repository对抽象web service调用是很好的,可是由于它只依赖于一个数据源,并非很是实用。

UserRepository的问题在于当获取完数据以后,它并无把数据保存下来。若是用户离开UserProfileFragment而后在回来,app会从新获取数据。这是很很差的,缘由有二:1.浪费了带宽资源,2.用户被迫等待新的查询完成。为了解决这个问题,咱们向UserRepository中添加了一个新的数据源,它将把User对象缓存到内存中。

  1. @Singleton  // informs Dagger that this class should be constructed once
  2. public class UserRepository {
  3.     private Webservice webservice;
  4.     // simple in memory cache, details omitted for brevity
  5.     private UserCache userCache;
  6.     public LiveData<User> getUser(String userId) {
  7.         LiveData<User> cached = userCache.get(userId);
  8.         if (cached != null) {
  9.             return cached;
  10.         }
  11.  
  12.         final MutableLiveData<User> data = new MutableLiveData<>();
  13.         userCache.put(userId, data);
  14.         // this is still suboptimal but better than before.
  15.         // a complete implementation must also handle the error cases.
  16.         webservice.getUser(userId).enqueue(new Callback<User>() {
  17.             @Override
  18.             public void onResponse(Call<User> call, Response<User> response) {
  19.                 data.setValue(response.body());
  20.             }
  21.         });
  22.         return data;
  23.     }
  24. }

持久化数据

目前的实现中,若是用户旋转屏幕或者是离开以后再次回到app,UI将当即可见,由于repository是从常驻内存的缓存中获取的数据。可是若是用户离开了app,几个小时以后再回来时进程已经被杀死了怎么办呢?

以目前的实现来看,咱们须要再次从网络获取数据。这不只仅是糟糕的用户体验,仍是一种浪费,由于它须要花费移动流量获取相同的数据。你能够直接缓存web请求,可是这又产生了新的问题。若是一样的user数据来自于另外一个类型的请求呢(好比获取一个朋友的列表)?那样的话你的app极可能会显示不一致的数据,这是一种困惑的用户体验。例如,由于朋友列表请求与用户请求可能在不一样的时间执行,同一用户的数据可能会不一致。你的app须要融合它们以免数据出现不一致。

处理这个问题的正确方式是使用持久化的model。持久化库Room就是为此而生。

Room是一个对象关系映射库,以最少的代码提供本地数据持久化功能。它在编译时验证每一个查询,因此损坏的SQL查询只会致使编译时错误而不是运行时崩溃。Room抽象了部分SQL查询与表的相关操做的底层细节。它还可让你经过一个LiveData对象监听到数据库数据的变化。另外,它还明肯定义了线程约束,解决了诸如从主线程获取存储这样的常见的问题。

注:若是熟悉其它的持久化方案好比SQLite ORM或者是一个不一样的数据库,如Realm,你不须要把它替换成Room,除非Room的特性对你的用例而言更加剧要。

要使用Room,咱们须要定义本地的schema。首先使用@Entity注解User类,将它标记为数据库中的一张表。

  1. @Entity
  2. class User {
  3.   @PrimaryKey
  4.   private int id;
  5.   private String name;
  6.   private String lastName;
  7.   // getters and setters for fields
  8. }

而后经过继承RoomDatabase建立一个database类:

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

注意MyDatabase是抽象类。Room根据它自动提供一个实现。详细状况参见Room文档。

如今咱们须要一个向数据库插入数据的方法。为此建立一个data access object (DAO)

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

而后,在database类中引用DAO。

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

注意load方法返回的是LiveData。Room知道database何时被修改过,当数据变化的时候,它将自动通知全部处于活动状态的observer。

注:对于alpha 1 版本,Room是根据表的修改检查验证,所以可能会发送错误的信号。

如今咱们能够修改UserRepository以和Room协同工做。

  1. @Singleton
  2. public class UserRepository {
  3.     private final Webservice webservice;
  4.     private final UserDao userDao;
  5.     private final Executor executor;
  6.  
  7.     @Inject
  8.     public UserRepository(Webservice webservice, UserDao userDao, Executor executor) {
  9.         this.webservice = webservice;
  10.         this.userDao = userDao;
  11.         this.executor = executor;
  12.     }
  13.  
  14.     public LiveData<User> getUser(String userId) {
  15.         refreshUser(userId);
  16.         // return a LiveData directly from the database.
  17.         return userDao.load(userId);
  18.     }
  19.  
  20.     private void refreshUser(final String userId) {
  21.         executor.execute(() -> {
  22.             // running in a background thread
  23.             // check if user was fetched recently
  24.             boolean userExists = userDao.hasUser(FRESH_TIMEOUT);
  25.             if (!userExists) {
  26.                 // refresh the data
  27.                 Response response = webservice.getUser(userId).execute();
  28.                 // TODO check for error etc.
  29.                 // Update the database.The LiveData will automatically refresh so
  30.                 // we don't need to do anything else here besides updating the database
  31.                 userDao.save(response.body());
  32.             }
  33.         });
  34.     }
  35. }

虽然咱们在UserRepository中改变了数据来源,可是咱们不须要修改UserProfileViewModel或者UserProfileFragment。这就是抽象带来的灵活性。这对测试一样有好处,由于你能够提供一个假的UserRepository来测试UserProfileViewModel。

如今咱们的代码就完成了。若是用户稍后回到相同的界面,将当即看到用户信息,由于这些信息作了持久化。同时,若是数据过于陈旧,repository将在后台更新数据。固然,这取决于你的案例,你可能会喜欢在数据太老的状况下不显示持久化的数据。

在某些状况下,好比下拉刷新,在界面上显示当前是否正在进行网络操做是很重要的。把UI操做和和实际数据分离是一种很好的实践,由于它可能由于各类缘由而更新(好比,若是咱们获取一个朋友的列表,同一用户可能被再次获取,从而触发一个 LiveData<User> update)。

这个问题有两种经常使用的解决办法:

  1. 把getUser修改为返回包含了网络操做状态的LiveData。附: 暴露网络状态小节中提供了一个实现了的例子。

  2. 在repository类中另外提供一个能够返回User刷新状态的公共的函数。若是只是为了明确的响应用户操做而在界面上显示网络状态((好比 pull-to-refresh).),这种方式更好些。

单一数据源(Single source of truth)

不一样的后端REST API返回相同的数据是很常见的事情。好比,若是有一个另外的后端地址返回一个朋友的列表,那么相同的User对象就有可能来自两个不一样的API地址,也许连内容详细程度都不同。若是UserRepository直接返回 Webservice 请求 的响应,咱们的UI就有可能显示不一致,由于服务端的两个请求之间数据是有区别的。这就是为何在UserRepository的实现中,web service 的回调只是把数据保存在数据库中。而后数据库中的变化将触发LiveData对象的callback。

在这个模型中,database就是这里的单一数据源(Single source of truth),其它部分都是经过repository获取它。无论你是不是使用磁盘缓存,咱们都推荐你的repository指明一个能够做为single source of truth数据源供app其它部分使用。

最终架构

下面的图标显示了咱们所推荐的架构的全部模块以及它们之间是如何交互的:

final-architecture.png

指导原则

  • 编程是一种创造性的劳动,开发Android app也不例外。同一问题有许多种解决办法,无论是activity或者fragment之间的数据交互,仍是获取远程数据并缓存到本地,仍是一个有必定规模的app可能遇到的其它常见问题。

  • 虽然下面的建议不是强制性的,但根据咱们以往的经验,从长远来看,遵照它们可让你的代码更加健壮,更加易于测试和维护。

  • manifest定义的入口-activity, service, broadcast receiver..等等,都不是数据源,它们只应该和与该入口相关的数据协做。由于每一个app组件的生命都很是短暂,取决于用户的行为以及设备运行时的情况,你确定不会但愿把它们做为数据源。

  • app不一样模块之间要有明确的责任边界。好比,不要把加载网络数据的代码分散到不一样的类或者包中。一样,也不要把不相关的职责-好比数据缓存和数据绑定-放在一个类中。

  • 每一个模块暴露的细节越少越好。不要图一时爽快把模块的内部实现暴露出来。你可能当时节省了一点时间,但随着代码的演变,你可能会付出不知翻了多少倍的技术债。

  • 在定义模块之间的交互时,思考如何作到各个模块的独立和可测试。好比,一个设计良好的网络数据请求API可让缓存数据到本地数据库模块的测试更加简单。而若是把这两个模块的逻辑混到一块儿,或者把网络请求的代码分散到代码的各处,都会使测试变得很难。

  • app的核心应该可让app脱颖而出的那些东西,不要把时间花在重复造轮子和反复写相同代码的事情上。相反,你应该把本身的精力集中到如何使app独一无二上。重复的工做就交给Android Architecture Components以及推荐的库来处理吧。

  • 尽量的持久化重要的,新生的数据,以便让你的app在离线状态下均可用。虽然你可能享受着高速的网络,但你的用户也许不是。

  • repository应该指派一个能够用做single source of truth的数据源。每当app须要获取一块数据的时候,它老是来自这个single source of truth。更多的信息参见上面的Single source of truth

附:暴露网络状态

在前面app架构推荐一节中,为了保持例子的简单,咱们有意省略了网络错误以及加载状态。这一节咱们将演示一种暴露网络状态的方法,使用一个Resource类来封装数据以及数据的状态。

下面是一个实现的例子:

  1. //a generic class that describes a data with a status
  2. public class Resource<T> {
  3.     @NonNull public final Status status;
  4.     @Nullable public final T data;
  5.     @Nullable public final String message;
  6.     private Resource(@NonNull Status status, @Nullable T data, @Nullable String message) {
  7.         this.status = status;
  8.         this.data = data;
  9.         this.message = message;
  10.     }
  11.  
  12.     public static <T> Resource<T> success(@NonNull T data) {
  13.         return new Resource<>(SUCCESS, data, null);
  14.     }
  15.  
  16.     public static <T> Resource<T> error(String msg, @Nullable T data) {
  17.         return new Resource<>(ERROR, data, msg);
  18.     }
  19.  
  20.     public static <T> Resource<T> loading(@Nullable T data) {
  21.         return new Resource<>(LOADING, data, null);
  22.     }
  23. }

由于从网络加载数据而显示从磁盘读出的数据是一种常见的用例,咱们将建立一个能够在多个地方重用的helper类:NetworkBoundResource。

下面是NetworkBoundResource的决策树:

network-bound-resource.png

首先从监听database获取resource开始。当第一次从数据库加载的时候,NetworkBoundResource检查获得的结果是否能够分发或者须要从网络获取。注意这二者可能同时发生,由于当从网络更新缓存数据的时候每每还须要显示数据。

若是网络调用成功完成,将响应结果保存到数据库中,而后从新初始化数据流。若是请求失败,咱们直接发出一个错误信号。

下面是NetworkBoundResource类为其子类提供的公共API:

  1. // ResultType: Type for the Resource data
  2. // RequestType: Type for the API response
  3. public abstract class NetworkBoundResource<ResultType, RequestType> {
  4.     // Called to save the result of the API response into the database
  5.     @WorkerThread
  6.     protected abstract void saveCallResult(@NonNull RequestType item);
  7.  
  8.     // Called with the data in the database to decide whether it should be
  9.     // fetched from the network.
  10.     @MainThread
  11.     protected abstract boolean shouldFetch(@Nullable ResultType data);
  12.  
  13.     // Called to get the cached data from the database
  14.     @NonNull @MainThread
  15.     protected abstract LiveData<ResultType> loadFromDb();
  16.  
  17.     // Called to create the API call.
  18.     @NonNull @MainThread
  19.     protected abstract LiveData<ApiResponse<RequestType>> createCall();
  20.  
  21.     // Called when the fetch fails. The child class may want to reset components
  22.     // like rate limiter.
  23.     @MainThread
  24.     protected void onFetchFailed() {
  25.     }
  26.  
  27.     // returns a LiveData that represents the resource
  28.     public final LiveData<Resource<ResultType>> getAsLiveData() {
  29.         return result;
  30.     }
  31. }

注意上面的类定义了两个类型的参数(ResultType,RequestType),由于API返回的数据类型可能和本地使用的数据类型不一致。

同时注意上面的代码使用了 ApiResponse 做为网络请求, ApiResponse 是对Retrofit2.Call 类的简单封装,用于将其响应转换为LiveData。

下面是NetworkBoundResource类其他的实现:

  1. public abstract class NetworkBoundResource<ResultType, RequestType> {
  2.     private final MediatorLiveData<Resource<ResultType>> result = new MediatorLiveData<>();
  3.  
  4.     @MainThread
  5.     NetworkBoundResource() {
  6.         result.setValue(Resource.loading(null));
  7.         LiveData<ResultType> dbSource = loadFromDb();
  8.         result.addSource(dbSource, data -> {
  9.             result.removeSource(dbSource);
  10.             if (shouldFetch(data)) {
  11.                 fetchFromNetwork(dbSource);
  12.             } else {
  13.                 result.addSource(dbSource,
  14.                         newData -> result.setValue(Resource.success(newData)));
  15.             }
  16.         });
  17.     }
  18.  
  19.     private void fetchFromNetwork(final LiveData<ResultType> dbSource) {
  20.         LiveData<ApiResponse<RequestType>> apiResponse = createCall();
  21.         // we re-attach dbSource as a new source,
  22.         // it will dispatch its latest value quickly
  23.         result.addSource(dbSource,
  24.                 newData -> result.setValue(Resource.loading(newData)));
  25.         result.addSource(apiResponse, response -> {
  26.             result.removeSource(apiResponse);
  27.             result.removeSource(dbSource);
  28.             //noinspection ConstantConditions
  29.             if (response.isSuccessful()) {
  30.                 saveResultAndReInit(response);
  31.             } else {
  32.                 onFetchFailed();
  33.                 result.addSource(dbSource,
  34.                         newData -> result.setValue(
  35.                                 Resource.error(response.errorMessage, newData)));
  36.             }
  37.         });
  38.     }
  39.  
  40.     @MainThread
  41.     private void saveResultAndReInit(ApiResponse<RequestType> response) {
  42.         new AsyncTask<Void, Void, Void>() {
  43.  
  44.             @Override
  45.             protected Void doInBackground(Void... voids) {
  46.                 saveCallResult(response.body);
  47.                 return null;
  48.             }
  49.  
  50.             @Override
  51.             protected void onPostExecute(Void aVoid) {
  52.                 // we specially request a new live data,
  53.                 // otherwise we will get immediately last cached value,
  54.                 // which may not be updated with latest results received from network.
  55.                 result.addSource(loadFromDb(),
  56.                         newData -> result.setValue(Resource.success(newData)));
  57.             }
  58.         }.execute();
  59.     }
  60. }

如今咱们就能够在repository中使用NetworkBoundResource来写实现用户的磁盘和网络操做了。

  1. class UserRepository {
  2.     Webservice webservice;
  3.     UserDao userDao;
  4.  
  5.     public LiveData<Resource<User>> loadUser(final String userId) {
  6.         return new NetworkBoundResource<User,User>() {
  7.             @Override
  8.             protected void saveCallResult(@NonNull User item) {
  9.                 userDao.insert(item);
  10.             }
  11.  
  12.             @Override
  13.             protected boolean shouldFetch(@Nullable User data) {
  14.                 return rateLimiter.canFetch(userId) && (data == null || !isFresh(data));
  15.             }
  16.  
  17.             @NonNull @Override
  18.             protected LiveData<User> loadFromDb() {
  19.                 return userDao.load(userId);
  20.             }
  21.  
  22.             @NonNull @Override
  23.             protected LiveData<ApiResponse<User>> createCall() {
  24.                 return webservice.getUser(userId);
  25.             }
  26.         }.getAsLiveData();
  27.     }
  28. }
相关文章
相关标签/搜索