Service简介
Service是Android中实现程序后台运行的解决方案,适用于执行不需要和用户交互且要求长期运行的任务。
- Service的运行不依赖任何用户界面
- 当某个进程结束的时候,依赖于该进程的Service也会停止
- Service并不会自动开启线程
Android 多线程编程
线程的基本用法
继承自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()
thread{
//具体操作
}
在子线程中更新UI
因为Android的UI是线程不安全的,所以想要更行应用程序里的UI元素,必须要在主线程中进行。
如果尝试直接在子线程中更新的话
mainBinding.changeTextBtn.setOnClickListener {
thread {
mainBinding.textView.text = "Nice"
}
}
点击按钮后就会产生如下报错
异步消息处理机制
如果有些时候必须在子线程里执行一些耗时任务,然后根据任务的执行结果来更新相应的UI控件,就可以使用异步消息处理机制。
class MainActivity : AppCompatActivity() {
private lateinit var mainBinding: ActivityMainBinding
val updateText = 1 //用于表示更新textView的动作
val handler = object : Handler(Looper.getMainLooper()){
override fun handleMessage(msg: Message) {
when(msg.what){
//如果Message的what字段等于updateText,就更新UI
updateText -> mainBinding.textView.text = "Nice"
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
mainBinding.changeTextBtn.setOnClickListener {
thread {
//在子线程中创建一个what值为updateText的Message对象并发送给Handler
val msg = Message()
msg.what = updateText
handler.sendMessage(msg)
}
}
}
}
解析异步消息处理机制
组成部分
Android中的异步消息处理主要由4个部分组成:
- Message
Message是在线程之间传递的消息,它可以在内部携带少量的信息, 用于在不同线程之间传递数据 - Handler
Handler主要是用于发送和处理消息的。发送消息一般是使用Handler的sendMessage()
方法、post()
方法等,而发出的消息经过一系列地辗转处理后,最终会传递到Handler的handleMessage()
方法中。 - MessageQueue
MessageQueue主要用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。 - Looper
Looper是每个线程中的MessageQueue的管家,调用Looper的loop()
方法后,就会进入一个无限循环当中,然后每当发现MessageQueue中存在一条消息时,就会将它取出,并传递到Handler的handleMessage()
方法中。每个线程中只会有⼀个Looper对象。
处理流程
- 需要在主线程当中创建一个Handler对象,并重写
handleMessage()
方法。 - 当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler将这条消息发送出去。
- 这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息。
- 最后分发回Handler的
handleMessage()
方法中。由于Handler的构造函数中我们传入Looper.getMainLooper()
,所以此时handleMessage()
方法中的代码也会在主线程中运行,在这里就可以进行UI操作了。
使用AsyncTask
AsyncTask是一个抽象类,在使用时需要创建一个子类去继承他。在继承时可以为他指定3个泛型参数。
- Params:在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。
- Progress:在后台任务执行时,如果需要在界面上显示当前的进度, 则使用这里指定的泛型作为进度单位。
- Result:当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
在进行实际的操作前,还需要重写AsyncTask中的几个方法才能完成对任务的定制。
onPreExecute()
:这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。doInBackground(Params...)
:这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理 所有的耗时任务。任务一旦完成,就可以通过return
语句将任务的执行结果返回,如果AsyncTask的第三个泛型参数指定的是Unit,就可以不返回任务执行结果。注意:在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用。publishProgress (Progress...)
方法来完成。onProgressUpdate(Progress...)
:当在后台任务中调用了publishProgress(Progress...)
方法后,onProgressUpdate (Progress...)
方法就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。onPostExecute(Result)
:当后台任务执行完毕并通过return
语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据进行一些UI操作,比如说提醒任务执行的结果,以及关闭进度条对话框等。
较完整的自定义AsyncTask形式如下:
class DownloadTask : AsyncTask<Unit,Int,Boolean>() {
/**
* 在后台任务开始之前调用,用于执行一些界面上的初始化操作
* eg:显示一个进度条对话框
*/
override fun onPreExecute() {
}
/**
* 在子线程中处理的耗时任务。完成后通过return语句将执行结果返回
* eg:执行具体的下载任务
*/
override fun doInBackground(vararg params: Unit?): Boolean? {
return true
}
/**
* 更新UI
*/
override fun onProgressUpdate(vararg values: Int?) {
}
/**
* 后台任务执行完毕后调用
*/
override fun onPostExecute(result: Boolean?) {
}
}
启动任务语句:
DownloadTask().execute()
Service的基本用法
定义
在项目目录上右击→New→Service→Service 就可以新建一个Service。
class MyService : Service() {
override fun onBind(intent: Intent?): IBinder? {
TODO("Not yet implemented")
}
/**
* 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()
}
}
Service需要在AndroidManifest.xml中注册才能生效。
其中,exported属性表示是否将这个Service暴露给外部其他程序访问;enabled属性表示是否启用这个Service。
启动和停止
通过设置点击事件和Log日志,可以观察到Service的方法调用
class MainActivity : AppCompatActivity() {
private lateinit var mainBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
mainBinding.startBtn.setOnClickListener {
val intent = Intent(this,MyService::class.java)
startService(intent) //启动Service
}
mainBinding.stopBtn.setOnClickListener {
val intent = Intent(this,MyService::class.java)
stopService(intent) //停止Service
}
}
}
在第一次点击启动按钮时,同时调用onCreat()
和onStartCommand()
方法,但再次点击时,无论几次,就只调用onStartCommand()
方法。点击停止按钮时,调用onDestory()
方法。
与Activity间的通信
通过Service中的onBind()
方法可以实现在Avtivity中调用Service的方法。
首先在MyService中新建一个继承自Binder
的DownloadBinder
类,创建实例后再onBind()
方法中返回。
class MyService : Service() {
private val TAG = "MyService.out"
private val mBinder = DownloadBinder()
class DownloadBinder : Binder(){
fun startDownload(){
Log.d("DownloadBinder.out","startDownload()")
}
fun getProgress() : Int{
Log.d("DownloadBinder.out","getProgress()")
return 0
}
}
override fun onBind(intent: Intent?): IBinder? {
return mBinder
}
override fun onCreate() {
super.onCreate()
Log.d(TAG,"onCreate()")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG,"onStartCommand()")
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG,"onDestroy()")
}
}
然后,在MainActivity中创建ServiceConnection的匿名类实现
class MainActivity : AppCompatActivity() {
private lateinit var mainBinding: ActivityMainBinding
lateinit var downloadBinder : MyService.DownloadBinder
private val connection = object : ServiceConnection{
/**
* Activity和Service成功绑定时调用
*/
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
downloadBinder = service as MyService.DownloadBinder
downloadBinder.startDownload()
downloadBinder.getProgress()
}
/**
* Service的创建京城崩溃或被杀掉时调用
*/
override fun onServiceDisconnected(name: ComponentName?) {
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
mainBinding.bindServiceBtn.setOnClickListener {
val intent = Intent(this,MyService::class.java)
// BIND_AUTO_CREATE表示在Activity和Service绑定厚自动创建Service
bindService(intent,connection,Context.BIND_AUTO_CREATE) //绑定Service
}
mainBinding.unbindServiceBtn.setOnClickListener {
unbindService(connection) //解绑Service
}
}
}
分别点击绑定和解绑按钮
可以看到在MainActivity中调用了MyService中的方法。
使用前台Service
从Android 8.0系统开始,只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台之后,Service随时都有可能被系统回收。
前台Service一直会有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,类似于通知的效果。
由于状态栏中一直有一个正在运行的图标,相当于应用以另外一种形式保持在前台可见状态,所以系统不会倾向于回收前台Service。另外,用户也可以通过下拉状态栏清楚地知道当前什么应用正在运行,因此也不存在某些恶意应用长期在后台偷偷占用手机资源的情况。
只需要修改MyService中onCreate()方法就能实现
override fun onCreate() {
super.onCreate()
Log.d(TAG,"onCreate()")
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
val channel = NotificationChannel(
"MyService","前台Service通知",NotificationManager.IMPORTANCE_DEFAULT)
manager.createNotificationChannel(channel)
}
val intent = Intent(this,MainActivity::class.java)
val pi = PendingIntent.getActivity(this,0,intent,0)
val notification = NotificationCompat.Builder(this,"my_service")
.setContentTitle("This is content title")
.setContentText("This is content text")
.setSmallIcon(R.drawable.ic_small)
.setLargeIcon(BitmapFactory.decodeResource(resources,R.drawable.ic_small))
.setContentIntent(pi)
.build()
startForeground(1,notification)
}
还需要在AndroidManifest.xml
文件中进行权限声明
在运行是点击开启Service按钮后下拉通知栏就能看到提示了。
使用IntentService
一个标准的Service在使用时,需要在每个具体的方法里开启一个子线程用于处理一些耗时的逻辑,并且在任务执行完成后要注意调用stopService()
。
而使用IntentService类则没有这样的问题。
新建一个MyIntentService
class MyIntentService : IntentService("MyIntentService") {
private val TAG = "MyIntentService.out"
/**
* 处理耗时操作 运行在子线程中
*/
override fun onHandleIntent(intent: Intent?) {
TODO("Not yet implemented")
Log.d(TAG,"Thread id is ${Thread.currentThread().name}")
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG,"onDestroy()")
}
}
写好后在MainActivity中添加一个开启IntentService的点击事件
mainBinding.startIntentServiceBtn.setOnClickListener {
Log.d("MainActivity.out","Thread id is ${Thread.currentThread().name}")
val intent = Intent(this,MyIntentService::class.java)
startService(intent)
}
点击按钮后查看日志它会在执行完任务后自动结束。