Android Service复习

Service概念

Service是Android中的四大组件之一,和Activity一样继承自Context,但是Service没有UI界面,是可以在后台运行的应用组件。

分类

Service包括有不同的类型:前台Service,后台Service,绑定Service。

  • 前台Service:前台服务可以执行一些用户能够注意到的操作,例如音频播放器可以使用前台服务来播放音频文件,前台服务会显示通知,提示用户当前的服务正在执行。

  • 后台Service:后台Service可以在应用后台执行用户不会直接注意到的操作。

在Android 8.0之后的版本,系统对运行在后台的服务进行了限制,在部分情况下也可以使用JobScheduler推进后台工作进行。

需要注意的是,创建好的Service会在其托管的主线程中运行,不会去主动自己的线程,也不会在单独的进程中运行(除非在注册时指定独立进程)。因此,如果在Service中存在有耗时操作的话,则需要在Service中创建单独的线程来执行,否则会有ANR风险。

一、基本使用

1. 自定义Service类

首先我们先创建一个MainService类继承自Service,代码如下:

class MainService : Service() {

    override fun onCreate() {
        super.onCreate()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        return super.onStartCommand(intent, flags, startId)
    }


    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    override fun onDestroy() {
        super.onDestroy()
    }

}

上述代码中的四个方法基本上就是Service的核心方法了,在这些方法中能够对服务进行绑定,处理生命周期等操作,下面我们对这几个方法进行简单说明。

1. onCreate()

首先是onCreate方法,onCreate方法会在Service被首次创建时执行,在StartCommand()onBind()方法之前执行,执行一些Service启动之后的动作。

如果Service已经在运行状态了,那么即使再去调用startService()方法也不会再执行。

2. onStartCommand()

当其他的组件执行启动Service时,通过调用startService()方法来调用起此方法。

此方法执行后,Service会处在运行状态,在服务完成工作之后,我们可以主动去执行stopService()来停止服务。

如果是要实现另外的组件和当前Service绑定,则此方法可以不用实现,而是实现onBind()方法。

每次执行startService()方法都会调用到此方法。

需要注意的是此方法需要一个返回值,用来描述系统终止服务的情况下系统应该如何继续服务,返回值需要是如下的几个常量之一:

  • START_NOT_STICKY:被杀后不重启,不保持启动状态,可以随时停止

    这种模式是和启动一些在内存压力大时能够被停止的任务,如定时数据轮询。

  • START_STICKY:被杀后自动重启,保持启动状态,不保持Intent,会重新调用onStartCommand方法,无新Intent传入的话则为空Intent;杀死重启后,不继续执行先前的任务,能够接收新任务。

  • START_REDELIVER_INTENT:如果存在未处理完的Intent,则被杀死后会重启,并在重启后发送所有的Intent,stopSelf后释放持有的Intent;如果没有尚未处理完的Intent的,服务不会重启。

  • START_STICKY_COMPATIBILITY:START_STICKY的通用选项,该选项不能保证,服务被杀死后onStartCommand会被重新调用

3. onBind()

如果要实现其他组件和当前Service进行绑定,我们可以通过bindService()来进行绑定,同时此方法会被调用。

如果不需要绑定Service,此方法可以返回null。

4. OnDestory()

当服务可以被销毁时,此方法会被执行。

此方法是服务终止前的最后一个调用,因此需要在此方法内对Service内的资源进行释放,防止出现不安全隐患。

2. 声明

在创建了Service文件之后,我们Service还需要在AndroidManifest.xml文件中进行声明注册,在注册之后才能够进行使用,和Activity等其他组件是一样的,代码如下:

<manifest ... >
  ...
  <application ... >
        <service android:name=".service.MainService"/>
        ...
  </application>
</manifest>

Service可以在声明的额外的设置其他的属性,我们对常用的进行简单的说明:

android:exported="false"

此属性在四大组件中都可以设置,其作用是:是否支持其它应用调用当前组件。true为支持,false为不支持。

默认值:如果包含有intent-filter 默认值为true; 没有intent-filter默认值为false。

android:process=":xxx"

此属性是执行当前的组件运行在独立的进程中,进程名为xxx.

3. 服务启动

我们可以通过调用startService()来启动服务,由于在Android8.0之后,系统对于后台服务有很大的限制,所以可以调用startForgroundService()来创建一个服务并在前台执行,需要注意的是,如果执行了此方法,则需要在5s内在onCreate()方法中执行startService()方法,否则可能会ANR。

示例如下,我们首先创建一个服务MainService,代码如下:

package com.example.demowork1.service

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import com.example.common.util.LogUtil

class MainService : Service() {

    private val serviceID = 101

    override fun onCreate() {
        LogUtil.d("MainService onCreate")
        super.onCreate()
        setForegroundService()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        LogUtil.d("MainService onStartCommand")
        return super.onStartCommand(intent, flags, startId)
    }


    override fun onBind(intent: Intent?): IBinder? {
        LogUtil.d("onBind")
        return null
    }

    override fun onDestroy() {
        LogUtil.d("onDestroy")
        super.onDestroy()
    }


    /**
     * 将service设置为前台服务
     */
    private fun setForegroundService() {
        val channelId = "DemoWork1MainService"
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val manager =
                getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            var notificationChannel =
                manager.getNotificationChannel(channelId)
            if (notificationChannel == null) {
                notificationChannel =
                    NotificationChannel(channelId, channelId, NotificationManager.IMPORTANCE_HIGH)
                notificationChannel.vibrationPattern = LongArray(0)
                notificationChannel.setSound(null, null)
                manager.createNotificationChannel(notificationChannel)
            }
        }
        val builder =
            NotificationCompat.Builder(this, channelId)
        builder.setSound(null)
        builder.setVibrate(longArrayOf())
        startForeground(serviceID, builder.build())
    }

}

如上述代码,在OnCreate()方法中执行了startForground()方法,则我们可以在调用时执行如下代码打开Service:

            var intent = Intent()
            intent.setClass(this,MainService::class.java)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                startForegroundService(intent)
            }
            else{
                startService(intent)
            }

在启动Service时可以采用intent传输数据到Service中。

4. 服务停止

如果组件通过调用 startService(intent) (每次执行此方法都会调用 onStartCommand() )启动服务,则服务会一直运行,直到其使用 stopSelf() 自行停止运行,或由其他组件通过调用 stopService() 将其停止为止,而后系统会将服务销毁。

Service的回收规则如下:

  1. 如果将服务绑定到拥有前台焦点的 Activity,则它其不太可能会终止
  2. 被声明为前台运行的服务,也基本上不会被终止
  3. 如果服务已启动并长时间运行,则系统逐渐降低其在后台任务列表中的位置,而服务被终止的概率也会大幅提升

二、绑定服务

如果组件通过调用 bindService() 来创建服务,且不去调用 onStartCommand(),则服务只会在该组件与其绑定时运行。当该服务与其所有组件取消绑定后,系统便会将其销毁。

绑定示例代码如下,首先我们先创建绑定的Service——BinderService,需要对其的onBind()onUnbind()方法进行重写,逻辑如下:

package com.example.demowork1.service

import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import com.example.common.util.LogUtil
import kotlin.random.Random

class BinderService : Service() {
    private var binder: MyBinder = MyBinder()

    override fun onCreate() {
        super.onCreate()
        LogUtil.d("BinderService onCreate")
    }

    override fun onDestroy() {
        LogUtil.d("BinderService onDestroy")
        super.onDestroy()
    }

    override fun onBind(intent: Intent): IBinder {
        LogUtil.d("BinderService onBind")
        return binder
    }

    override fun onUnbind(intent: Intent?): Boolean {
        LogUtil.d("BinderService onUnbind")
        return super.onUnbind(intent)
    }

    fun getRandomNumber():Int{
        return Random(10).nextInt()
    }
}

class MyBinder : Binder() {
    fun getService(): BinderService {
        return BinderService()
    }
}

然后我们在Activity中进行绑定,首先我们需要定义一个ServiceConnection来连接绑定,如下:

    private val conn: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName, binder: IBinder) {
            isBound = true
            val myBinder = binder as MyBinder
            binderService = myBinder.getService()
            LogUtil.d("ActivityA onServiceConnected")
            val num: Int? = binderService?.getRandomNumber()
            LogUtil.d("ActivityA 中调用 TestService的getRandomNumber方法, 结果: $num")
        }

        override fun onServiceDisconnected(name: ComponentName) {
            isBound = false
            LogUtil.d("ActivityA onServiceDisconnected")
        }
    }

然后我们在Activity中定义绑定和解除绑定的方法,剩余代码如下:

class ServiceActivity : BaseActivity<ActivityServiceBinding>() {

    private var binderService: BinderService? = null

    private var isBound = false
  ...//ServiceConnection

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewBinding.btnOpenService.setOnClickListener {
            startService()
        }
        viewBinding.btnStopService.setOnClickListener {
            stopService()
        }
        viewBinding.btnBinderService.setOnClickListener {
            binderService()
        }
        viewBinding.btnUnBinderService.setOnClickListener {
            unBinderService()
        }
    }

    /**
     * 绑定服务
     */
    private fun binderService() {
        var bindIntent = Intent(this, BinderService::class.java)
        bindService(bindIntent, conn, Context.BIND_AUTO_CREATE)
    }

    /**
     * 解除Service绑定
     */
    private fun unBinderService() {
        unbindService(conn)
    }
  ...

    override fun initViewBinding() {
        viewBinding = ActivityServiceBinding.inflate(layoutInflater)
    }
}

生命周期

Service的生命周期主要是包括OnCreate()OnDestory(),无论是启动服务还是绑定服务这两个生命周期回调方法一定会走到。

上一张经典的Service的生命周期图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nzHNASMq-1644824723294)(https://upload-images.jianshu.io/upload_images/16647598-dbc04dc1a7330f10.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]

三、前台服务

前台服务是可以被用户注意到的服务,会在状态栏出显示通知,以便用户能够看到你的应用程序正在前台执行任务,如果不需要服务被用户注意到,可以使用WorkManager等后台任务来实现。

在Android8.0之后对于后台服务不再支持,启动服务时需要指定为前台服务。

1. 启动前台服务

startService(Intent intent)方法类似,启动前台Service使用startForgroundService(Intent intent)方法,代码演示见启动服务章节,对于Android系统在8.0之上需要调用启动前台服务的方法。

2. 注意事项

启动前台服务需要注意如下事项:

  1. 在启动了前台Service服务之后,还需要在Service的onCreate方法中执行startForeground(serviceID, builder.build())方法,否则可能会直接ANR。

  2. 需要注意的是如果系统的版本是Android 9 或者更高的版本需要为应用添加前台服务权限,否则会报错,权限项如下:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

总结

本文为Service基本概念和使用的复习总结。

猜你喜欢

转载自blog.csdn.net/cat_is_so_cute/article/details/122925937