当前位置: 首页 > 科技观察

应用开发架构指南(谷歌官方文档翻译)

时间:2023-03-15 08:22:22 科技观察

本文面向已经掌握应用开发基础知识,想知道如何开发出健壮应用的读者。注意:本指南假定读者已经熟悉Android框架。如果您不熟悉应用程序开发,请查看入门系列教程,其中涵盖了本指南的先决条件。应用程序开发人员面临的常见问题与传统的桌面应用程序开发不同,Android应用程序的架构要复杂得多。一个典型的Android应用程序由多个应用程序组件组成,包括活动、片段、服务、内容提供者和广播接收器。传统的桌面应用往往是在一个巨大的单一进程中完成的。大多数应用程序组件都在应用程序清单中声明,Android操作系统使用它来确定如何将您的应用程序与设备集成以形成统一的用户体验。虽然刚才说了,桌面应用只运行一个进程,但是一个优秀的Android应用需要更加灵活,因为用户在不同的应用之间操作,不断切换进程和任务。例如,想象一下当您在最喜欢的社交网络应用程序中分享照片时会发生什么。该应用程序触发相机意图,Android操作系统启动相机应用程序来处理该动作。此时,用户已经离开了社交网络应用,但用户的操作体验是无缝的。相机应用程序可能会触发另一个意图,例如启动文件选择器,这可能会再次打开另一个应用程序。***用户返回社交网络应用并分享照片。在此期间的任何时候,用户都可以被电话打断,并在电话结束后继续回来分享照片。这种应用程序并行运行的行为在Android中很常见,因此您的应用程序必须正确处理这些进程。还请记住,移动设备的资源有限,因此操作系统随时可能会杀死一些应用程序以为新应用程序运行腾出空间。一般情况下,您的应用程序组件可能会单独和乱序启动,并可能随时被系统或用户销毁。由于应用组件的生命周期短,生命周期不可控,任何数据都不应该存储在应用组件中,应用组件之间也不应该相互依赖。一般架构指南如果应用程序组件不能存储数据和状态,应用程序是否仍然可以架构?最重要的原则之一是尝试在应用程序中实现关注点分离(separationofconcerns)。一个常见的错误是将所有代码都写在Activity或Fragment中。任何与UI和系统交互无关的东西都不应该放在这些类中。使它们尽可能简单和轻便可以避免很多生命周期问题。不要忘记energy不拥有这些类,它们只是应用程序和操作系统之间的桥梁。根据用户操作和其他因素(例如内存不足),Android操作系统可能会随时销毁它们。为了提供可靠的用户体验,最好尽量减少对它们的依赖。第二个非常重要的标准是使用。持久化的原因是基于两个原因:如果操作系统销毁应用程序释放资源,用户数据不会丢失;当网络不佳或断开连接时,该应用程序可以继续工作。Model是负责app数据处理的组件。它们不依赖于View或应用程序组件(Activity、Fragment等),因此它们不受这些组件生命周期的影响。保持UI代码简单并将其与业务逻辑分离可以使其更易于管理。App架构推荐在本节中,我们将通过一个用例来演示如何使用ArchitectureComponent来构建一个应用程序。注意:应用程序编写没有万能的方法。也就是说,这里建议的架构适合作为大多数用户故事的起点。但是如果你已经有了一个好的架构,就没有必要修改它了。假设我们正在创建一个显示用户配置文件的UI。用户信息是从我们自己的私有后端RESTAPI中获取的。创建用户界面UI由UserProfileFragment.java和相应的布局文件user_profile_layout.xml组成。为了驱动UI,我们的数据模型需要包含两个数据元素。用户ID:用户标识。***使用片段参数传递此数据。如果操作系统终止了您的进程,则可以保留此数据,因此再次启动应用程序时该ID仍然可用。用户对象:保存用户信息数据的POJO对象。我们将创建一个扩展ViewModel类的UserProfileViewModel来保存此信息。ViewModel为特定的UI组件(例如片段或活动)提供数据,并负责与数据处理的业务逻辑部分通信,例如调用其他组件加载数据或转发用户修改。ViewModel并不知道View的存在,也不会受到配置变化的影响。现在我们有了三个文件。user_profile.xml:定义页面的UIUserProfileViewModel。java:为UI准备数据的UserProfileFragment.java类privatestaticfinalStringUID_KEY="uid";privateUserProfileViewModelviewModel;@OverridepublicvoidonActivityCreated(@NullableBundlessavedInstanceState){super.onActivityCreated(savedInstanceState);StringuserId=getArguments().getString(UID_KEY);viewModel=ViewModelProviders.of(this).get(UserProfileViewModel.class);viewModel.init(userId);}@OverridepublicViewonCreateView(LayoutInflaterinflater,@NullableViewGroupcontainer,@NullableBundlesavedInstanceState){returninflater.inflate(R.layout.user_profile,container,false);}}注意:上例中继承的是LifecycleFragment,而不是Fragment类.ArchitectureComponent中的lifecyclesAPI稳定后,AndroidSupportLibrary中的Fragment类也会实现LifecycleOwner。现在我们有了这些代码模块,如何连接它们呢?毕竟当ViewModel的用户成员设置好后,我们还是需要在界面上显示出来。这将使用LiveData。LiveData是一个可观察的数据持有者。可以观察到LiveData对象的更改,而无需在它与应用程序组件之间显式创建依赖关系。LiveData还考虑了应用程序组件(活动、片段、服务)的生命周期状态,并采取措施防止对象泄漏。注意:如果您已经在使用RxJava或Agera等库,则可以继续使用它们而不是LiveData。但是在使用它们时,请确保正确处理生命周期问题。当相关的LifecycleOwner停止时,数据流也应该停止,当LifecycleOwner被销毁时,数据流也应该被销毁。您还可以使用android.arch.lifecycle:reactivestreams将LiveData与其他反应流库(例如RxJava2)一起使用。现在我们将UserProfileViewModel中的User成员替换为LiveData,这样当数据发生变化时,片段就会得到通知。LiveData的美妙之处在于它具有生命周期感知能力,并且会在不再需要时自动清理引用。publicclassUserProfileViewModelextendsViewModel{...privateUseruser;privateLiveDatauser;publicLiveDatagetUser(){returnuser;}}现在我们修改UserProfileFragment让它观察数据并更新UI。@OverridepublicvoidonActivityCreated(@NullableBundlesavedInstanceState){super.onActivityCreated(savedInstanceState);viewModel.getUser().observe(this,user->{//updateUI});}每当User数据更新时都会触发onChanged回调,然后刷新的用户界面。如果您熟悉其他库中可观察回调的使用,您会意识到我们不需要重写片段的onStop()方法来停止观察数据。因为LiveData是生命周期感知的,也就是说,除非片段处于活动状态,否则不会触发回调。LiveData也可以在fragmentonDestroy()时自动移除观察者。它也没有为我们处理配置更改(例如旋转屏幕)做任何特别的事情。ViewModel可以在配置更改时自动保存,一旦新的fragment进入生命周期,它会收到相同的ViewModel实例,并立即调用携带当前数据的回调。这就是为什么ViewModel不应该直接引用任何View,它们在View的生命周期之外。请参阅ViewModel的生命周期。获取数据现在我们把ViewModel和fragment连接起来了,但是ViewModel是怎么获取数据的呢?在我们的示例中,假设后端提供了RESTAPI,我们使用Retrofit从后端获取数据。您也可以出于相同目的使用任何其他库。下面是与后端交互的retrofitWebservice:publicinterfaceWebservice{/***@GETdeclaresanHTTPGETrequest*@Path("user")annotationontheuserIdparametermarksitasa*replacementforthe{user}placeholderinthe@GETpath*/@GET("/users/{user}")CallgetUser(@Path("user")StringuserId);}ViewModel的一个简单实现是直接调用Webservice获取数据,然后赋值给User对象。虽然这可行,但随着应用程序的增长,它变得难以维护。为ViewModel承担太多责任也违反了前面提到的关注点分离原则。另外,ViewModel的有效时间与Activity和Fragment的生命周期是绑定的,生命周期结束就丢失所有数据是很糟糕的用户体验。相反,我们的ViewModel会将这项工作委托给Repository模块。Repository模块负责处理数据操作。他们提供带有简洁API的应用程序。他们知道从哪里获取数据以及在更新数据时调用什么API。您可以将它们视为不同数据源(持久模型、Web服务、缓存等)之间的中介。以下UserRepository类使用WebService获取用户数据。publicclassUserRepository{privateWebservicewebservice;//...publicLiveDatagetUser(intuserId){//这不是一个最优的实现,我们将在下面修复它finalMutableLiveDatadata=newMutableLiveData<>();webservice.getUser(userId).enqueue(newCallback(){@OverridepublicvoidonResponse(Callcall,Responseresponse){//errorcaseisleftoutforbrevitydata.setValue(response.body());}});returndata;}}虽然存储库模块看起来不必要,其实性能起着重要的作用;它从应用程序中抽象出数据源。现在我们的ViewModel并不知道数据是Webservice提供的,也就是说如果需要的话可以换成其他的实现。注意:为简单起见,我们忽略了网络错误的发生。请参阅下面的附录:公开网络状态以获取实现公开网络错误和加载状态的版本。管理不同组件之间的依赖关系:前面的UserRepository类需要一个Webservice的实例来完成它的工作。它可以直接创建,但是为此我们还需要知道Webservice依赖于什么才能构建它。这显着增加了代码的复杂性和耦合性(例如,每个需要Webservice实例的类都需要知道如何使用其依赖项来构建它)。此外,UserRepository很可能不是唯一需要Web服务的类。如果每个类都创建一个新的WebService,它会变得很重。有两种模式可以解决这个问题:DependencyInjection:DependencyInjection允许类定义自己的依赖对象,而无需构造依赖项。另一个类负责在运行时提供这些依赖项。我们建议使用Google的Dagger2在Android应用程序中进行依赖注入。Dagger2通过遍历依赖树自动构建对象并提供编译时依赖。服务定位器:服务定位器提供了一个注册表,类可以从中获取它们的依赖关系而不是构建它们。它比依赖注入更简单,所以如果你不熟悉依赖注入,你可以使用服务定位器。这些模式允许您扩展自己的代码,因为它们提供了一个清晰的模式来管理依赖关系,而不是不断重复代码。两者都支持通过替换模拟依赖项进行测试,这是使用它们的主要优势之一。在本例中,我们将使用Dagger2来管理依赖项。将ViewModel连接到存储库现在我们修改UserProfileViewModel以使用存储库。publicclassUserProfileViewModelextendsViewModel{privateLiveDatauser;privateUserRepositoryuserRepo;@Inject//UserRepository参数由Dagger2提供userRepo.getUser(userId);}publicLiveDatagetUser(){returnthis.user;}}