第10章:Service
10.1 Service的概念
Service用来使程序在后台运行。它依赖于创建Service时所在的应用程序进程。当某个应用进程被杀掉时,所有依赖于该进程的Service都会停止。
所有代码默认运行在主线程中,Service不会自动开启线程。我们需要在Service内部手动创建子线程,并在这里执行具体任务。
10.2 Android多线程编程
Java多线程编程中,当执行一些耗时操作时,如果不将其放在子线程里运行,就会导致主线程被阻塞(例如发起一条网络请求,而网速过慢时)。
10.2.1 线程的基本用法
有这么几种和java离类似的语法。
/* 第一种:继承自Thread类 */
class MyThread: Thread {
override fun run() {
// 编写具体逻辑
}
}
// 启动线程
MyThread.start()
/* 第二种:实现Runnable接口 */
class MyThread: Runnable {
override fun run() {
// 编写具体逻辑
}
}
// 启动线程
val myThread = MyThread()
Thread(myThread).start()
/* 第三种:使用Lambda */
Thread {
// 编写具体逻辑
}.start()
Kotlin提供了一种很简单的开启线程的方式:
thread {
// 编写具体逻辑
}
10.2.2 在子线程中更新UI
Android的UI是线程不安全的。如果要更新UI元素,必须在主线程中更新。如果直接在子线程中更新UI,则会直接崩溃。这时就需要异步消息处理机制了!
线程不安全:在没有提供加锁机制保护的情况下,多个线程对共享资源进行写操作,可能导致数据不一致或脏数据的问题。如果多个线程同时修改同一份资源,可能会出现竞态条件,导致程序功能不能正确完成。
class MainActivity: AppCompatActivity() {
lateinit var binding : ActivityMainBinding
val updateText = 1 // 用于表示TextView这个动作
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.changeTextBtn.setOnClickListener {
thread {
val msg = Message()
msg.what = updateText
handle.sendMessage(msg) // 发出消息
}
}
}
val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) { // 接收到消息后做什么
// 进行UI操作
when (msg.what) {
binding.updateText -> textView.text = "Nice to meet you"
}
}
}
}
10.2.3 异步消息处理机制的原理
-
Message:
Message是在线程之间传递的消息,可以携带少量信息。可以使用Message的what字段;使用arg1字段和arg2字段携带整形数据;使用obj字段携带一个Object对象。 -
Handler:
主要用于发送和处理消息的。发送是Handler.sendMessage();发出的消息最后会传递到Handler的handleMessage()方法中 -
MessageQueue:
消息队列,存放所有通过Handler发送的消息。每个线程中只会有一个MessageQueue对象。 -
Looper:
调用Looper的loop()方法后会进入一个循环,每当发现MessageQueue中存在一条消息就会将其取出,并传递到handleMessage()方法中。每个线程中只会有一个Looper对象。
10.2.3 AsyncTask
AsncTask有三个泛型参数:
- Params:在执行AsyncTask时需要传入的参数,可用于在后台任务中使用;
- Progress:后台任务执行时,如果需要在界面上显示当前进度,则使用这里指定的类型作为进度单位;
- Result:后台任务执行完毕后如果需要返回结果,则指定这里的泛型作为返回值类型;
经常需要重写的方法有一下四个:
onPreExecyte()
:后台任务开始执行前调用,用于初始化。例如显示一个进度条对话框等;doInBackground(Params...)
:此方法中所有代码都会在子线程中运行,应该在这里进行耗时任务。但不能进行UI更新。更新UI需要调用publishProgress(Progress...)
方法。onProgressUpdate(Progress...)
:调用publishProgress(Progress...)
方法后,onProgressUpdate()
会很快被调用。该方法中携带的参数就是后台任务中传递过来的。在此方法中可以对UI进行更新。onPostExecute(Result)
:当后台任务执行完毕并通过return语句进行返回时,这个方法很快会被调用。返回的数据会作为参数传递到此方法中,可以进行一些UI操作。
简单来说就是:在doInBackground(Params...)
中执行具体的耗时任务;在onProgressUpdate(Progress...)
中进行UI操作;在onPostExecute(Result)
中执行一些任务的收尾工作。
想要启动任务,只需调用.execute()
方法即可。
10.3 Service的基本用法
10.3.1 定义Service
class MyService : Service() {
// 用Activity控制Service
override fun onBind(intent: Intent): IBinder {
TODO("Return the communication channel to the service.")
}
// 第一次创建Service时执行
override fun onCreate() {
super.onCreate()
}
// 每次运行Service时执行
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return super.onStartCommand(intent, flags, startId)
}
// 关闭Service时执行
override fun onDestroy() {
super.onDestroy()
}
}
10.3.2 启动和停止Service
通过startService()
和stopService()
方法传入intent来启动和停止Service:
binding.startServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
startService(intent)
}
binding.stopServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
stopService(intent)
}
10.3.3 Activity和Service通信
借助onBind()
方法可以通过Activity来指挥Service。
修改MyService.kt中的代码,添加一个下载功能:
class MyService : Service() {
private val mBinder = DownloadBinder()
class DownloadBinder: Binder() {
// 开始下载
fun startDownload() {
Log.d("MainActivity", "StartDownload")
}
// 查看下载进度
fun getProcess(): Int {
Log.d("MainActivity", "getProcess executed.")
return 0
}
}
override fun onBind(intent: Intent): IBinder {
return mBinder
}
}
接着在MainActivity中分别设置两个将Activity与Service绑定/解绑的按钮:
class MainActivity : AppCompatActivity() {
...
lateinit var downloadBinder: MyService.DownloadBinder
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
downloadBinder = service as MyService.DownloadBinder // 向下转型
downloadBinder.startDownload()
downloadBinder.getProcess()
}
override fun onServiceDisconnected(name: ComponentName?) {
}
}
override fun onCreate(savedInstanceState: Bundle?) {
...
binding.startServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
bindService(intent, connection, Context.BIND_AUTO_CREATE) // 绑定Service
}
binding.stopServiceBtn.setOnClickListener {
unbindService(connection) // 解绑Service
}
}
}
其中bindService(intent, connection, flags)
方法接收三个参数。第三个参数是标志位,Context.BIND_AUTO_CREATE
表示在Activity和Service绑定后自动创建Service,即执行MyService中的onCreate。
10.4 Service的生命周期
在任何位置调用Context的startService()
方法,就会调用onStartCommand()
方法。若这个Service还未被创建过,那么会先执行onCreate()
方法。
每个Service只会存在一个实例,因此无论调用多少次startService()
,只需调用一次stopService()
或者stopSelf()
,Service就会停止。
另外,还可以调用Context的bindService()
来获取一个Service的持久连接,这时会回调Service中的onBind()
方法。若这个Service还未被创建过,那么会先执行onCreate()
方法。
调用stopService()
和unBindService()
都会执行onDestroy()
。如果既调用了startService()
方法,又调用了bindService()
,那么根据Android机制,一个Service只要被启动或者被绑定了之后都会处于运行状态。因此此时想要结束Service,就要同时调用stopService()
和unBindService()
方法。
10.5 Service的更多技巧
10.5.1 前台Service
普通Servic随时可能会被系统回收。如果希望Service保持2运行,就可以考虑使用前台Service。前台Service和普通Service的最大区别就是,它会一直有一个正在运行的图标在系统的状态栏显示。
创建一个前台Service:
override fun onCreate() {
super.onCreate()
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channel = NotificationChannel("my_service", "前台Service通知", NotificationManager.IMPORTANCE_DEFAULT)
manager.createNotificationChannel(channel)
val intent = Intent(this, MainActivity::class.java)
val pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
val notification = NotificationCompat.Builder(this, "my_service")
.setContentTitle("This is content title")
.setContentText("This is content text")
.setSmallIcon(R.drawable.small_icon)
.setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.large_icon))
.setContentIntent(pi)
.build()
startForeground(114514, notification) // startForeground(id, Notification); 调用此方法使MyService成为前台Service
}
同时需要在AndroidManifest.xml中声明相关权限:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
书上只声明了第一行,这是使用前台Service的权限。但是如果不声明第二行的通知权限,应用就无法发布通知。因此必须添加这行代码之后,再在手机设置中讲ServiceTest的通知权限打开(如果不声明,设置中该应用的通知权限无法更改)。
10.5.2 使用IntentService
注意:该方法现已被废弃。最好使用
WorkManager
。
前面说过Service中如果处理一些耗时逻辑,很容易导致ANR (Application Not Responding) 的情况。此时就需要用到多线程编程的技术,开启一个子线程:
class MyService : Service() {
...
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
thread {
// 处理具体逻辑
}
return super.onStartCommand(intent, flags, startId)
}
}
如果想要实现让Service在执行完毕后自动停止,应该在thread里加一个stopSelf()
方法:
class MyService : Service() {
...
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
thread {
// 处理具体逻辑
stopSelf()
}
return super.onStartCommand(intent, flags, startId)
}
}
但是总有人会忘记开启子线程,或者忘记调用stopSelf()
方法。怎么办呢?
Android专门提供了一个IntentService
类:
class MyIntentService : IntentService("MyintentService") { // 这个字符串可以随意制定,只在调试时有用
override fun onHandleIntent(intent: Intent?) {
Log.d("MyIntentService", "Thread id is ${Thread.currentThread().name}")
}
override fun onDestroy() {
super.onDestroy()
Log.d("MyIntentService", "onDestroy")
}
}
这样就可以集开启线程和自动停止于一身。