后台Service

探究Service

Service并不是运行在一个独立的进程当中的,而是依赖于创建Service时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的Service也会停止运行。

另外,也不要被Service的后台概念所迷惑,实际上Service并不会自动开启线程,所有的代码都是默认运行在主线程当中的。

我们需要在Service的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞的情况。

多线程编程

基本用法

  • 继承Thread,然后重写父类的run()方法,并调用start()方法启动;
  • 使用Runnable接口,并调用start方法;
  • thread顶层函数(Kotlin),在里面写Lambda即可。

更新UI

Android的UI是线程不安全的。要更新应用程序里的UI元素,必须在主线程中进行,否则就会出现异常,因此常常使用异步消息处理的方式来更新UI元素。

Android中的异步消息处理主要由4个部分组成:Message、Handler、MessageQueue和Looper。

  • Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间传递数据。
  • Handler顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。
  • MessageQueue是消息队列的意思,它主要用于存放所有通过Handler发送的消息。
  • Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入一个无限循环当中,然后每当发现MessageQueue中存在一条消息时,就会将它取出,并传递到**Handler的handleMessage()**方法中。

异步消息处理流程:

  1. 首先需要在主线程当中创建一个Handler对象,并重写handleMessage()方法。
  2. 然后当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler将这条消息发送出去。
  3. 之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息,最后分发回Handler的handleMessage()方法中。
  4. 由于Handler在主线程中创建,所以handleMessage()方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行UI操作了。

QQ_1737703522609

安卓也使用AsyncTask工具来在子线程中进行UI操作,其基本使用步骤如下:

  1. 继承AsyncTask类,指定三个泛型参数(传入参数、进度单位、结果返回);
  2. 重写AsyncTask的方法,如onPreExecute(),doInBackground(),onProgressUpdate(),onPostExecute();
  3. 调用execute()启动任务。
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
class DownloadTask : AsyncTask<Unit, Int, Boolean>() { 

override fun onPreExecute() {
progressDialog.show() // 显示进度对话框
}

override fun doInBackground(vararg params: Unit?) = try {
while (true) {
val downloadPercent = doDownload() // 这是一个虚构的方法
publishProgress(downloadPercent)
if (downloadPercent >= 100) {
break
}
}
true
} catch (e: Exception) {
false
}

override fun onProgressUpdate(vararg values: Int?) {
// 在这里更新下载进度
progressDialog.setMessage("Downloaded ${values[0]}%")
}

override fun onPostExecute(result: Boolean) {
progressDialog.dismiss()// 关闭进度对话框
// 在这里提示下载结果
if (result) {
Toast.makeText(context, "Download succeeded", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, " Download failed", Toast.LENGTH_SHORT).show()
}
}

}

Service

创建

  1. 继承Service类;
  2. 按功能重写生命周期回调函数;
  3. 在AndroidManifest.xml文件中注册。

启动和停止

启动和停止借助Intent实现。

现在只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台之后,Service随时都有可能被系统回收。

如果你真的非常需要长期在后台执行一些任务,可以使用前台Service或者WorkManager。

区别:onCreate()方法是在Service第一次创建的时候调用的,而onStartCommand()方法则在每次启动Service的时候都会调用。

和Activity通信

使用binder类实现通信。

  1. 在service中创建binder类,根据自己的需要写方法,并实例化;
  2. 在service的onBinder函数中返回自己的实例化binder;
  3. 在Activity中借助Intent,使用bindService()绑定Service;
  4. 使用unbindService()可以解绑。

生命周期

63d2db246138ca89cd35f0285fb69d92

根据Android系统的机制,一个Service只要被启动或者被绑定了之后,就会处于运行状态,必须要让以上两种条件同时不满足,Service才能被销毁。所以,这种情况下要同时调用stopService()和 unbindService()方法,onDestroy()方法才会执行。

前台Service

前台Service和普通Service最大的区别就在于,它一直会有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。

前台Service的创建很大程度上和通知的创建类似,唯一不同是调用startForeground()进行通知显示。此外,前台Service需要进行权限声明。

IntentService

本质是简化Android多线程编程,提供的一个异步的、会自动停止的Service。

  1. 这里首先要求必须先调用父类的构造函数,并传入一个字符串,这个字符串可以随意指定,只在调试的时候有用;
  2. 实现onHandleIntent方法,处理耗时逻辑;
  3. 可以重写了onDestroy()方法,进行结尾工作。

[!CAUTION]

以下内容晦涩难懂,出于自己单方面理解的内容居多,不保证下面的理解是正确的。

如果你发现不正确的地方,可以在评论区指出。

Kotlin课堂

泛型的高级特性

主要学习泛型的实化、协变和逆变。

弄懂泛型之前,首先区别一件事情,一般的类型转化。

在进行非泛型的赋值时,子类对象可以赋值给父类对象的,但是父类对象不能赋值给子类对象。其实原因很好懂,子类包含比父类更多的信息和功能,子类赋值给父类无非就是将多余的信息和功能给禁用掉;相反,父类赋值给子类,则需要增添对于的信息和功能才行,很明显这是不可能的。同时,父类转向子类或者不同子类之间相互转换也是不被允许的(强制转换暂时不讨论)。

而在泛型类型中,Kotlin 不允许类型的替换或转换,因为泛型的类型在编译时已经是固定的,不会发生类型推断,因此不加特殊语法,不能实现协变和逆变的功能。

实化

Java的泛型擦除机制:即对于泛型的约束只在编译时期存在,运行的时候不进行约束(这是兼容性导致的)。所有基于JVM的语言,它们的泛型功能都是通过类型擦除机制来实现的,包括Kotlin。

简而言之,不允许将子类的泛型对象赋值给父类的泛型类型声明。

类型擦除就是在编译时期将泛型位置替换为泛型上界,如果没有设置泛型上界就替换为object。

由于Kotlin有内联函数,因此可以稍微改善泛型擦除机制的问题。使用reified关键字可以将泛型实化,通俗来讲就是将泛型类型使用传入的类型进行替换,更加偏重于内联的功能。

1
2
// 以下写法就是合理的
inline fun <reified T> getGenericType() = T::class.java

协变

协变通俗简短来讲就是允许子类对象给父类的泛型类型声明,同时设置为可读。

错误的根本原因:泛型不变性

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
class SimpleData<T> { 
private var data: T? = null

fun set(t: T?) {
data = t
}

fun get(): T? {
return data
}
}

fun main() {
val student = Student("Tom", 19)
val data = SimpleData<Student>()
data.set(student)

// 错误:无法将 SimpleData<Student> 传递给 SimpleData<Person>
// 在泛型类型中,Kotlin 不允许类型的替换或转换,因为泛型的类型在编译时已经是固定的,不会发生类型推断。
// 这是协变和逆变存在的原因
handleSimpleData(data) // 这里报错
val studentData = data.get()
}

// 这是协变不能写入的原因
// 光看这个函数,这是可行的。
// 但是传入的是SimpleData<Student>类型,只是屏蔽信息成为SimpleData<Person>类型
fun handleSimpleData(data: SimpleData<Person>) {
val teacher = Teacher("Jack", 35)
data.set(teacher) // 子类型之间强制转化失败,会报错
}

关键字是out

逆变

协变通俗简短来讲就是允许父类对象给子类的泛型类型声明,同时设置为可写。

错误的根本原因:泛型不变性

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
// 例子1:
interface Transformer<T> {
fun transform(t: T): String
}

fun main() {
val trans = object : Transformer<Person> {
override fun transform(t: Person): String {
return "${t.name} ${t.age}"
}
}
// 错误:无法将 SimpleData<Person> 传递给 SimpleData<Student>
// 在泛型类型中,Kotlin 不允许类型的替换或转换,因为泛型的类型在编译时已经是固定的,不会发生类型推断。
// 这是协变和逆变存在的原因
handleTransformer(trans) // 这行代码会报错
}

// 这是逆变不能写入的原因
// 光看这个函数,这是可行的。
// 但是传入的是SimpleData<Person>类型,只是假设成为SimpleData<Student>类型,因此是不可读的
fun handleTransformer(trans: Transformer<Student>) {
val student = Student("Tom", 19)
val result = trans.transform(student) // 这里是正确的,将子类型转化为父类型
}


// 例子2:
interface Transformer<T> {
fun transform(name: String, age: Int): T
}
fun main() {
val trans = object : Transformer<Person> {
override fun transform(name: String, age: Int): Person {
return Teacher(name, age)
}
}
handleTransformer(trans)
}
fun handleTransformer(trans: Transformer<Student>) {
val result = trans.transform("Tom", 19) // 强制转化,将Teacher转化为Student错误
}

关键字是in