[Android] Learn And Use Jetpack

Posted by xiuyuantech on 2023-03-18

Jetpack 包含一系列 Android 库,它们都采用最佳做法并在 Android 应用中提供向后兼容性。
Jetpack 库可以单独使用,也可以组合使用,以满足应用的不同需求。
例如:
WorkManager - 满足您的后台调度需求。
Room - 实现数据存储持久性。
Navigation - 管理应用导航流程。
CameraX - 满足相机应用需求。

实现方式

所有 Jetpack 组件都可在 Google Maven 代码库中找到。 在应用中使用
Jetpack 库:打开 settings.gradle 文件,将 google() 代码库添加到 dependencyResolutionManagement { repositories
{…}} 块中,如下所示:

1
2
3
4
5
6
7
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
jcenter()
}
}

如需详细了解如何使用 Jetpack,请查看以下页面:

Android 架构组件
Jetpack 组件的完整列表
框架图

从零到一搭建

我的Demo采用MVVM的设计模式,框架图如下:
Demo框架图

Jetpack组件:

  1. Navigation:一个用于管理Fragment切换的工具类,可视化、可绑定控件、支持动画等是其优点。
  2. Data Binding:不用说,都知道,加速MVVM的创建。

Lifecycle:虽然我没有写文章介绍,但是不代表它的作用不够强大,他是我们能够处理Activity和Fragment的生命周期的重要原因,在AndroidX的Fragment和Activity已经对Lifecycle提供了默认支持。

  1. ViewModel:当做MVVM的ViewModel层,并具有声明周期意识的处理和UI相关的数据。
  2. LiveData:同RxJava的作用一样,对数据进行监听,优点就是无需处理生命周期、无内存泄漏等。
  3. Room:强大的ORM数据库框架。
  4. Paging:易于使用的数据分页库,支持RecyclerView。
  5. WorkManager:灵活、简单、延迟和保证执行的后台任务处理库。

使用踩坑

1.当我们快速点击按钮会重复调用navigate()导致异常如下:

1
2
3
java.lang.IllegalArgumentException: Navigation action / destination com.soushin.tinmvvm:id / action_homeFragment_to_categoryFragment cannot be found from the current destination Destination(
com.soushin.tinmvvm:id/ categoryFragment) label =
CategoryFragment class=com.soushin.tinmvvm.mvvm.ui.fragment.CategoryFragment

这是因为第一次调用navigate()
后当前页面已经换了,而我们的action是明确了起点和终点的,所以当第二次调用的时候起点不再是HomeFragment就会报上述异常,这在页面跳转时添加转场动画会显得非常明显(
转场动画有duration),只需要在跳转前判断currentDestination是否是当前页面的destination即可。

1
if (Navigation.findNavController(v).currentDestination?.id != R.id.homeFragment) return

2.出现无法找到导航图的问题.检查在xml文件中,是否设置了navGraph的资源文件; 如果设置了还是无法找到,请点击右上角的扳手的图标;
使用组件自带fragmentNavigator跳转时候,出现重复新建fragment对象问题,解决方案:请自定义fragmentNavigator;

3.传参数问题:
我是在重构项目中开始使用的,在别的页面开始进行navigation的使用的时候,需要把上一个页面的参数传进来,所以我会优先考虑使用FragmentManager的进行传递(因为这不是navigation的范围了),但是有个很重要的因素,就是他们使用的FragmentManager不是同一个,所以会导致数据传递失败了!!!
解决方法:

  1. 把上一个页面的参数,传到承载navigation容器的Activity/Fragment中,然后然后通过以下方式,把数据传入到起始目的地
1
navController.setGraph(R.navigation.nav_setting, bundleOf(FRAGMENT_DATA_IS_SET to ISSETPASSWORD))
  1. fragment之间传递参数
1
2
3
4
5
6
发送数据:
val bundle = bundleOf("amount" to amount)
view.findNavController().navigate(R.id.confirmationAction, bundle)
接受数据:
val tv = view.findViewById<TextView>(R.id.textViewAmount)
tv.text = arguments?.getString("amount")
  1. fragment向activity传递参数
1
2
3
4
5
6
发送数据:
val bundle = bundleOf("amount" to amount)
view.findNavController().navigate(R.id.confirmationAction, bundle)
接受数据:
val tv = view.findViewById<TextView>(R.id.textViewAmount)
tv.text = intent?.getString("amount")
  1. 如果Fragment中的参数需要用到其他页面,但并是不导航图的下一个目的地,请使用FragmentManager进行数据传递 同级Fragment中:
1
2
3
4
5
6
7
8
9
10
发送数据:
val result = Bundle()
result.putString("account", editAccount.text.toString())
result.putString("password", editPasswordAgain.text.toString())
setFragmentResult("numberKey", result)
接受数据:
setFragmentResultListener("numberKey") { key, bundle ->
registerAccount = bundle.getString("account", "")
registerPassWord = bundle.getString("password", "")
}

父Fragment与子Fragment之间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
父Fragment:
发送数据:
parentFragment.setFragmentResult("numberKey", result)
接受数据:
parentFragmentManager.setFragmentResultListener("numberKey", this,
FragmentResultListener { requestKey, result ->
registerAccount = result.getString("account", "")
registerPassWord = result.getString("password", "")
})

子Fragment:
发送数据:
parentFragment.setFragmentResult("numberKey", result)
接受数据:
childFragmentManager.setFragmentResultListener("numberKey", this,
FragmentResultListener { requestKey, result ->
registerAccount = result.getString("account", "")
registerPassWord = result.getString("password", "")
})

Navigation的底层对Fragment的管理直接采取了替换的方式,虽然它可以配合BottomNavigationView使用,但每次都重新加载显然是不合理的,不推荐使用。

LiveData

优点:对数据进行监听,拥有可观察、生命周期感知、、无内存泄漏等的特点。 缺点:

  1. 只能在主线程中订阅和移除观察者
  2. 数据丢失问题,在数据生产速度 > 数据消费速度时,LiveData 无法观察者能够接收到全部数据。比如在子线程大量 postValue 数据但主线程消费跟不上时,中间就会有一部分数据被忽略。
  3. 在 Fragment 中出现 LiveData 同样数据多次回调的问题,针对这个问题,Google 一位大神在 Stack Overflow 实现了一个复写类 SingleLiveEvent.
  4. observeForever 和 removeObserver 方法要配套使用,否则会导致内存泄漏问题
  5. LiveData天生就是粘性的,当我们重复订阅的时候,会返回最后一次结果. UnPeek-LiveData开源库可以解决这个问题。 LiveData 的替代者: RxJava: RxJava
    是第三方组织 ReactiveX 开发的组件,Rx 是一个包括 Java、Go 等语言在内的多语言数据流框架。功能强大是它的优势,支持大量丰富的操作符,也支持线程切换和背压。然而 Rx
    的学习门槛过高,对开发反而是一种新的负担,也会带来误用的风险。 Kotlin Flow: Kotlin Flow 是基于 Kotlin 协程基础能力搭建的一套数据流框架,从功能复杂性上看是介于
    LiveData 和 RxJava 之间的解决方案。Kotlin Flow 拥有比 LiveData 更丰富的能力,但裁剪了 RxJava 大量复杂的操作符,做得更加精简。并且在 Kotlin
    协程的加持下,Kotlin Flow 目前是 Google 主推的数据流框架。

Room

  1. 数据库加密不友好,推荐使用saferoom开源库适配
  2. 数据库版本升级迁移不友好,仅支持增删列字段,增删索引必须删除表重新创建; 新增字段必须添加默认值,建议项目创建时就添加默认值,索引一个字段单独使用普通索引,不建议使用联合索引即Index(
    字段1,字段2);

Paging

  1. 仅适合加载查看数据的场景,不适合增删改的场景且无增删改方法。一旦增删改之后,Paging分页逻辑将失去作用。
  2. 界面不可见后一段时间切回来时会重新加载数据。 解决方法: 1)对PagingFlow添加cacheIn(viewModelScope)。 2)不使用CoroutineLiveData,直接用
    lifecycleScope.launchWhenCreated 和 flow 来接收 PagingData 数据。

WorkManager

  1. 适配android12,版本必须在2.7.1之上
  2. 定时任务不一定准时执行,但一定会执行

总结

1.Android Jetpack组件中的很多库都对其他库提供了支持,比如Room和Paging就对LiveData提供了支持。
2.向后兼容:基本上每个组件都对低版本提供了支持。支持RxJava:由于RxJava强大的生态环境,几乎和数据相关的组件都对RxJava提供了支持。
3.减少代码量:以Data Binding + ViewModel + LiveData或RxJava构建的MVVM模式能够显著减少代码量,比较平时使用的MVP模式也会更加方便,无需主动更新UI。
4.无需捆绑:Android Jetpack系列组件可以无需捆绑使用,你如果只想用里面的单个库,那么就可以仅仅依赖一个库。