Android 11 (R) 之 IntentService

Android 11/R 废弃了 IntentService

本文我们一起来回顾它


IntentService

1
2
3
4
5
6
7
8
public abstract class IntentService
extends Service

java.lang.Object
↳ android.content.Context
↳ android.content.ContextWrapper
↳ android.app.Service
↳ android.app.IntentService
  • 废弃原因 : IntentService受到Android 8.0(API级别26)施加的所有后台执行限制
  • 替代方案 : 当运行在 Android 8.0 或者更高的版本的时候考虑使用WorkManager或者JobIntentService , 利用jobs代替services

回顾

intentService.jpg

IntentService (API level 3 ~ API level R)基于Service,可根据需要处理异步请求(表示为Intent)。客户端通过调用Context.startService(Intent)()发送请求。服务根据需要启动,依次使用工作线程处理每一个Intent,当它运行完成自动会停止。

这种“工作队列处理器”模式通常用于从应用程序的主线程分担任务。IntentService类存在是为了简化此模式并注意机制。要使用它,继承IntentService 并实现 onHandleIntent(android.content.Intent)。IntentService将收到Intents,启动一个工作线程,并适当停止服务

所有的请求都在一个单独的工作线程种处理 – 它们或许会耗费大量时间(并且不会阻塞应用程序的主循环),但是一次只处理一个请求。

构造函数 说明
IntentService(String name) 创建一个IntentService,由您的子类的构造函数调用
方法 说明
onBind (Intent) 除非您为服务提供绑定,否则不需要实现此方法,因为默认实现返回null
onCreate() 首次创建服务时由系统调用。不要直接调用此方法
onDestroy() 由系统调用以通知服务该服务已不再使用且已被删除
onStart (Intent, int) 改为实现 onStartCommand(Intent,int,int)
onStartCommand(Intent,int,int) 您不应为IntentService覆盖此方法。而是改写onHandleIntent(Intent),当IntentService收到启动请求时,系统将调用该方法。
setIntentRedelivery(boolean) 设置Intent重新交付首选项
onHandleIntent(Intent) 在工作线程上调用此方法以进行处理。

示例说明

intentServiceTest.png

  • 一个进度条:更新加载进度
  • 三个文本:服务状态(进行中/结束)、 进程状态(进行中/结束)和进度条百分比 (0-100%)
  • 两个按钮:开始、停止

Activity 启动/停止任务、广播监听更新UI :文本状态和进度条进度

以下分别通过 IntentServiceJobIntentServiceWorkManager 3 种方式来实现进度加载效果

[ activity_main.xml ]

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="11dp"
android:layout_marginEnd="12dp"
android:max="100"
android:progress="0"
app:layout_constraintBottom_toBottomOf="@+id/tvProgress"
app:layout_constraintEnd_toStartOf="@+id/tvProgress"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/tvProgress" />

<TextView
android:id="@+id/tvThreadValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="线程结束"
app:layout_constraintStart_toStartOf="@+id/tvServiceValue"
app:layout_constraintTop_toBottomOf="@+id/tvServiceValue" />

<TextView
android:id="@+id/tvThreadStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="11dp"
android:layout_marginTop="10dp"
android:text="线程状态:"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvServiceStatus" />

<Button
android:id="@+id/btnStopService"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:text="停止"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/progressBar" />

<TextView
android:id="@+id/tvProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="86dp"
android:layout_marginEnd="15dp"
android:text="0%"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/tvServiceValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:text="服务结束"
app:layout_constraintStart_toEndOf="@+id/tvServiceStatus"
app:layout_constraintTop_toTopOf="@+id/tvServiceStatus" />

<Button
android:id="@+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:layout_marginEnd="13dp"
android:text="开始"
app:layout_constraintEnd_toStartOf="@+id/btnStop"
app:layout_constraintTop_toBottomOf="@+id/progressBar" />

<TextView
android:id="@+id/tvServiceStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="11dp"
android:layout_marginTop="27dp"
android:text="服务状态:"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

[ MainActivity.kt ]

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package com.shoewann.androidrtest

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.work.*
import kotlinx.android.synthetic.main.activity_main.*
import java.util.concurrent.TimeUnit


class MainActivity : AppCompatActivity(), View.OnClickListener {

private var receiver: MyBroadcastReceiver? = null

private var myWorkRequest: WorkRequest? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG, "onCreate() called with: savedInstanceState = $savedInstanceState")

//初始化广播
initReceiver()
//按钮点击监听事件
btnStart.setOnClickListener(this)
btnStop.setOnClickListener(this)
}

/**
* 开始 IntentService
*/
private fun startIntentService() {
val intent = Intent(this, MyIntentService::class.java)
startService(intent)
}

/**
* 停止 IntentService
*/
private fun stopIntentService() {
val intent = Intent(this, MyIntentService::class.java)
stopService(intent)
}

/**
* 开始 JobIntentService
*/
private fun startJobIntentService() {
val intent = Intent(this, MyJobIntentService::class.java)
MyJobIntentService.enqueueWork(this@MainActivity, intent)
}

/**
* 开始 WorkManager一次 Work 任务
*/
private fun startMyWorkManagerOneTimeWork() {
myWorkRequest = OneTimeWorkRequestBuilder<MyWorkManager>()
.build()
WorkManager.getInstance(this).enqueue(myWorkRequest as OneTimeWorkRequest)
}

/**
* 开始 WorkManager 周期性 Work 任务
*/
private fun startMyWorkManagerPeriodicWork() {

//注意:最短重复间隔是 15 分钟
myWorkRequest = PeriodicWorkRequestBuilder<MyWorkManager>(15 * 60 * 1000, TimeUnit.SECONDS)
.build()
WorkManager.getInstance(this).enqueue(myWorkRequest as PeriodicWorkRequest)
}

/**
* 停止work任务
*/
private fun stopMyWorkManager() {
WorkManager.getInstance(this).cancelWorkById(myWorkRequest!!.id)
}

/**
* 初始化广播
*/
private fun initReceiver() {
Log.d(TAG, "initReceiver() called")
if (receiver != null) return
receiver = MyBroadcastReceiver()
val intentFilter = IntentFilter()
intentFilter.addAction(ACTION_CASE_SERVICE)
intentFilter.addAction(ACTION_CASE_THREAD)
registerReceiver(receiver, intentFilter)
}

/**
* 广播内部类
*/
inner class MyBroadcastReceiver : BroadcastReceiver() {

override fun onReceive(context: Context, intent: Intent?) {
Log.d(TAG, "onReceive() called with: context = $context, intent = $intent")
if (intent == null) return
when (intent.action) {
ACTION_CASE_SERVICE -> {
tvServiceValue.text = intent.getStringExtra("status")
}
ACTION_CASE_THREAD -> {
val progress = intent.getIntExtra("progress", 0)
tvThreadValue.text = intent.getStringExtra("status")
progressBar.progress = progress
tvProgress.text = "$progress%"
}
else -> return
}
}
}

override fun onClick(v: View?) {
when (v?.id) {
R.id.btnStart -> {
//startIntentService()
//startJobIntentService()
//startMyWorkManagerOneTimeWork()
//startMyWorkManagerPeriodicWork()
}
R.id.btnStop -> {
//stopIntentService()
//stopMyWorkManager()
}
else -> return
}
}

override fun onResume() {
super.onResume()
Log.d(TAG, "onResume() called")
initReceiver()
}

override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy() called")
unregisterReceiver(receiver)
}

companion object {
private const val TAG = "MainActivity"
const val ACTION_CASE_SERVICE = "action_case_service"
const val ACTION_CASE_THREAD = "action_case_thread"
}
}

1. IntentService 方式

[ MyIntentService.kt ]

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package com.shoewann.androidrtest

import android.app.IntentService
import android.content.Intent
import android.util.Log
import com.shoewann.androidrtest.MainActivity.Companion.ACTION_CASE_SERVICE
import com.shoewann.androidrtest.MainActivity.Companion.ACTION_CASE_THREAD


class MyIntentService : IntentService("MyIntentService") {

private var isRunning = true
private var count = 0

companion object{
private const val TAG = "MyIntentService"
}

override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate() called")
sendServiceStatus("服务启动");
}

override fun onHandleIntent(intent: Intent?) {
Log.d(TAG, "onHandleIntent() called with: intent = $intent")
try {
sendThreadStatus("线程启动", count)
Thread.sleep(1000)
sendServiceStatus("服务运行中...")
isRunning = true
count = 0
while (isRunning) {
count++
if (count >= 100) {
isRunning = false
}
Thread.sleep(50)
sendThreadStatus("线程运行中...", count)
}
sendThreadStatus("线程结束", count)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}

override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy() called")
sendServiceStatus("服务结束")
}

// 发送服务状态信息
private fun sendServiceStatus(status: String) {
Log.d(TAG, "sendServiceStatus() called with: status = $status")
val intent = Intent(ACTION_CASE_SERVICE)
intent.putExtra("status", status)
sendBroadcast(intent)
}

// 发送线程状态信息
private fun sendThreadStatus(status: String, progress: Int) {
Log.d(TAG, "sendThreadStatus() called with: status = $status, progress = $progress")
val intent = Intent(ACTION_CASE_THREAD)
intent.putExtra("status", status)
intent.putExtra("progress", progress)
sendBroadcast(intent)
}
}
1
2
3
<service
android:name=".MyIntentService"
android:exported="false" />

2. JobIntentService 方式

[ MyJobIntentService.kt ]

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package com.shoewann.androidrtest

import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.core.app.JobIntentService


class MyJobIntentService : JobIntentService() {

private var isRunning = true
private var count = 0

companion object {

private const val TAG = "MyJobIntentService"
private const val JOB_ID = 1000

fun enqueueWork(context: Context, work: Intent) {
enqueueWork(context, MyJobIntentService::class.java, JOB_ID, work)
}
}

override fun onHandleWork(intent: Intent) {
Log.d(TAG, "onHandleWork() called with: intent = $intent")
try {
sendThreadStatus("线程启动", count)
Thread.sleep(1000)
sendServiceStatus("服务运行中...")
isRunning = true
count = 0
while (isRunning) {
count++
if (count >= 100) {
isRunning = false
}
Thread.sleep(50)
sendThreadStatus("线程运行中...", count)
}
sendThreadStatus("线程结束", count)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}

override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy() called")
sendServiceStatus("服务结束")
}

// 发送服务状态信息
private fun sendServiceStatus(status: String) {
Log.d(TAG, "sendServiceStatus() called with: status = $status")
val intent = Intent(MainActivity.ACTION_CASE_SERVICE)
intent.putExtra("status", status)
sendBroadcast(intent)
}

// 发送线程状态信息
private fun sendThreadStatus(status: String, progress: Int) {
Log.d(TAG, "sendThreadStatus() called with: status = $status, progress = $progress")
val intent = Intent(MainActivity.ACTION_CASE_THREAD)
intent.putExtra("status", status)
intent.putExtra("progress", progress)
sendBroadcast(intent)
}

}

[ AndroidManifest.xml ]

1
2
3
4
5
<uses-permission android:name="android.permission.WAKE_LOCK" />
<service
android:name=".MyJobIntentService"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" />

2. WorkManager 方式

[ MyWorkManager.kt ]

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.shoewann.androidrtest

import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters

class MyWorkManager(context: Context, params: WorkerParameters) : Worker(context, params) {

private var isRunning = true
private var count = 0

companion object {
private const val TAG = "MyWorkManager"
}

private var context: Context? = null
private var params: WorkerParameters? = null

init {
this.context = context
this.params = params
}

override fun doWork(): Result {
Log.d(TAG, "doWork() called")
try {
sendThreadStatus("线程启动", count)
Thread.sleep(1000)
sendServiceStatus("服务运行中...")
isRunning = true
count = 0
while (isRunning) {
count++
if (count >= 100) {
isRunning = false
}
Thread.sleep(50)
sendThreadStatus("线程运行中...", count)
}
sendThreadStatus("线程结束", count)
} catch (e: InterruptedException) {
e.printStackTrace()
}
return Result.success()
}

private fun sendServiceStatus(status: String) {
Log.d(TAG, "sendServiceStatus() called with: status = $status")
val intent = Intent(MainActivity.ACTION_CASE_SERVICE)
intent.putExtra("status", status)
context?.sendBroadcast(intent)
}

private fun sendThreadStatus(status: String, progress: Int) {
Log.d(TAG, "sendThreadStatus() called with: status = $status, progress = $progress")
val intent = Intent(MainActivity.ACTION_CASE_THREAD)
intent.putExtra("status", status)
intent.putExtra("progress", progress)
context?.sendBroadcast(intent)
}

override fun onStopped() {
super.onStopped()
Log.d(TAG, "onStopped() called")
}
}

[ app -> build.gradle ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
dependencies {

// 依赖配置
def work_version = "2.3.3"

// (Java only)
implementation "androidx.work:work-runtime:$work_version"

// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"

// optional - RxJava2 support
implementation "androidx.work:work-rxjava2:$work_version"

// optional - GCMNetworkManager support
implementation "androidx.work:work-gcm:$work_version"

// optional - Test helpers
androidTestImplementation "androidx.work:work-testing:$work_version"
}

效果

1. IntentService

  • 点击[开始],服务运行,线程计数完成后,服务和线程都结束。
  • 点击[开始],服务运行,线程计数完成前,点击[停止],服务结束,线程计数完成后线程结束。
  • 点击两次[开始],服务运行,第一次线程计数完成后,进行第二次线程计数,两次完成后,服务和线程都结束。
  • 点击两次[开始],服务运行,第一次线程计数完成前,点击两次[停止],服务结束,第一次线程计数完成后线程结束,不进行第二次线程计数

2. JobIntentService

  • 点击[开始],服务运行,线程计数完成后,服务和线程都结束。
  • 点击[开始],服务运行,线程计数完成前,点击[停止],服务运行中,线程计数完成后线程结束。
  • 点击两次[开始],服务运行,第一次线程计数完成后,进行第二次线程计数,两次完成后,服务和线程都结束。
  • 点击两次[开始],服务运行,第一次线程计数完成前,点击两次[停止],服务运行中,第一次线程计数完成后线程结束,不进行第二次线程计数

3. WorkManager (OneTimeWork,非周期性)

  • 点击[开始],服务运行,线程计数完成后,服务和线程都结束。
  • 点击[开始],服务运行,线程计数完成前,点击[停止],服务无法结束,线程计数完成后线程结束。
  • 点击两次[开始],服务运行,两次线程同时进行叠加计数,线程结束,服务运行中。
  • 点击两次[开始],服务运行,两次线程同时进行叠加计数,点击两次[停止],两次完成后分别进行两次线程结束,服务运行中。

总结

  1. IntentService 和 JobIntentService(都继承自Service),创建一个工作线程处理多线程任务,线程优先级相对普通线程要高,不需要手动结束,在任务处理完后,会自动关闭服务

  2. IntentService从未被设计为可以取消的,因此缺少用于取消JobIntentService的任何API。因此,JobIntenService仅应用于不需要取消的工作。JobIntentService是作为实现的IntentService。尽管您可以stopService()用来销毁IntentService,这可能不会对用于onHandleIntent()的后台线程产生影响,在onHandleIntent方法完成前一个请求之前,它不会通过某些intent操作停止或处理任何其他请求。在任务执行完毕之前,将不会调用stopService()( via : how-to-cancel-a-jobintentservice )

  3. WorkManager,并不是所有的任务都可以取消,当任务正在执行时是不能取消的,当然任务执行完成了,取消也是意义的,也就是说当任务加入到ManagerWork的队列中但是还没有执行时才可以取消。

  1. 如果将[开始]按钮里面的代码延时(70s)启动,模拟后台启动,MainActivity.kt 改成如下:
1
2
3
4
5
6
7
8
9
R.id.btnStart -> {
Log.d(TAG, "startService() called")
Handler(Looper.getMainLooper()).postDelayed({
//startIntentService()
//startJobIntentService()
//startMyWorkManagerOneTimeWork()
//startMyWorkManagerPeriodicWork()
}, 70 * 1000)
}

再点击 开始 按钮之后,按Home键回到launcher
IntentService 的方式会因为Android O / 8.0 后台限制抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2020-03-10 17:23:01.132 10813-10813/com.shoewann.androidrtest D/MainActivity: startIntentService() called
2020-03-10 17:23:01.139 10813-10813/com.shoewann.androidrtest D/AndroidRuntime: Shutting down VM


--------- beginning of crash
2020-03-10 17:23:01.143 10813-10813/com.shoewann.androidrtest E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.shoewann.androidrtest, PID: 10813
java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.shoewann.androidrtest/.MyIntentService }: app is in background uid UidRecord{f520899 u0a421 LAST bg:+1m8s709ms idle change:idle procs:1 seq(0,0,0)}
at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1699)
at android.app.ContextImpl.startService(ContextImpl.java:1654)
at android.content.ContextWrapper.startService(ContextWrapper.java:719)
at com.shoewann.androidrtest.MainActivity.startIntentService(MainActivity.kt:59)
at com.shoewann.androidrtest.MainActivity.access$startIntentService(MainActivity.kt:18)
at com.shoewann.androidrtest.MainActivity$onClick$1.run(MainActivity.kt:158)
at android.os.Handler.handleCallback(Handler.java:907)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:216)
at android.app.ActivityThread.main(ActivityThread.java:7506)
at java.lang.reflect.Method.invoke(Native Method)
a
2020-03-10 17:23:01.169 10813-10813/com.shoewann.androidrtest I/Process: Sending signal. PID: 10813 SIG: 9

其他均验证通过
JobIntentServiceWorkManager 的方式为 IntentService 替代方案

以上就是本文的全部内容,其他方面请自行总结扩展测试,感谢您的阅读

本文基于Android 11 / R DP1 发布,如有不足, 欢迎指正

-------------本文结束感谢您的阅读-------------
if (本文对您有用) { Pay (请随意¥打赏) } else { Commit feedback (底部评论区提交建议、反馈) } 感谢支持!
0%