[[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中的数据与响应用户交互的控制器
下面我们开始实现(为简单起见,省略了布局文件):
- 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 LifecycleFragment {
- 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);
- }
- }
注:上面的例子中继承的是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的妙处在于它是有生命周期意识的,当它不再被需要的时候会自动清理引用。
- 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数据更新的时候 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:
- public interface Webservice {
- /**
- * @GET declares an HTTP GET request
- * @Path("user") annotation on the userId parameter marks it as %3