Android Jetpack 架构组件最佳实践

Android Jetpack介绍

Android Jetpack 是一套组件、工具和指导,可以帮助您快速构建出色的 Android 应用。

  • Google在17年的I/O大会上推出了架构组件(Architecture Component)工具集。

  • 随后在18年I/O大会上发布了 Android Jetpack,Jetpack 是Android开发组件工具集,旨在帮助我们轻松构建更稳定、更健壮、以及更可维护的应用程序。

android-jetpack.png

每个 Jetpack 组件均可单独采用,并且它们依然可以流畅地协作。

Jetpack主要分为4个部分,基础、架构、行为、界面。从图中得知,Jetpack并不全是些新东西,只要是能够帮助开发者更好更方便地构建应用程序的组件,Google都将其归纳入了Jetpack,可以看出Google对jetpack很重视,对开发者很上心。

  • 紧接着Google推出AndroidX,将许多Google认为是正确的方案和实践集中起来。

    • AndroidX 是对support library的重大改进。
    • AndroidX中的所有软件包名都以字符串androidx.开头,位于一致的命名空间中。
    • 与support支持库不同,AndroidX软件包可单独维护和更新。
    • 所有新的支持库开发都将在AndroidX库中进行。

官方推荐架构

官方架构图

使用此架构能带来什么好处?

  • UI和业务逻辑解耦。

  • 有效避免生命周期组件内存泄漏。

  • 提高模块可测试性。
  • 提高应用稳定性,有效降低以下异常发生概率。
    • Can not perform this action after onSaveInstanceState
    • WindowManager$BadTokenException, is your activity running?
    • OOM 、 NullPointerException

测试每个组件

  • 界面和交互:使用 Android 界面插桩测试。基于此架构只需mock 一个ViewModel即可完成界面测试。

  • ViewModel:使用 JUnit 测试。只需mock一个类,即 Repository

  • Repository:使用 JUnit 测试。只需mock两个类,XxxDao,XxxService;由于XxxDao,XxxService都是接口,还可以创建虚拟实现来完成复杂测试用例。

  • XxxDao:可以使用插桩测试来测试 DAO 类。这里注意对于每个测试,都请创建内存中数据库以确保测试没有任何副作用(例如更改磁盘上的数据库文件)。

  • XxxService:就Retrofit而言可以使用MockWebServer模拟本地服务器。

Lifecycle

Lifecycle是一个类,它包含组件(Activity或Fragment)生命周期状态的信息,并允许其他对象观察此状态。

跟踪组件生命周期

lifecycle-states

  • Lifecycle内部使用两个主要枚举(EventState)来跟踪其关联组件的生命周期状态。
  • Event:对应Activity或Fragment组件的生命周期回调事件。
  • State:表示被跟踪组件的当前状态,其中 STARTEDRESUMED 为活跃状态,可接受到liveData的数据更新。

LifecycleOwner和LifecycleRegistry

  • LifecycleOwner 是一个单一的方法接口,表示该类具有生命周期。support包从26.1.0版本开始,Fragment和Activity就默认实现了该接口。
  • LifecycleRegistry : Lifecycle接口的实现类,协助组件处理生命周期,可处理多个观察者。如果你想自定义LifecyclerOwner请参考support包中Fragment和Activity实现。

ViewModel

ViewModel 是用来保存应用UI数据的类,它会在配置变更(Configuration Change)后继续存在。

生命周期

viewmodel-lifecycle

关于ViewModel的生命周期就一句话:在Activity/Fragment等组件整个生命周期过程中,ViewModel的实例有且只有一个。

这样设计好处在哪呢?

  • 可用ViewModel存储数据,它能安全度过手机旋转等配置变更场景。
  • ViewModel能很好的实现多个Fragment之间的数据共享。

单一责任原则

viewmodel_duty

上图为官方中文视频截图。

  • Actvity或Fragment只显示UI和接收互动。
  • 为避免ViewModel臃肿,可创建presenter处理UI数据。(比如从数据列表中获取某个item的属性)
  • Repository 数据源操作入口。(便于单元测试)
  • 配合其它架构组件使用。

最佳实践

  • 如何时候都不要将Context传入ViewModel。
  • 如果要在ViewModel中使用Application实例,请使用AndroidViewModel子类。
  • ViewModel+LiveData+Databinding 可构建反应式UI。(请查看文末提供的参考资料)
  • ViewModel与onSaveInstanceState要配合使用。

    ViewModel | onSaveInstanceState
    —|—
    能度过配置变更 | 能度过配置变更和进程关闭
    存储大量数据 | 存储少量数据

    | 可序列化
    

    ViewModel和onSaveInstanceState是相辅相成的,当进程被关闭时,ViewModel会被销毁,而onSaveInstanceState不会受影响。所以用onSaveInstanceState存储少量关键数据(如xxxId),并在该场景恢复后用来加载页面数据。

ViewModel和View之间通信

Communication between ViewModel and View

  • UserProfileActivity引用UserViewModel,可观察其提供的UserLiveData、StatusLiveData、PageStateLiveData数据源变更来刷新UI。
  • 响应用户事件,比如更新用户信息。Activity将更新事件传递给ViewModel,ViewModel有将其委托给Presenter处理,Presenter处理过程及结果通过LiveData与Activity交互。
  • 注意Activity和ViewModel之间是单向引用。为避免内存泄漏,ViewModel不能持有任何Context引用。

LiveData

LiveData是一个具有生命周期感知特性的可观察的数据保持类。

  • LiveData只通知活跃状态( STARTED or RESUMED )的Observer更新,并在 DESTROYED状态时自动移除Observer,避免内存泄漏。
  • 始终保持最新数据。举例:1.退后台的Activity在返回前台后会立即收到最新数据。2. 配置变更导致Activity重建后也会立即收到最新数据。
  • 共享资源。单利模式共享同一个LiveData。

LiveData、MutableLiveData、MediatorLiveData区别?

  • 继承关系:MediatorLiveData -> MutableLiveData -> LiveData。 所以MediatorLiveData功能最强大。
  • LiveData 是一个具有生命周期感知的可观察的数据保持类。
  • MutableLiveData 在LiveData基础上打开了修改Value的方法权限。
  • MediatorLiveData 可管理多个LiveData。

Transformations

  • map : 将一种数据类型的

    转换为另一种类型 ```LiveData```
    1

    // 观察将被转换LiveData,待其数据源变更后转换为LiveData并通知订阅者。
    // 内部使用的MediatorLiveData实现。
    LiveData userLiveData = …;

    LiveData<String> userName = Transformations.map(userLiveData, user -> {
      user.name + " " + user.lastName
    

    });

    1
    2
    - **switchMap** : 和map类似。差别在于triggerLiveData变更后,会触发和等待另外一个LiveData获取数据。

    // 实例代码:将addressInputLiveData转换为postalCodeLiveData.
    class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    private final MutableLiveData addressInput = new MutableLiveData();
    public final LiveData postalCode =

          Transformations.switchMap(addressInput, (address) -> {
              return repository.getPostCode(address);
           });
    
    public MyViewModel(PostalCodeRepository repository) {
    this.repository = repository
    }
    

    // addressInputLiveData变更时触发repository.getPostCode,
    // 待其回去成功后,再将数据设置给postalCodeLiveData。

    private void setInput(String address) {
            addressInput.setValue(address);
        }
    

    }

    ```

几个问题

LifecycleOwner组件是如何与liveData通信的?

  • SupportActivity 通过添加一个空的ReportFragment来处理生命周期状态变更回调;Fragment则在自身生命周期函数中处理。
  • LifecycleOwner组件,通过LifecycleRegistry类中handleLifecycleEvent -> dispatchEvent方法与liveData通信,从而是liveData具有自动感知组件生命周期的能力。
  • 组件销毁时,LifecycleRegistry会通知liveData移除observer。

ViewModel如何做到一直在内存中,直到Activity销毁或Fragment被移除时才被清除的?

1.x.x版本实现

  • Activity或Fragment会添加一个空的HolderFragment,而ViewModelStore实例被HolderFragment持有,所以就保证了整个生命周期中ViewModelStore实例始终唯一,也就保证了其缓存的ViewModel实例会一直存在直到组件销毁(在onDestroy中会调用ViewModelStore.clear()方法清除其缓存的ViewModel实例)。
  • 由于这个HolderFragment设置了setRetainInstance(true), 这样在Activity重建时它不会执行onDestroy回调,这就是它能度过配置变更的原因

2.x.x版本实现

  • Activity

    • 缓存:onRetainNonConfigurationInstance()回调方法中将ViewModelStore实例缓存到NonConfigurationInstances中。
    • 恢复:在onCreate中通过getLastNonConfigurationInstance()获取重建前的状态并回复ViewModelStore。
  • Fragment

    • 缓存:FragmentActivity.onSaveInstanceState -> Fragment.saveAllState -> Fragment.saveNonConfig,将ViewModelStore实例缓存到了FragmentManagerNonConfig中,最终通过FragmentActivity将其缓存到NonConfigurationInstances中。

    • 回复:FragmentActivity.onCreate -> FragmentManager.restoreAllState(arg1, nonConfig) -> FragmentState.instantiate(x,x,x,nonConfig, viewModelStore)此方法会创建一个新的Fragment并将ViewModelStore变量赋值。

参考资料

0%