Android Service组件使用详解

279 阅读6分钟

Service使用介绍

Service 是Android 四大组件之一,拥有自己完整的生命周期,需要在AndroidManifest.xml中注册

  1. Service通常运行在当前宿主进程的主线程中,所以在Service中进行一些耗时操作就需要在Service内部开启线程去操作,否则会引发ANR异常;
  2. 不是一个单独的进程,除非在清单文件中声明时指定进程名,否则Service所在进程就是application所在进程;
<service android:name=".service.CustomService" android:process=":remote"/>
  1. Service 可以不依赖UI单独运行在系统中,启动Service用两种方式startService()、BindService(),需要注意点是:
    • 通过startService()方式启动后不用管启动它的组件是否销毁,只要service运行的进程存活,当前Service就可以一直运行,只有通过手动stopService()、或者在service中主动调用stopSelf()才会停止;
    • 通过bindService()方式与启动它的组件生命周期绑定,通过可以对外提供方法调用,同时也是IPC进程通信主要方式之一;

Service生命周期

class BinderPoolService: Service() {
    companion object {
        private const val TAG = "BinderPoolService"
    }
    override fun onCreate() {
        super.onCreate()
        Log.d(TAG, "onCreate")
    }
    /**
    * 返回值类型
    * START_STICKY_COMPATIBILITY, START_STICKY的兼容版,被kill后可能会重启
    * START_STICKY, 默认,kill后会被重启,但是重启后调用onStarfCommand()传进来的Intent参数可能为null,说明被kill的时候没有保存Intent
    * START_NOT_STICKY, kill之后不会被重启
    * START_REDELIVER_INTENT kill后会被重启,同时重启调用onStartCommand()时再次传入保存的Intent
    **/
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.d(TAG, "onStartCommand")
        return super.onStartCommand(intent, flags, startId)
    }
    override fun onBind(intent: Intent?): IBinder {
        Log.d(TAG, "onBind")
        return null
    }
    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroy")
    }
}
class CustomViewActivity:AppCompatActivity() {

    companion object{
        private const val TAG = "CustomViewActivity"
    }

    private val serviceConnection = object :ServiceConnection{
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            Log.d(TAG, "onServiceConnected")
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            Log.d(TAG, "onServiceDisconnected")
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.custom_activity_layout)
        Log.d("CustomViewActivity", "onCreate")
        
        findViewById<Button>(R.id.startService).setOnClickListener{
            startService(Intent(this, BinderPoolService::class.java))
        }

        findViewById<Button>(R.id.stopService).setOnClickListener{
            stopService(Intent(this, BinderPoolService::class.java))
        }

        findViewById<Button>(R.id.bindService).setOnClickListener{
            val intent = Intent(this, BinderPoolService::class.java)
            bindService(intent, serviceConnection, BIND_AUTO_CREATE)
        }

        findViewById<Button>(R.id.unbindService).setOnClickListener{
            unbindService(serviceConnection)
        }
    }
}

执行startService()多次生命周期如下,OnCreate()执行一次,onStartCOmmand()执行多次

//第一次执行startService()
14:34:04.379 5631-5631/? D/BinderPoolService: onCreate
14:34:04.380 5631-5631/? D/BinderPoolService: onStartCommand startId:1
//第2次执行startService()
14:34:05.558 5631-5631/? D/BinderPoolService: onStartCommand startId:2 
//执行stopService()
14:49:27.335 5822-5822/? D/BinderPoolService: onDestroy

设置前台服务

  1. 在Android O版本后Google对Service后台服务进行限制,切换到后台Service会被Kill掉,默认时间60s,应对策略通过设置为前台服务,并调用startForegroundService()启动服务;
//应用程序切换到后台后,如果未设置成前台服务,60s后被系统回收
14:35:07.633 1610-1625/? W/ActivityManager: Stopping service due to app idle: u0a73 -1m3s256ms com.malaysia.myapplication/.service.BinderPoolService
class BinderPoolService: Service() {
    companion object {
        private const val TAG = "BinderPoolService"
    }
    override fun onCreate() {
        super.onCreate()
        Log.d(TAG, "onCreate")
        val notificationManager = NotificationManagerCompat.from(this)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val notificationChannel = NotificationChannel(
                NOTIFICATION_CHANNEL_ID,
                "Service notification channel", NotificationManager.IMPORTANCE_DEFAULT
            )
            notificationManager.createNotificationChannel(notificationChannel)
        }
        val builder: NotificationCompat.Builder =
            NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("Service")
                .setContentText("Service is Running")
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            builder.setChannelId(NOTIFICATION_CHANNEL_ID)
        }
        startForeground(1, builder.build())
    }
}

//启动服务方式
findViewById<Button>(R.id.startService).setOnClickListener{
    val intent = Intent(this, BinderPoolService::class.java)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        startForegroundService(intent)
    } else {
        startService(intent)
    }
}

startService&BindService区别

在有多个Client端调用startServcie()时,只要其中一个Client调用stopService()后,服务就将停止,生命周期结束,这一点与bindService()有区别,bindService()后必须所有的Client都调用unbindService()服务才会停止;

  • 每一次调用 startService 都会回调onStartCommand,之后调用了stopService之后就会 destroy Service。即使有多个client启动服务,那调用一次stopService 就能 destroy Service 。通过这种方式还有一个好处就是Service可以通过调用 stopSelf 主动退出;
  • 第一次调用bindService 的时候才会回调 onBind,如果有多个client连接服务,在最后一个client调用unbindService时才会回调 onUnbind,并destroy Service;
//在MainActivity中startService
D/MainActivity: startService
//Service启动
D/BinderPoolService: onCreate
D/BinderPoolService: onStartCommand startId:1
//在CustomViewActivity中startService
D/CustomViewActivity: startService
D/BinderPoolService: onStartCommand startId:2
//在CustomViewActivity中stopService
D/CustomViewActivity: stopService
//Service停止
D/BinderPoolService: onDestroy
//在MainActivity中bindService
D/MainActivity: bindService
//Service启动,回调onBind(),只会回调一次
D/BinderPoolService: onCreate
D/BinderPoolService: onBind
D/MainActivity: onServiceConnected
//在CustomViewActivity中bindService
D/CustomViewActivity: bindService
D/CustomViewActivity: onServiceConnected
//在CustomViewActivity中unbindService
D/CustomViewActivity: unbindService
//在MainActivity中unbindService
D/MainActivity: unbindService
//Service停止
D/BinderPoolService: onDestroy

AIDL IPC跨进程通信

AIDL远程服务,理解为运行在另外一个进程中的Service,需设置AIDL接口对外暴露相关业务

基本使用方式

  1. 新建AIDL接口 2099385-d68440c5786c6047.webp
  2. 创建AIDL类,点击Sync\Rebuild Project生成对应AIDL实体类
import com.malaysia.myapplication.ITestRemoteCallback;
interface IBinderPool {
    //binder连接池,解决同一个Service对外提供不能服务,获取不同的binder服务
    IBinder queryBinder(int binderType);

    //解决Client、Service端双向通信问题,通过Callback进行回调,Service端采用RemoteCallbackList存储callback。同时可以用来处理Client、service死亡监听
    void register(ITestRemoteCallback callback);
    void unRegister(ITestRemoteCallback callback);

    void callServer(String msg);
}

interface IPay {
//  void buy();
  void buy(String product);
}

interface ISecurity {
    String encode(String pwd);
    String decode(String pwd);
}

interface ITestRemoteCallback {
    void onReceived(String msg);
}
  1. 在Service端实现AIDL对应的Stub类,实现IPay\ISecurity的接口方法,实现对外提供能力
import com.malaysia.myapplication.IPay
class PayImpl:IPay.Stub() {
    override fun buy() {
        Log.d("PayImpl","buy method invoke")
    }
}
import com.malaysia.myapplication.ISecurity

class SecurityImpl:ISecurity.Stub() {
    override fun encode(pwd: String?): String {

        return pwd.plus("-encode")
    }

    override fun decode(pwd: String?): String {
        return pwd.plus("-decode")
    }
}
class BinderPoolService : Service() {

    companion object {
        private const val TAG = "BinderPoolService"
        private const val NOTIFICATION_CHANNEL_ID = "1"
    }

    private val callbackList = RemoteCallbackList<ITestRemoteCallback>();

    override fun onCreate() {
        super.onCreate()
        // 发送通知,把service置于前台
        val notificationManager = NotificationManagerCompat.from(this)
        // 从Android 8.0开始,需要注册通知通道
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val notificationChannel = NotificationChannel(
                NOTIFICATION_CHANNEL_ID,
                "Service notification channel", NotificationManager.IMPORTANCE_DEFAULT
            )
            notificationManager.createNotificationChannel(notificationChannel)
        }
        val builder: NotificationCompat.Builder =
            NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("Service")
                .setContentText("Service is Running")
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            builder.setChannelId(NOTIFICATION_CHANNEL_ID)
        }
        startForeground(1, builder.build())
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.d(TAG, "onStartCommand startId:$startId   intent:$intent")
        return START_STICKY
    }

    private val mBinderDeathRecipient: IBinder.DeathRecipient = IBinder.DeathRecipient {
        Log.d(TAG, "service listen binderDied")
    }

    override fun onBind(intent: Intent?): IBinder {
        Log.d(TAG, "onBind")
        val binder = BinderPool()
        binder.linkToDeath(mBinderDeathRecipient, 1)
        return binder
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroy")
        job?.cancel()
    }

    private inner class BinderPool : IBinderPool.Stub() {
        override fun queryBinder(binderType: Int): IBinder? {
            var binder: Binder? = null
            when (binderType) {
                1 -> binder = PayImpl() //PayImpl继承了IPay.Stub, IPay.Stub继承了Binder
                2 -> binder = SecurityImpl()
            }
            return binder
        }

        override fun register(callback: ITestRemoteCallback?) {
            Log.d(TAG, "register callback from pid:${Binder.getCallingPid()}")
            callbackList.register(callback)
        }

        override fun unRegister(callback: ITestRemoteCallback?) {
            Log.d(TAG, "unRegister callback from pid:${Binder.getCallingPid()}")
            callbackList.unregister(callback)
        }

        override fun callServer(msg: String?) {
            Log.d(TAG, "callServer callback from pid:${Binder.getCallingPid()}")
            Log.d(TAG, "callServer msg:$msg")
        }
    }
}
  1. 新建项目创建Client连接AIDL Service,需将aidl拷贝到新建的项目对应目录中 image.png

  2. 在Client此时绑定服务只能通过隐式调用方式,同时需要在Service中AndroidManifest.xml声明对外提供能力属性,以及响应action

<service
    android:name=".service.BinderPoolService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="com.malaysia.myapplication.service.BinderPoolService"/>
    </intent-filter>
</service>
class MainActivity : AppCompatActivity() {

    companion object{
        private const val TAG = "MainActivity"
    }

    private lateinit var binding: ActivityMainBinding
    private var binderPool: IBinderPool? = null
    private var security:ISecurity?= null
    private var pay:IPay?=null
    private var mCountDownLatch:CountDownLatch? = null
    private var conn: ServiceConnection? = null

    private val remoteCallback by lazy {
        object : ITestRemoteCallback.Stub(){
            override fun onReceived(msg: String?) {
                Log.d(TAG, "ITestRemoteCallback onReceived:$msg")
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        WindowCompat.setDecorFitsSystemWindows(window, false)
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.bindService.setOnClickListener {
            bindService()
        }
        binding.register.setOnClickListener {
            binderPool?.register(remoteCallback)
        }
        binding.unRegister.setOnClickListener {
            binderPool?.unRegister(remoteCallback)
        }
        binding.encode.setOnClickListener {
            val encode = security?.encode("password")
            binding.encode.text = encode
            Log.d(TAG, "encode:$encode")
        }
        binding.decode.setOnClickListener {
            val decode = security?.decode("password")
            binding.decode.text = decode
            Log.d(TAG, "decode:$decode")
        }
        binding.pay.setOnClickListener {
            pay?.buy("apple")
        }
    }

    private fun bindService(){
        if(conn == null){
            conn = object : ServiceConnection{
                override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
                    binderPool = IBinderPool.Stub.asInterface(service)
                    security = ISecurity.Stub.asInterface(binderPool?.queryBinder(2))
                    pay = IPay.Stub.asInterface(binderPool?.queryBinder(1))
                    Log.d(TAG, "onServiceConnected binderPool:${binderPool}")
                    binderPool?.asBinder()?.linkToDeath(mBinderDeathRecipient, 1)
                }
                override fun onServiceDisconnected(name: ComponentName?) {
                    Log.d(TAG, "onServiceDisconnected")
                }
            }

            val intent = Intent()
            intent.action = "com.malaysia.myapplication.service.BinderPoolService"
            intent.setPackage("com.malaysia.myapplication")
            bindService(intent, conn!!, Context.BIND_AUTO_CREATE)
            Log.d(TAG, "bind service success")
        }else{
            Log.d(TAG, "have bind service success")
        }
    }

    private val mBinderDeathRecipient:DeathRecipient = DeathRecipient {
        Log.d(TAG, "client listen binderDied")
    }
}
  1. AIDL接口的函数都不支持重载,即函数名不能一样,即使函数参数个数不一样;
  2. AIDL接口传递的参数只有是基本数据类型、String 和CharSequence、List 和 Map、实现android.os.Parcelable 接口的类;
  3. 对于增删参数的接口:AIDL函数的访问会检测参数,Client有参数的接口可以调用Service的接口(不管有无参数),反过来,Client的接口没参数就只能调用Service没有参数的接口。比如我们的新接口定义函数添加了参数,那client必须同时或提前修改,不然我们发布了新接口的Service应用,Client就不能调用了,但是Client用新接口是可以去访问老接口的服务的

关于StartCommand返回值可参考
Service注意事项
Android进程间通信 深入浅出AIDL - 知乎 (zhihu.com)