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

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

[[192223]]

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

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

app开发者面临的常见问题

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

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

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

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

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

通用的架构准则

如果app组件不能存放数据和状态,那么app还是可架构的吗?

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

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

app架构推荐

在这一小节中,我们将通过一个用例演示如何使用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.  
  3.     private String userId; 
  4.  
  5.     private User user
  6.  
  7.   
  8.  
  9.     public void init(String userId) { 
  10.  
  11.         this.userId = userId; 
  12.  
  13.     } 
  14.  
  15.     public User getUser() { 
  16.  
  17.         return user
  18.  
  19.     } 
  20.  
  1. public class UserProfileFragment extends LifecycleFragment { 
  2.  
  3.     private static final String UID_KEY = "uid"
  4.  
  5.     private UserProfileViewModel viewModel; 
  6.  
  7.   
  8.  
  9.     @Override 
  10.  
  11.     public void onActivityCreated(@Nullable Bundle savedInstanceState) { 
  12.  
  13.         super.onActivityCreated(savedInstanceState); 
  14.  
  15.         String userId = getArguments().getString(UID_KEY); 
  16.  
  17.         viewModel = ViewModelProviders.of(this).get(UserProfileViewModel.class); 
  18.  
  19.         viewModel.init(userId); 
  20.  
  21.     } 
  22.  
  23.   
  24.  
  25.     @Override 
  26.  
  27.     public View onCreateView(LayoutInflater inflater, 
  28.  
  29.                 @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 
  30.  
  31.         return inflater.inflate(R.layout.user_profile, container, false); 
  32.  
  33.     } 
  34.  

注:上面的例子中继承的是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.     ... 
  4.  
  5.     private User user
  6.  
  7.     private LiveData<Useruser
  8.  
  9.     public LiveData<User> getUser() { 
  10.  
  11.         return user
  12.  
  13.     } 
  14.  

现在我们修改UserProfileFragment,让它观察数据并更新UI。

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

每当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.     /** 
  4.  
  5.      * @GET declares an HTTP GET request 
  6.  
  7.      * @Path("user") annotation on the userId parameter marks it as a 
  8.  
  9.      * replacement for the {user} placeholder in the @GET path 
  10.  
  11.      */ 
  12.  
  13.     @GET("/users/{user}"
  14.  
  15.     Call<User> getUser(@Path("user") String userId); 
  16.  

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

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

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

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

虽然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.  
  3.     private LiveData<Useruser
  4.  
  5.     private UserRepository userRepo; 
  6.  
  7.   
  8.  
  9.     @Inject // UserRepository parameter is provided by Dagger 2 
  10.  
  11.     public UserProfileViewModel(UserRepository userRepo) { 
  12.  
  13.         this.userRepo = userRepo; 
  14.  
  15.     } 
  16.  
  17.   
  18.  
  19.     public void init(String userId) { 
  20.  
  21.         if (this.user != null) { 
  22.  
  23.             // ViewModel is created per Fragment so 
  24.  
  25.             // we know the userId won't change 
  26.  
  27.             return
  28.  
  29.         } 
  30.  
  31.         user = userRepo.getUser(userId); 
  32.  
  33.     } 
  34.  
  35.   
  36.  
  37.     public LiveData<User> getUser() { 
  38.  
  39.         return this.user
  40.  
  41.     } 
  42.  

 

责任编辑:庞桂玉 来源: Android技术之家
相关推荐

2011-03-17 08:58:09

数据储存Data StoragAndroid API

2015-07-28 12:59:11

微软Windows 10指南

2017-07-18 16:40:31

AndroidLiveData

2011-06-17 15:57:46

CocoaXcode苹果

2012-03-26 09:27:40

谷歌安卓开发谷歌安卓

2017-09-18 14:27:51

AndroidInstant AppFAQ Vol.6

2016-09-27 15:06:08

2011-09-05 14:02:53

Android视频教程

2012-02-16 10:31:02

AndroidWeb App官方文档

2012-02-13 16:39:03

AndroidWeb App官方文档

2012-02-16 10:18:48

AndroidWeb App官方文档

2012-02-09 10:39:37

AndroidWeb App官方文档

2010-04-21 14:25:22

bada三星

2011-06-17 16:23:49

Cocoa苹果

2015-08-04 10:25:12

velocity

2011-04-19 10:38:53

Xcode 4MacRubyiOS

2019-10-11 09:38:56

谷歌Android开发者

2012-02-22 10:14:41

Web App

2012-02-16 09:56:29

2018-05-03 08:57:58

谷歌 域名 APP
点赞
收藏

51CTO技术栈公众号