Android应用架构简介

 

对于经过过构建app的Android开发人员来说, 现在是时候了解一下构建鲁棒, 质量高的应用的最佳实践和推荐架构了.

这篇文章假设读者对Android framework比较熟悉.

OK, let's begin!

 

App开发人员面临的常见问题

 

传统的桌面开发, 在大多数情况下, 拥有一个来自Launcher快捷键的单独入口点, 并在独立的整体进程中运行. 而Android应用则拥有更多复杂的结构. 典型的Android应用由多个应用构件组成, 包括Activities, Fragments, Services, ContentProviders和BroadcastReceivers.

大多数这些应用构件声明在AndroidManifest文件中, 该文件被Android系统使用以将应用整合进全面的用户体验中. 尽管, 如先前所言, 传统的桌面应用作为一个完整的进程运行, 而正确书写的Android应用需要更多的灵活性, 因为用户通过频繁地切换流和任务, 在设备上不同的应用间编写不同的路径.

比如, 当你在最喜欢的社交网络应用上分享照片时, 想想会发生什么吧. 应用触发了一个相机Intent, Android系统通过这个Intent打开相机应用来处理这个请求. 此时, 用户离开了社交网络应用, 但是体验是无缝联接的. 之后, 相机应用可能触发其它的Intent, 比如打开文件选择器, 而文件选择器可能打开了其它应用. 最后用户回到了社交网络应用并分享照片. 而且, 在这些进程, 用户可能随时被电话打断, 之后在打完电话后返回分享照片.

在Android中, 应用跳跃行为很是普遍, 所以你的应用必须正确地处理这些流程. 谨记: 移动设备是资源限制的, 由此在任意时刻, 操作系统需要杀掉一些应用, 为新的应用腾出空间.

关键点是: 应用构件能够被独立且无序地打开, 而且在任意时刻都能够被用户或者系统销毁. 因为应用构件是瞬息的, 它们的生命周期(控件被创建和销毁的时刻)并不受你控制, 由此, 在应用构件中不应该存储任何应用数据和状态, 而且应用构件之间不应该依赖于彼此.

 

通用架构规则

 

如果你不能使用应用构件保存应用数据和状态, 应用应该怎么组织?

你应该关注的最重要的事件是:关注分离. 常见的一个错误时: 在单个Activity/Fragment中写全部的代码. 任何不处理UI和操作系统交互的代码不应该写在这些类里面. 尽可能保持简洁会允许你避免生命周期相关的问题. 不要忘记: 你不拥有这些类, 它们仅仅是胶水类, 象征了操作系统和应用之间的协议. Android操作系统可能随时基于用户交互或者其它诸如内存不足等因素销毁它们. 要提供稳定的用户体验, 最好最小化对它们的依赖.

第二个重要的规则是: 使用模型, 尤其是持久化模型驱动UI. 持久化因两个原因而完美: 如果操作系统销毁了应用以释放资源, 用户不会丢失数据; 在网络连接弱或者未连接时, 应用能够持续工作. 而模型就是为用户处理数据的构件. 它们独立于应用中的视图和应用构建, 由此它们绝缘于这些构件的生命周期问题. 保持UI代码简单且独立于应用逻辑使得管理更加简便. 通过定义良好的数据管理职责, 将应用基于模型类, 将使得模型类更加容易测试, 使得应用更具一致性.

 

推荐的应用架构

 

下面将通过用例使用架构组件来组织应用.
备注: 不可能有一种写应用的方式对所有场景都是最好的. 也就是说, 这里推荐的架构对于大多数用例应该是好的起点. 如果你已经有了好找写Android应用的方式, 你无需改变.

想像一下, 构建一个展示用户概况的UI. 用户概况将会使用REST API从自有私有后台拉取.

 

构建user interface

 

UI由UserProfileFragment.java和对应的user_profile_layout.xml组成.

要驱动UI, 我们的数据模型需要持有两个数据元素.

  • User ID: 用户标识符. 最好使用fragment的arguments将这些信息传给fragment. 如果Android系统销毁了你的进程, 这个信息将会被保存, 所以在下次应用重启时, id是可用的.
  • User object: 持有用户数据的POJO.

我们会基于ViewModel类创建UserProfileViewModel来保持信息.
ViewModel为特定的UI构件, 诸如Activity/Fragment, 提供数据, 并处理与数据处理的业务部分的通讯, 诸如调用其它构件加载数据或者提交用户修改. ViewModel并不知晓视图, 且并不受诸如由于屏幕旋转导致的Activity重建等配置改变的影响.

现在我们有三个文件:

  • user_profile.xml: 为屏幕定义的UI.
  • UserProfileViewModel.java: 为UI准备数据的类.
  • UserProfileFragment.java: 在ViewModel中展示数据和对用户交互反馈的UI控制器.

下面是起始实现(为简单起见, layout文件没有提供):

复制代码
 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 Fragment {  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 }
复制代码


现在, 我们有三个代码模块, 我们该如何连接? 毕竟, ViewModel的user域设置的时候, 我们需要一种方式通知UI. 这就是LiveData出现的地方:

LiveData是可观测的数据持有者. 它让应用组件观测LiveData对象的改变, 却没有创建显著且严格的依赖路径. LiveData也尊重应用组件的生命周期状态, 做正确的事防止对象泄露, 保证应用并没消费更多的内存.

备注: 如果你在使用诸如RxJava/Agera库, 你可以继续使用它们而不必使用LiveData. 但是当你使用它们和其它的途径的时候, 确保你在正确地处理生命周期, 确保在相关的LifecycleOwner停止的时候, 数据流暂停; 在LifecycleOwner销毁的时候, 数据流被销毁. 你也可以添加 android.arch.lifecycle:reactivestreams 和其它的reactive库(比如, RxJava2)一起使用LiveData.

现在我们使用LiveData<User>取代UserProfileViewModel里的User域, 确保在数据更新的时候, fragment能够被通知到. LiveData的一个好处是: 它是可感知生命周期的, 在引用不再需要的时候, LiveData会自动地清除它们.