美文网首页
Android的后台服务Service

Android的后台服务Service

作者: 超级绿茶 | 来源:发表于2020-01-30 16:58 被阅读0次

Android和其它现代的操作系统一样,除了有前台的图形化界面外还有悄悄运行的后台服务程序。最常见的有各种自动下载并安装的升级程序、消息收发程序,也包括那些音乐播放程序。后台服务以组件的形式参与到应用程序的运作当中。

有点需要注意的是这里的“后台”是从用户交互的角度来说(有界面,有交互的称为前台),这并不意味后台服务是运行在独立的线程上面,而是运行在当前程序所在的主线程上,所以在服务中执行耗时操作的话仍会造成主线程的阻塞

如何创建Service

创建后台服务的流程大致如下:

  1. 自定义一个类并继承Service。
  2. 重写Service的相关方法。
  3. 在AndroidManifest.xml对定义的Service类进行注册。
  4. 通过startService或bindService启动服务。

自定义Service类

从菜单依次选择file > new > service > service,然后会弹出创建Service的对话框


create_service
  • Class Name:要创建的自定义Service的类名。这里使用默认的类名。
  • Exported:是否允许被其它的应用程序调用这个Service,建议把勾去掉。
  • Enabled:是否允许实例化这个Service,默认为勾选。
  • Soruce Language:选择使用什么语言创建Service类。默认就用Kotlin吧。

在按需操作后点Finish按钮就创建了一个自定义的Service类。由于是通过Android Studio创建的,所以在AndroidManifest.xml里的注册也顺带完成了,如果是用Eclipse的话,Service类的创建和注册就只能手动操作了。现在来一下AndroidManifest.xml里的注册内容

<service
    android:name=".MyService"
    android:enabled="true"
    android:exported="false"></service>

可以看出注册的内容和之前的对话框是一致的。而我们自定义的Service类的内容如下:

import android.app.Service
import android.content.Intent
import android.os.IBinder

class MyService : Service() {

    override fun onBind(intent: Intent): IBinder {
        TODO("Return the communication channel to the service.")
    }
}

MyService类里面只有一个重写方法onBind,这个方法只在绑定启动时才有用。目前我们已经有了个自定义的Service类,也已经在AndoridManifest里注册了这个组件。现在就轮到该怎么启动这个Service服务了。

启动方式和生命周期

和Activity一样Service也有自己的生命周期和自己的启动方式。Service的启动方式可以分为:普通启动startService和绑定启动bindService

  • 普通启动:通过Context的startService方法启动,启动后的Service服务是独立运行,不和其它组件产生关联。
  • 绑定启动:通过Context的bindService方法启动,启动后的Service可以和指定的组件进行绑定,并和组件实现数据的交互。

由于启动方式不同,Service的生命周期也不同。以下是官方的生命周期图


Service的生命周期

从图可知,普通启动(startService)时生命周期是先走onCreate方法,再走onStartCommand方法,然后在结束时走onDestroy方法。

如果是绑定启动(bindService)时先走onCreate方法,再走onBind方法,结束时会先走onUnbind方法再去onDestroy方法。

这里要说明一下的是在Android 6.0之前用startService启动的服务在运行完onStartCommand方法后是不会结束的,必须调用stopService或stopSelf方法才能结束掉服务。但从Android 7.0开始只要Service所在的进程切换到后台一段时间后(大约一分钟)就会自动结束并响应onDestroy方法。

普通启动startService

以下代码现实了一个以startService方式启动的服务,代码中在相应的生命周期方法内插入了Log以便于观察:

class MyService : Service() {
    override fun onCreate() {
        super.onCreate()
        Log.i("123", "onCreate $this")
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.i("123", "onStartCommand $this")
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.i("123", "onDestory $this")
    }

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

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 点击按钮后以启动MyService        
        btnStarService.setOnClickListener {
            startService(Intent(this, MyService::class.java))
        }
    }
}

如何在手机上查看运行中的服务

启动后的服务可以在“开发者模式”的“正在运行的服务”中看到,如图:


开发者模式>正在运行的服务 开发者模式>正在运行的服务

启动后的服务有个特点:当服务创建成功并进入了存活期时,服务是以单例形式存在的,也就是说在这时候再次创建的话是不会有新的实例出现的,无论创建多少次都只会是同一个实例。我们可以通过观察Log的输出得到验证:

Log输出结果
从第一个onCreate方法到onDestory方法之间的MyService都是同一个实例,在onDestroy销毁服务后再次创建的MyService实例已经是一个新的了,所以又开启了新一轮的生命周期。

onStartCommand方法

onStartCommand方法有如下两个特点:

  1. 属于普通启动(startService)时的生命周期方法。
  2. 服务处于存活期时可被多次调用,所以在不建议在此方法中申请资源或开启线程。

方法声明如下:

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int
  • 参数intent:主要作用存取参数,启动前将参数存入,再在此出取出。
  • 参数flags:主要作用是在服务重启时如何附加参数,这个参数是一个常量值。
  • 参数startId:服务的唯一值,在每次调用这个方法时都会生成一个新的值。

flags的常量值如下:

  • START_FLAG_REDELIVERY:表示服务在被杀后Intent对象不丢失。
  • START_FLAG_RETRY:表示服务被杀后又重启了,但Intent对象丢失。

onStartCommand方法的返回值主要用于指定服务被杀死后的重启方法。这个值是一个Int类型的常量值,取值范围如下:

  • START_STICKY:服务被杀后尝试重启并再次调用onStartCommand方法,但传入的Intent对象为null。
  • START_NOT_STICKY:服务被杀后不再重启。
  • START_REDELIVER_INTENT:服务被杀后尝试重启,并能重传Intent对象。
  • START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,服务被杀后尝试重启。

结束服务stopService或stopSelf

以普通方式启动的服务在运行完onStartCommand方法后是不会结束的,也就意味着服务所占的资源也无法释放。所以在我们用代码去结束服务。结束服务的方法无非就两个,一个是stopService,另一个是stopSelf。两者的作用一样,只是stopSelf适合在服务内部使用,方便自己结束自己。而stopService适合在服务之外通过传Intentl对象来结束服务。

绑定启动bindService

如果组件需要和服务交互的话就需要用到绑定启动,然后在组件销毁的时候再解绑服务,被解绑的服务也会随之结束。(这里说的组件基本上就是Activity)

在使用绑定启动服务之前需要先创建一个可以被绑定的自定义Service类

import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import android.util.Log

class MyBindService : Service() {
    private lateinit var binder: MyBinder
    override fun onCreate() {
        super.onCreate()
        Log.i("123", "onCreate")
        binder = MyBinder() // 实例化Binder对象
    }

    /**
     * 此方法用于和组件绑定,绑定完成时会把Binder实例交给组件(MainActivity)
     */
    override fun onBind(intent: Intent): IBinder {
        Log.i("123", "onBind")
        return binder
    }

    /**
     * 解绑服务
     */
    override fun onUnbind(intent: Intent?): Boolean {
        val result = super.onUnbind(intent)
        Log.i("123", "onUnbind $result")
        return result
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.i("123", "onDestroy")
    }

    /**
     * 自定义Binder类,在这个类模拟音乐播放器的功能
     * 在这个类里面定义的方法是给组件(MainActivity)调用的
     */
    class MyBinder() : Binder() {
        fun playMusic(): String {
            Log.i("123", "playMusic")
            return "开始播放音乐"
        }

        fun stopMusic(): String {
            Log.i("123", "stopMusic")
            return "停止播放音乐"
        }
    }
}

这里除了自定义的Service外还定义了一个继承自Binder的静态内部类MyBinder,MyBinder会在onCreate方法中实例化,然后在服务和组件绑定后由onBind方法返回给组件,组件通过该类的实例和服务进行互通。

接着还需要定义一个实现了ServiceConnection接口的类,这个类的主要作用是在组件在绑定和解绑时使用,绑定时可以通过onServiceConnection方法获取到服务类定义的Binder对象实例。为了便于演示,我们将这个类放在MainActivity内,以静态内部类的形式存在:

import android.app.Service
import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    private var conn: MyServiceConnection? = null
    private var binder: MyBindService.MyBinder? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btnStarService.setOnClickListener {
            startService(Intent(this, MyService::class.java))
        }
        btnStopService.setOnClickListener {
            stopService(Intent(this, MyService::class.java))
        }

        // 绑定启动服务
        btnBindService.setOnClickListener {
            conn = MyServiceConnection().apply {
                bindService(
                    Intent(this@MainActivity, MyBindService::class.java),
                    this,
                    Service.BIND_AUTO_CREATE
                )
            }
        }

        // 解绑服务
        btnUnbindService.setOnClickListener {
            conn?.let { unbindService(it) }
        }

        //模拟播放音乐的功能
        btnPlay.setOnClickListener {
            binder?.let {
                val result = it.playMusic()
                Toast.makeText(this, result, Toast.LENGTH_SHORT).show()
            }
        }

        //模拟停止音乐的功能
        btnStop.setOnClickListener {
            binder?.let {
                val result = it.stopMusic()
                Toast.makeText(this, result, Toast.LENGTH_SHORT).show()
            }
        }
    }

    /**
     * 当组件绑定或解绑服务时使用
     */
    inner class MyServiceConnection : ServiceConnection {
        override fun onServiceDisconnected(name: ComponentName?) {
            Log.i("123", "onServiceDisconnected")
        }

        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            Log.i("123", "onServiceConnected")
            binder = service as MyBindService.MyBinder
        }
    }
}

上述代码中我们用绑定启动的方式来模拟了一个音乐播放器,只要绑定成功后可以通过点击按钮来播放音乐或停止音乐(只是弹一句话示例下)。绑定的方法就是bindService,方法声明如下:

public boolean bindService(Intent service, ServiceConnection conn, int flags)

第一个参数是Intent类型,用于指定要启动的服务,跟startActivity或startService的用法一样。第二个参数就是一个ServiceConnection的实例。第三个参数用于指定服务是启动模式,这里直接使用BIND_AUTO_CREATE就可以了。

需要说明下:当一个服务被绑定后再次调用bindService方法的话,只有onServiceConnected方法会被响应。这跟onStartCommand方法被多次响应的原理是一样的。

解绑服务unbindService

unbindService方法用于解除一个已经绑定的服务,解绑服务并不一定会结束服务,因为服务是可以被多个组件绑定的,只有当所有的组件都解绑后服务才会结束。

如果对一个服务即startService启动又用bindService绑定,那在结束时也需要用stopService和unbindService才能完全销毁掉。

IntentService

很多情况下我们用Service的目的是为了实现异步操作,在服务中开启一个子线程,并在线程上执行耗时操作,当操作完成后再调用stopSelf结束服务。对于这种套路化的操作Android也有相应的封装类,就是IntentService。IntentService继承自Service,在内部通过Thread+Handler的组合实现了一个可以异步操作的工作队列,IntentService在使用上也非常简单,而且当队列中的异步操作全部完成后服务会自动结束,不用调用stopSelf。

IntentService的定义如下:

public abstract class IntentService extends Service {
    // 省略部分代码 
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}

通过定义可以看出IntentService是一个抽象类,有一个抽象方法onHandleIntent,而且这个方法的注释已经标明此方法是运行在子线程上的。

创建IntentService的方法和创建普通Service一样。
可以点击菜单File > New > Service > Service(IntentService),然后在弹出的对话框里直接点OK就可以了。为了便于演示直接创建了一个MyIntentService类:

import android.app.IntentService
import android.content.Context
import android.content.Intent
import android.util.Log

class MyIntentService : IntentService("MyIntentService") {

    private var count = 0

    override fun onHandleIntent(intent: Intent?) {
        Log.i("123", "第${count}调用开始 ThreadName:${Thread.currentThread().name}")
        Thread.sleep(1000)
        Log.i("123", "第${count}调用结束")
        count++
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.i("123", "onDestroy")
    }

    companion object {
        /**
         * 服务的启动方法
         */
        @JvmStatic
        fun startAction(context: Context) {
            val intent = Intent(context, MyIntentService::class.java)
            context.startService(intent)
        }
    }
}

在MainActivity新加一个按钮,实现点击一次就启动一次服务的效果。

btnIntentService.setOnClickListener { MyIntentService.startAction(this) }

上述示例在onHandleIntent方法中先让线程延时一秒,再对count进行累加,然后我们多点几下按钮,观察Log输出信息:


Log输出

根据Log信息可以看出IntentService是以队列方式处理多个异步操作的,服务存活时所有的调用操作都在队列中排队执行,当队列中的任务全部完成时就自动结束服务,所以onDestroy方法会得到响应。

前台服务

由于Service服务是一种后台程序,运行时没有任务界面,除了从开发者模式中查看外根本无法知道服务是否还在运行或已经被系统杀死。所以Android从7.0开始出现了一种新的概念,称为前台服务。特点是这种服务拥有和前台进程相同的优先级,而且在存活时会在顶部用通知栏的形式显示。
从Android 8.0开始使用前台服务需要在AndroidManifest.xml文件里注册权限

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

下面的代码演示怎么自定义前台服务:

import android.app.*
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.os.IBinder
import androidx.core.app.NotificationCompat

class MyForegroundService : Service() {
    override fun onCreate() {
        super.onCreate()
        val intent = Intent(this, MainActivity::class.java)
        val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
        val channelId = "001"
        //适配Android 8.0
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            val mChannel = NotificationChannel(channelId, "test", NotificationManager.IMPORTANCE_HIGH)
            val ntfManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            ntfManager.createNotificationChannel(mChannel)
        }
        val notification = NotificationCompat.Builder(this, channelId)
            .setContentTitle("前台服务通知的标题")
            .setContentText("前台服务通知的文字")
            .setContentIntent(pendingIntent)
            .setWhen(System.currentTimeMillis())
            .setSmallIcon(R.mipmap.ic_launcher)
            .setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher))
            .build()
        startForeground(1, notification)
    }

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

MainActivity中的代码如下:

btnForegroundService.setOnClickListener {
    val intent = Intent(this, MyForegroundService::class.java)
    // 适配Android 8.0
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        startForegroundService(intent)
    } else {
        startService(intent)
    }
}

前台服务在创建时需要定义一个通知栏,可以给这个通知设置一个点击并跳转的Activity,在设置通知时要注意版本的适配问题,最后调用startForeground方法将服务和通知关联起来。最后通过startForegroundService或startService启动服务(根据API版本决定用哪个)。

源代码下载 ServiceDemo.rar

传送门-Android的后台服务Service

点击链接加入QQ群聊:https://jq.qq.com/?_wv=1027&k=5z4fzdT
或关注微信公众号:口袋里的安卓

相关文章

网友评论

      本文标题:Android的后台服务Service

      本文链接:https://www.haomeiwen.com/subject/bnwpthtx.html