Jetpack 高级程序开发组件

Jetpack 简介

Jetpack 是一个开发组件工具集,是一个由多个库组成的套件,它的主要目的是帮助我们编写出更加简洁的代码、并简化开发过程。

Jetpack 中的组件有一个特点,它们大部分不依赖于任何 Android 系统版本,这意味着这些组件通常是定义在 AndroidX 库当中的,并且拥有非常好的向下兼容性,代码可在各种Android版本和设备中一致运行。

早在2017年时,Google就推出了一系列架构组件,称为Architecture Components(架构 组件),并于2018年在Google I/O大会上提出Jetpack,且将Architecture Components纳入其中,时至今日,越来越多组件被纳入其中。

Jetpack 家族主要由基础、架构、行为、界面这 4 个部分组成。其中也不全是些新东西,像通知、权限、Fragment 都属于 Jetpack。

Jetpack 所有的库都发布在AndroidX下面

AndroidX

早期的 support-v4 和 appcompat-v7支持库已经不合时宜了,他们是Android早期为了解决新版API的向后兼容问题而发布的。

按照官方文档说明 AndroidX 是对 android.support.xxx 包的整理后产物。由于之前的 support 包过于混乱,所以,Google 推出了AndroidX,将所有API的包名都统一为androidx.* 的方式,AndroidX不仅提供与支持库同等的功能,而且还提供了新的库。(support 包在 28 之后便不再更新了,为了避免影响使用AndroidX,可以使compileSdkVersion的编译版本不低于API 28)

在 AS 3.5 之后,新建项目默认使用 Android X。这对应着gradle.properties文件里这两句话:

1
2
3
4
5
# 表示当前项目启用 AndroidX
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
# 表示将依赖包(support包)也迁移到AndroidX 。如果取值为 false ,表示不迁移依赖包到AndroidX,但在使用依赖包中的内容时可能会出现问题,如果项目中没有使用任何三方依赖,那么,此项可以设置为 false
android.enableJetifier=true

AppCompatActivity 是 AndroidX 中提供的一种向下兼容的 Activity,可以使 Activity 在不同系统版本中的功能保持一致。AppCompatActivity 是 Activity 的子类。


WorkManager

WorkManager 很适合用于处理一些要求定时执行的任务,它可以根据操作系统的版本自动选择底层是使用 AlarmManager 实现还是 JobScheduler 实现,从而降低了我们的使用成本。另外,它还支持周期性任务、链式任务处理等功能,是一个非常强大的工具。

但 WorkManager 和 Service 并不相同,也没有直接联系。Service 是 Android 系统的四大组件之一,它在没有被销毁的情况下是一直保持在后台运行的。而 WorkManager 只是一个处理定时任务的工具,它可以保证即使在应用程序退出甚至手机重启的情况下,之前注册的任务依然将会得到执行,因此 WorkManager 很适合用于执行一些定期和服务器进行交互的任务,比如周期性地同步数据,等等。

另外,使用 WorkManager 注册的周期性任务不能保证一定会准时执行,这并不是 bug,而是系统为了减少电量消耗,可能会将触发时间临近的几个任务放在一起执行,这样可以大幅度低减少 CPU 被唤醒的次数,从而有效延长电池的使用时间。

WorkManager 的所有功能,在国产手机上都有可能得不到正确的运行。这是因为绝大多数的国产手机厂商在进行 Android 系统定制时会增加一个一键关闭的功能,允许用户一键杀死所有非白名单的应用程序。而被杀死的应用程序既无法接收广播,也无法运行 WorkManager 的后台任务。

所以,建议是 WorkManager 可以用,但是千万别依赖它去实现什么核心功能,因为它在国产手机上可能会非常不稳定。

基本用法

1
2
// 添加依赖
implementation "androidx.work:work-runtime:2.3.4"

基本用法主要分三步:

  • 定义一个后台任务,并实现具体的任务逻辑。
  • 配置该后台任务的运行条件和约束信息,并构建后台任务请求。
  • 将该后台任务请求传入 WorkManager 的 enqueue() 中,系统会在合适的时间运行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 后台任务的写法非常固定
* 首先每一个后台任务都必须继承自 Worker 类,并调用它唯一的构造函数。然后重写父类中的 doWork()。
*/
class SimpleWorker(context: Context,params:WorkerParameters):Worker(context,params) {


/**
* 编写具体的后台任务逻辑
* 不会运行在主线程中,可放心执行耗时逻辑。
*/
override fun doWork(): Result {
Log.e("TAG","do work in SimpleWorker")
// 返回一个 Result 对象,用于表示任务的运行结果。
// 成功就 Result.success(),失败就 Result.failure()
return Result.success()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

doWorkBtn.setOnClickListener{
// 配置该后台任务的运行条件和约束信息,这里只做最基本的配置。
// OneTimeWorkRequest.Builder 是 WorkRequest.Builder 的子类,用于构建单次运行的后台任务请求。
// 由于这里没有指定任何约束,因此后台任务基本上会在点击按钮之后立刻运行。
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java).build()

// WorkRequest.Builder 还有另一个子类 PeriodicWorkRequest.Build,
// 可用于构建周期性运行的后台任务请求,但是为了降低设备性能消耗,构造函数中传入的运行周期间隔不能短于 15 分钟。
// var request = PeriodicWorkRequest.Builder(SimpleWorker::class.java,15,TimeUnit.MINUTES).build()

// 将构建出的后台任务请求传入 WorkManager 的 enqueue() 中,系统就会在合适的时间去运行了。
WorkManager.getInstance(this).enqueue(request)
}
}
}

处理复杂任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

doWorkBtn.setOnClickListener{

val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
// 延迟在 5分钟之后运行
.setInitialDelay(5, TimeUnit.MINUTES)
// 添加标签,最主要的一个功能就是可以通过标签来取消后台任务请求
// WorkManager.getInstance(this).cancelAllWorkByTag("simple)
// 即使没有标签,也可以通过 id 来取消后台任务请求。
// WorkManager.getInstance(this).cancelWorkById(request.id)
// 使用 id 只能取消单个后台任务请求,而使用标签,则可以将同一标签名的所有后台任务请求全部取消。
// 还可以一次性取消所有后台任务请求:WorkManager.getInstance(this).cancelAllWork()
.addTag("simple")
// 当后台任务的 doWork() 中返回了 Result.retry(),那么可结合这个方法来重新执行任务。
// 后两个参数用于指定在多久之后重新执行任务,时间最短不能少于 10 秒钟。
// 第一个参数用于指定如果任务再次执行失败,下次重试的时间应该以什么样的形式延迟:
// 可选值有两种:LINEAR(以线性的方式延迟)和 EXPONENTIAL(以指数的方式延迟)。
.setBackoffCriteria(BackoffPolicy.LINEAR,10,TimeUnit.SECONDS)
.build()


// 监听后台任务的 doWork() 中返回的运行结果信息。
// 还可以调用 getWorkInfosByTagLiveData() 监听同一标签名下所有后台任务请求的运行结果
WorkManager.getInstance(this)
.getWorkInfoByIdLiveData(request.id).observe(this){ workInfo ->
if (workInfo.state == WorkInfo.State.SUCCEEDED){
Log.e("TAG","do work succeeded")
}else if (workInfo.state == WorkInfo.State.FAILED){
Log.e("TAG","do work failed")
}
}

WorkManager.getInstance(this).enqueue(request)

// WorkManager 的链式任务
val sync = ...
val compress = ...
val upload = ...
WorkManager.getInstance(this)
// 开启一个链式任务,后续使用 then() 来连接。
// 另外,必须前一个任务运行成功之后,后续任务才会执行。如果失败或是被取消了,后续任务就得不到运行。
.beginWith(sync)
.then(compress)
.then(upload)
.enqueue()
}
}
}

备注

参考资料

第一行代码(第3版)