Service

service是android实现程序后台运行的解决方案。
当然它并不是独立运行,而是依赖于应用程序,应用程序停止时,他也会一起停止。Service并不会自动开启线程,所有的代码都是默认运行在主线程当中的。

Android多线程编程

在kotlin中开启线程的方式更加简单。它为你定义了顶层方法thread{//方法体}直接在其中写内容,然后他会被拉起为一个新线程。

  1. 更新UI:和许多其他的GUI库一样,Android的UI也是线程不安全的。也就是说,如果想要更新应用程序里的UI元素,必须在主线程中进行,否则就会出现异常。
  2. 所以android提供了一套异步消息处理机制:解决了在子线程进行UI操作的问题
    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
    class MainActivity : AppCompatActivity() {
    val updateText = 1
    val handle = object :Handler(Looper.getMainLooper()){
    //在这里处理UI的异步操作
    override fun handleMessage(msg: Message) {
    when(msg.what){
    updateText ->{
    binding.textView.text = "Nice to meet you"
    }
    }
    }
    }
    private lateinit var binding: ActivityMainBinding
    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)//将message对象发送过去
    }
    }
    }
    }

异步消息处理机制原理

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

  1. Message
    Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间传递数据。上一小节中我们使用到了Message的what字段,除此之外还可以使用arg1和arg2字段来携带一些整型数据,使用obj字段携带一个Object对象。
  2. Handler
    Handler顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。发送消息一般是使用Handler的sendMessage()方法、post()方法等,而发出的消息经过一系列地辗转处理后,最终会传递到Handler的handleMessage()方法中。
  3. MessageQueue
    MessageQueue是消息队列的意思,它主要用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。
  4. Looper
    Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入一个无限循环当中,然后每当发现MessageQueue中存在一条消息时,就会将它取出,并传递到Handler的handleMessage()方法中。每个线程中只会有一个Looper对象。

使用AsyncTask

借助AsyncTask,即使你对异步消息处理机制完全不了解,也可以十分简单地从子线程切换到主线程
基本用法:AsyncTask是一个抽象类,我们需要去继承他,同时它需要接收三个参数:

  • Params,在执行时需要传入的参数,用于在后台任务中使用
  • Progress,在后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位
  • Result。当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
    样例:
    1
    2
    3
    class DownloadTask : AsyncTask<Unit, Int, Boolean>() {
    ...
    }
    常用重写的四个方法:
  1. onPreExecute()
    这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。

  2. doInBackground(Params…)
    这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成,就可以通过return语句将任务的执行结果返回,如果AsyncTask的第三个泛型参数指定的是Unit,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress (Progress…)方法来完成。

  3. onProgressUpdate(Progress…)
    当在后台任务中调用了publishProgress(Progress…)方法后,onProgressUpdate (Progress…)方法就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。

  4. onPostExecute(Result)
    当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据进行一些UI操作,比如说提醒任务执行的结果,以及关闭进度条对话框等。

简单来说,使用AsyncTask的诀窍就是,在doInBackground()方法中执行具体的耗时任务,在onProgressUpdate()方法中进行UI操作,在onPostExecute()方法中执行一些任务的收尾工作。

如果需要启动某个任务,就执行AsyncTask的execute()方法并传入任意数量的参数,这些参数将会传递到DownloadTask
的doInBackground()方法当中。

Service的基本用法

创建一个service,只需要这样即可


service常用的重写方法:
onCreate()方法会在Service创建的时候调用,
onStartCommand()方法会在每次Service启动的时候调用
onDestroy()方法会在Service销毁的时候调用。

启动和停止service都是通过intent来实现的。

Activity与Service进行通信

需要使用到service中的onBind()方法
在service中定义一个bind类继承自Binder:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//声明这个bind类并构建出来
private val mBinder = DownloadBinder()
class DownloadBinder : Binder() {
fun startDownload() {
Log.d("MyService", "startDownload executed")
}
fun getProgress(): Int {
Log.d("MyService", "getProgress executed")
return 0
}
}
//在与activity绑定时,将会返回这个创建的binder类
override fun onBind(intent: Intent?): IBinder {
return mBinder
}

在activity中提前预埋好用来存储的bind:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//用来存储从service中获取的binder类。
private lateinit var downloadBinder: MyService.DownloadBinder
//继承ServiceConnection接口,当连接建立时,将会调用onServiceConnected方法,连接关闭时将会调用onServiceDisconnected方法。
private var connect = object :ServiceConnection{
override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
downloadBinder = p1 as MyService.DownloadBinder
downloadBinder.startDownload()
downloadBinder.getProgress()
}

override fun onServiceDisconnected(p0: ComponentName?) {
TODO("Not yet implemented")
}

}

与按钮绑定相关连接事件:

1
2
3
4
5
6
7
binding.bindServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
bindService(intent,connect,Context.BIND_AUTO_CREATE)//绑定service
}
binding.unbindServiceBtn.setOnClickListener {
unbindService(connect) // 解绑Service
}

bindService接受三个参数,第一个就是intent,第二个放入连接时对象,当连接成功时就会调用其中的方法。,第三个参数则是一个标志位,这里传入BIND_AUTO_CREATE表示在Activity和Service进行绑定后自动创建Service。这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行。

这样子后,获取到activity就可以通过已经存储好的downloadBinder命令service完成任何需要完成的事。注意的是,不同的activity都可以获得这个binder对象。

Service的生命周期

  1. service只有在第一次被启动时,才会执行onCreate方法,然后执行onStartCommand()。其他每次执行都只会调用onStartCommand()方法。所以service只会有一个实例,所以只要你调用了stopself()或者stopService()都会直接停止。

  2. bindService()方法则会回调onBind()方法,如果service没有建立,就会调用一次onCreate()方法。从onBind()获取对象后就可以相互通信了。

  3. 当调用了startService()方法后,再去调用stopService()方法。这时Service中的onDestroy()方法就会执行,表示Service已经销毁了。类似地,当调用了bindService()方法后,再去调用unbindService()方法,onDestroy()方法也会执行,这两种情况都很好理解。但是需要注意,我们是完全有可能对一个Service既调用了startService()方法,又调用了bindService()方法的,在这种情况下该如何让Service销毁呢?根据Android系统的机制,一个Service只要被启动或者被绑定了之后,就会处于运行状态,必须要让以上两种条件同时不满足,Service才能被销毁。所以,这种情况下要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行。

前台Service

从Android 8.0系统开始,只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台之后,Service随时都有可能被系统回收。前台Service和普通Service最大的区别就在于,它一直会有一个正在运行的图标在系统的状态栏显示

构建前台service:
样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
override fun onCreate() {
super.onCreate()
Log.d("MyService", "onCreate executed")
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
val channel = NotificationChannel("my_channel", "前台service通知", NotificationManager.IMPORTANCE_DEFAULT)
manager.createNotificationChannel(channel)
}
val intent = Intent(this,MainActivity::class.java)
val pi = PendingIntent.getActivities(this,0, arrayOf(intent), PendingIntent.FLAG_IMMUTABLE)
val notification = NotificationCompat.Builder(this,"my_channel")
.setContentTitle("前台service通知")
.setContentText("炉知笔记")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pi)
.build()
startForeground(1,notification)
}

它与通知很像,但不同的是,它不是通过manager.notify()来启动。,而是调用了startForeground()方法
第一个参数是通知的id,类似于notify()方法的第一个参数;第二个参数则是构建的Notification对象。调用startForeground()方法后就会让MyService变成一个前台Service,并在系统状态栏显示出来。

而且前台service需要获取权限认可:

IntentService

它与service大差不差,属于service的一个子类。需要实现一个抽象方法onHandleIntent(),它会默认在子线程中运行。
这个抽象方法与startCommand一样,每次启动时都会调用