Service详解_绑定服务实现

作者: 一笑小先生 | 来源:发表于2019-01-09 14:58 被阅读2次

本篇文章主要讲解Service绑定服务的实现方式以及三种绑定方法。

1. Service绑定服务

绑定服务是Service的另一种使用方式,当Service处于绑定状态时,其代表着客户端-服务器接口中的服务器。当其他组件(如 Activity)绑定到服务时(有时我们可能需要从Activity组建中去调用Service中的方法,此时Activity以绑定的方式开启Service后,我们就可以轻松地方法到Service中的指定方法),组件(如Activity)可以向Service(也就是服务端)发送请求,或者调用Service(服务端)的方法,此时被绑定的Service(服务端)会接收信息并响应,甚至可以通过绑定服务进行执行进程间通信 (即IPC)。

与启动服务不同的是绑定服务的生命周期通常只在为其他应用组件(如Activity)服务时处于活动状态,不会无限期在后台运行,也就是说宿主(如Activity)解除绑定后,绑定服务就会被销毁。那么在提供绑定的服务时,该如何实现呢?实际上我们必须提供一个 IBinder接口的实现类,该类用以提供客户端用来与服务进行交互的编程接口,该接口可以通过三种方法定义接口:

  • 扩展 Binder 类
  • 使用 Messenger
  • 使用 AIDL

下面👇,我们将对这三种方式进行详细的介绍:

1.1 扩展Binder类

如果服务是提供给自有应用专用的,并且Service(服务端)与客户端相同的进程中运行(常见情况),则应通过扩展 Binder 类并从 onBind() 返回它的一个实例来创建接口。客户端收到 Binder 后,可利用它直接访问 Binder 实现中以及Service 中可用的公共方法。如果我们的服务只是自有应用的后台工作线程,则优先采用这种方法。 不采用该方式创建接口的唯一原因是,服务被其他应用或不同的进程调用。

其使用开发步骤如下

  1. 创建BindService服务端,继承自Service并在类中,创建一个实现IBinder 接口的实例对象并提供公共方法给客户端调用
  2. 从 onBind() 回调方法返回此 Binder 实例。
  3. 在客户端中,从 onServiceConnected() 回调方法接收 Binder,并使用提供的方法调用绑定服务

注意:此方式只有在客户端和服务位于同一应用和进程内才有效,如对于需要将 Activity 绑定到在后台播放音乐的自有服务的音乐应用,此方式非常有效。

以下是一个扩展 Binder 类的实例,先看看Service端的实现:

class LocalService : Service() {

    companion object {
        const val TAG = "LocalService"
    }

    private lateinit var thread: Thread
    private var quit = false
    private var count = 0

    override fun onBind(intent: Intent?): IBinder? = LocalBinder()

    inner class LocalBinder : Binder() {
        // 声明一个方法,getService。(提供给客户端调用)
        // 返回当前对象LocalService,这样我们就可在客户端端调用Service的公共方法了
        val service: LocalService = this@LocalService
    }

    override fun onCreate() {
        super.onCreate()
        Log.i(TAG, "Service is invoke Created")
        thread = Thread(Runnable {
            while (!quit) {
                try {
                    Thread.sleep(1000)
                } catch (e: Exception) {
                    e.printStackTrace()
                }
                count++
            }
        })
        thread.start()
    }

    fun getCount(): Int = count

    override fun onUnbind(intent: Intent?): Boolean {
        Log.i(TAG, "Service is invoke onUnbind")
        return super.onUnbind(intent)
    }

    override fun onDestroy() {
        super.onDestroy()
        this.quit = true
        Log.i(TAG, "Service is invoke onDestroy")
    }
}

LocalService类继承自Service,在该类中创建了一个LocalBinder继承自Binder类,LocalBinder中声明了一个getService方法,客户端可访问该方法获取LocalService对象的实例,只要客户端获取到LocalService对象的实例就可调用LocalService服务端的公共方法,如getCount方法,值得注意的是,我们在onBind方法中返回了binder对象,该对象便是LocalBinder的具体实例,而binder对象最终会返回给客户端,客户端通过返回的binder对象便可以与服务端实现交互。接着看看客户端BindActivity的实现:

class BindActivity : AppCompatActivity() {

    private lateinit var conn: ServiceConnection
    private var localService: LocalService? = null

    companion object {
        const val TAG = "BindActivity"
    }

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

        conn = object : ServiceConnection {
            override fun onServiceDisconnected(name: ComponentName?) {
                localService = null
            }

            override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
                Log.d(TAG, "绑定成功调用:onServiceConnected")
                localService = (service as LocalService.LocalBinder).service
            }

        }

        val intent = Intent(this, LocalService::class.java)

        bindServiceBtn.setOnClickListener {
            Log.d(TAG, "绑定调用:bindService")
            bindService(intent, conn, Context.BIND_AUTO_CREATE)
        }

        unBindServiceBtn.setOnClickListener {
            Log.d(TAG, "解除绑定调用:unbindService")
            localService?.let {
                localService = null
                unbindService(conn)
            }
        }

        getCountBtn.setOnClickListener {
            Log.i(TAG, localService?.getCount().toString())
        }
    }
}

在客户端中我们创建了一个ServiceConnection对象,该代表与服务的连接,它只有两个方法, onServiceConnected和onServiceDisconnected,其含义如下:

  • onServiceConnected(ComponentName name, IBinder service)
    系统会调用该方法以传递服务的 onBind() 方法返回的 IBinder。其中service便是服务端返回的IBinder实现类对象,通过该对象我们便可以调用获取LocalService实例对象,进而调用服务端的公共方法。而ComponentName是一个封装了组件(Activity, Service, BroadcastReceiver, or ContentProvider)信息的类,如包名,组件描述等信息,较少使用该参数。
  • onServiceDisconnected(ComponentName name)
    Android 系统会在与服务的连接意外中断时(例如当服务崩溃或被终止时)调用该方法。

注意:当客户端取消绑定时,系统“绝对不会”调用该方法。

在onServiceConnected()被回调前,我们还需先把当前Activity绑定到服务LocalService上,绑定服务是通过通过bindService()方法,解绑服务则使用unbindService()方法,这两个方法解析如下:

  • bindService(Intent service, ServiceConnection conn, int flags)
    该方法执行绑定服务操作,其中Intent是我们要绑定的服务(也就是LocalService)的意图,而ServiceConnection代表与服务的连接,它只有两个方法,前面已分析过,flags则是指定绑定时是否自动创建Service。0代表不自动创建、BIND_AUTO_CREATE则代表自动创建。
  • unbindService(ServiceConnection conn)
    该方法执行解除绑定的操作。

Activity通过bindService()绑定到LocalService后,ServiceConnection#onServiceConnected()便会被回调并可以获取到LocalService实例对象mService,之后我们就可以调用LocalService服务端的公共方法了,最后还需要在清单文件中声明该Service。

运行程序,点击绑定服务并多次点击绑定服务接着多次调用LocalService中的getCount()获取数据,最后调用解除绑定的方法移除服务,其结果如下:

01-08 15:30:18.059 24842-24842/com.wangyy.service I/LocalService: Service is invoke onCreate
01-08 15:30:18.059 24842-24842/com.wangyy.service I/LocalService: Service is invoke onBind
01-08 15:30:18.060 24842-24842/com.wangyy.service I/BindActivity: 绑定成功调用:onServiceConnected
01-08 15:30:21.123 24842-24842/com.wangyy.service I/BindActivity: 3
01-08 15:30:22.384 24842-24842/com.wangyy.service I/BindActivity: 4
01-08 15:30:25.607 24842-24842/com.wangyy.service I/BindActivity: 7
01-08 15:30:28.159 24842-24842/com.wangyy.service I/LocalService: Service is invoke onUnbind
01-08 15:30:28.160 24842-24842/com.wangyy.service I/LocalService: Service is invoke onDestroy

通过Log可知,当我们第一次点击绑定服务时,LocalService服务端的onCreate()、onBind方法会依次被调用,此时客户端的ServiceConnection#onServiceConnected()被调用并返回LocalBinder对象,接着调用LocalBinder#getService方法返回LocalService实例对象,此时客户端便持有了LocalService的实例对象,也就可以任意调用LocalService类中的声明公共方法了。

值得注意的是,我们多次调用bindService方法绑定LocalService服务端,而LocalService得onBind方法只调用了一次,那就是在第一次调用bindService时才会回调onBind方法。接着我们点击获取服务端的数据,从Log中看出我们点击了3次通过getCount()获取了服务端的3个不同数据,最后点击解除绑定,此时LocalService的onUnBind、onDestroy方法依次被回调,并且多次绑定只需一次解绑即可。此情景也就说明了绑定状态下的Service生命周期方法的调用依次为onCreate()、onBind、onUnBind、onDestroy。ok~,以上便是同一应用同一进程中客户端与服务端的绑定回调方式。

1.2 使用 Messenger

Messenger可以翻译为信使,通过它可以在不同的进程中共传递Message对象(Handler中的Messager,因此 Handler 是 Messenger 的基础),在Message中可以存放我们需要传递的数据,然后在进程间传递。如果需要让接口跨不同的进程工作,则可使用 Messenger 为服务创建接口,客户端就可利用 Message 对象向服务发送命令。同时客户端也可定义自有 Messenger,以便服务回传消息。这是执行进程间通信 (IPC) 的最简单方法,因为 Messenger 会在单一线程中创建包含所有请求的队列,也就是说Messenger是以串行的方式处理客户端发来的消息,这样我们就不必对服务进行线程安全设计了。

Messenger 使用的主要步骤:

  1. 服务实现一个 Handler,由其接收来自客户端的每个调用的回调
  2. Handler 用于创建 Messenger 对象(对 Handler 的引用)
  3. Messenger 创建一个 IBinder,服务通过 onBind() 使其返回客户端
  4. 客户端使用 IBinder 将 Messenger(引用服务的 Handler)实例化,然后使用Messenger将 Message 对象发送给服务
  5. 服务在其 Handler 中(在 handleMessage() 方法中)接收每个 Message

以下是一个使用 Messenger 接口的简单服务示例,服务端进程实现如下:

class MessengerService : Service() {

    companion object {
        const val MSG_SAY_HELLO = 1
        const val TAG = "MessengerService"
    }

    /**
     * 创建Messenger并传入Handler实例对象
     */
    private val messenger: Messenger = Messenger(IncomingHandler())

    /**
     * 用于接收从客户端传递过来的数据
     */
    class IncomingHandler : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                MSG_SAY_HELLO -> {
                    Log.i(TAG, "thanks,Service had receiver message from client!")
                }
                else -> {
                    super.handleMessage(msg)
                }
            }
        }
    }

    /**
     * 当绑定Service时,该方法被调用,将通过mMessenger返回一个实现
     * IBinder接口的实例对象
     */
    override fun onBind(intent: Intent?): IBinder? {
        Log.i(TAG, "Service is invoke onBind")
        return messenger.binder
    }

    override fun onCreate() {
        super.onCreate()
        Log.i(TAG, "Service is invoke onCreate")
    }

    override fun onUnbind(intent: Intent?): Boolean {
        Log.i(TAG, "Service is invoke onUnbind")
        return super.onUnbind(intent)
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.i(TAG, "Service is invoke onDestroy")
    }
}

首先我们同样需要创建一个服务类MessengerService继承自Service,同时创建一个继承自Handler的IncomingHandler对象来接收客户端进程发送过来的消息并通过其handleMessage(Message msg)进行消息处理。接着通过IncomingHandler对象创建一个Messenger对象,该对象是与客户端交互的特殊对象,然后在Service的onBind中返回这个Messenger对象的底层Binder即可。下面看看客户端进程的实现:

class MessengerActivity : AppCompatActivity() {

    private lateinit var conn: ServiceConnection
    private var mService: Messenger? = null

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

        conn = object : ServiceConnection {
            override fun onServiceDisconnected(name: ComponentName?) {
                mService = null
            }

            override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
                mService = Messenger(service)
            }

        }

        val intent = Intent(this, MessengerService::class.java)

        bindServiceBtn.setOnClickListener {
            bindService(intent, conn, Context.BIND_AUTO_CREATE)
        }

        unBindServiceBtn.setOnClickListener {
            mService?.let {
                mService = null
                unbindService(conn)
            }
        }

        sendMsgBtn.setOnClickListener {
            sayHello()
        }
    }

    private fun sayHello() {
        mService?.let { messenger ->
            val msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0)
            try {
                messenger.send(msg)
            } catch (e: Exception) {
                e.printStackTrace()
            }

        }
    }
}

在客户端进程中,我们需要创建一个ServiceConnection对象,该对象代表与服务端的链接,当调用bindService方法将当前Activity绑定到MessengerService时,onServiceConnected方法被调用,利用服务端传递给来的底层Binder对象构造出与服务端交互的Messenger对象,接着创建与服务交互的消息实体Message,将要发生的信息封装在Message中并通过Messenger实例对象发送给服务端。关于ServiceConnection、bindService方法、unbindService方法,前面已分析过,这里就不重复了,最后我们需要在清单文件声明Service和Activity,由于要测试不同进程的交互,则需要将Service放在单独的进程中,因此Service声明如下:

<service
    android:name=".MessengerService"
    android:process=":remote" />

其中android:process=":remote"代表该Service在单独的进程中创建

接着多次点击绑定服务,然后发送信息给服务端,最后解除绑定,Log打印如下:

01-08 18:04:13.753 26092-26092/com.wangyy.service:remote I/MessengerService: Service is invoke onCreate
01-08 18:04:13.754 26092-26092/com.wangyy.service:remote I/MessengerService: Service is invoke onBind
01-08 18:04:21.214 26092-26092/com.wangyy.service:remote I/MessengerService: thanks,Service had receiver message from client!
01-08 18:04:21.429 26092-26092/com.wangyy.service:remote I/MessengerService: thanks,Service had receiver message from client!
01-08 18:04:23.069 26092-26092/com.wangyy.service:remote I/MessengerService: Service is invoke onUnbind
01-08 18:04:23.071 26092-26092/com.wangyy.service:remote I/MessengerService: Service is invoke onDestroy

通过上述例子可知Service服务端确实收到了客户端发送的信息,而且在Messenger中进行数据传递必须将数据封装到Message中,因为Message和Messenger都实现了Parcelable接口,可以轻松跨进程传递数据。

以上的例子演示了如何在服务端解释客户端发送的消息,但有时候我们可能还需要服务端能回应客户端,这时便需要提供双向消息传递了,下面就来实现一个简单服务端与客户端双向消息传递的简单例子。

先来看看服务端的修改,在服务端,我们只需修改IncomingHandler,收到消息后,给客户端回复一条信息。

class IncomingHandler : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                MSG_SAY_HELLO -> {
                    Log.i(TAG, "thanks,Service had receiver message from client!")
                    //回复客户端信息,该对象由客户端传递过来
                    val client = msg.replyTo
                    //获取回复信息的消息实体
                    val replyMsg = Message.obtain(null, MSG_SAY_HELLO)
                    val bundle = Bundle()
                    bundle.putString("reply", "ok~,I had receiver message from you! ")
                    replyMsg.data = bundle
                    try {
                        client.send(replyMsg)
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }

                }
                else -> {
                    super.handleMessage(msg)
                }
            }
        }
    }

接着修改客户端,为了接收服务端的回复,客户端也需要一个接收消息的Messenger和Handler,其实现如下:

 private val mReceiverReplyMsg = Messenger(ReceiverReplyMsgHandler())

    class ReceiverReplyMsgHandler : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                MessengerService.MSG_SAY_HELLO -> {
                    Log.i(TAG, "receiver message from service:" + msg.data.getString("reply"))
                }
                else -> {
                    super.handleMessage(msg)
                }
            }

        }
    }

除了添加以上代码,还需要在发送信息时把接收服务器端的回复的Messenger通过Message的replyTo参数传递给服务端,以便作为同学桥梁,代码如下:

private fun sayHello() {
        mService?.let { messenger ->
            val msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0)
            msg.replyTo = mReceiverReplyMsg
            try {
                messenger.send(msg)
            } catch (e: Exception) {
                e.printStackTrace()
            }

        }
    }

ok~,到此服务端与客户端双向消息传递的简单例子修改完成,我们运行一下代码,看看Log打印,如下:

2019-01-08 21:46:13.498 4259-4259/com.wangyy.service:remote I/MessengerService: Service is invoke onCreate
2019-01-08 21:46:13.500 4259-4259/com.wangyy.service:remote I/MessengerService: Service is invoke onBind
2019-01-08 21:46:50.758 4259-4259/com.wangyy.service:remote I/MessengerService: thanks,Service had receiver message from client!
2019-01-08 21:46:50.782 3736-3736/com.wangyy.service I/MessengerActivity: receiver message from service:ok~,I had receiver message from you! 
2019-01-08 21:52:11.365 4259-4259/com.wangyy.service:remote I/MessengerService: Service is invoke onUnbind
2019-01-08 21:52:11.367 4259-4259/com.wangyy.service:remote I/MessengerService: Service is invoke onDestroy

由Log可知,服务端和客户端确实各自收到了信息,到此我们就把采用Messenge进行跨进程通信的方式分析完了,最后为了辅助大家理解,这里提供一张通过Messenge方式进行进程间通信的原理图:


messenger_process.png

1.3 使用 AIDL

由于Messenger是以串行的方式处理客户端发来的消息,如果当前有大量消息同时发送到Service(服务端),Service仍然只能一个个处理,这也就是Messenger跨进程通信的缺点了,因此如果有大量并发请求,Messenger就会显得力不从心了,这时AIDL(Android 接口定义语言)就派上用场了, 但实际上Messenger 的跨进程方式其底层实现 就是AIDL,只不过android系统帮我们封装成透明的Messenger罢了 。因此,如果我们想让服务同时处理多个请求,则应该使用 AIDL。 在此情况下,服务必须具备多线程处理能力,并采用线程安全式设计。使用AIDL必须创建一个定义编程接口的 .aidl 文件。Android SDK 工具利用该文件生成一个实现接口并处理 IPC 的抽象类,随后可在服务内对其进行扩展。

这部分主要是跨进程通信的内容,在此不过多的讲解,后面会有专门的文章讲述AIDL。

关于绑定服务的注意点
  1. 多个客户端可同时连接到一个服务。不过,只有在第一个客户端绑定时,系统才会调用服务的 onBind() 方法来检索 IBinder。系统随后无需再次调用 onBind(),便可将同一 IBinder 传递至任何其他绑定的客户端。当最后一个客户端取消与服务的绑定时,系统会将服务销毁(除非 startService() 也启动了该服务)。
  2. 通常情况下我们应该在客户端生命周期(如Activity的生命周期)的引入 (bring-up) 和退出 (tear-down) 时刻设置绑定和取消绑定操作,以便控制绑定状态下的Service,一般有以下两种情况:
  • 如果只需要在 Activity 可见时与服务交互,则应在 onStart() 期间绑定,在 onStop() 期间取消绑定。
  • 如果希望 Activity 在后台停止运行状态下仍可接收响应,则可在 onCreate() 期间绑定,在 onDestroy() 期间取消绑定。
  1. 通常情况下(注意),切勿在 Activity 的 onResume() 和 onPause() 期间绑定和取消绑定,因为每一次生命周期转换都会发生这些回调,这样反复绑定与解绑是不合理的。此外,如果应用内的多个 Activity 绑定到同一服务,并且其中两个 Activity 之间发生了转换,则如果当前 Activity 在下一次绑定(恢复期间)之前取消绑定(暂停期间),系统可能会销毁服务并重建服务,因此服务的绑定不应该发生在 Activity 的 onResume() 和 onPause()中。
  2. 我们应该始终捕获 DeadObjectException DeadObjectException 异常,该异常是在连接中断时引发的,表示调用的对象已死亡,也就是Service对象已销毁,这是远程方法引发的唯一异常,DeadObjectException继承自RemoteException,因此我们也可以捕获RemoteException异常。
  3. 应用组件(客户端)可通过调用 bindService() 绑定到服务,Android 系统随后调用服务的 onBind() 方法,该方法返回用于与服务交互的 IBinder,而该绑定是异步执行的。

相关文章

  • Service详解_绑定服务实现

    本篇文章主要讲解Service绑定服务的实现方式以及三种绑定方法。 1. Service绑定服务 绑定服务是Ser...

  • 多线程2

    基础知识 绑定服务是Service类的实现,可让其他应用与其绑定和交互。要提供服务绑定,您必须实现onBind()...

  • Android - Service(四)之 进程内通信

    绑定式服务(Bound Service) 应用组件通过 bindService() 方法来绑定服务,服务只有在...

  • Android - Service(五)之 使用 AIDL 进行

    绑定式服务(Bound Service) 应用组件通过 bindService() 方法来绑定服务,服务只有在...

  • Android基础12

    service:服务。 开始服务 停止服务 通信,绑定活动 通信,解绑活动 新建service 布局文件 开始服务...

  • Android 面试题之Service(不断更新)

    Service系列问题 Service本地服务及生命周期详解 1.1 注册Service需要注意什么 Servic...

  • Android Service学习(二)

    本篇主要讲述service的绑定状态及如何设计前台service。 一、综述 绑定服务允许本APP内的activi...

  • k8s service

    Service代理: kube-proxy组件详解 Kubernetes service只是把应用对外提供服务的方...

  • 很高兴能再次遇见您——service

    只有 Activity、服务和内容提供程序可以绑定到服务 — 您无法从广播接收器绑定到服务。 1:Service和...

  • 绑定服务Bound Service

    两块板砖A,S 1、去五金商店买502胶水2、将胶水涂在S的表面上3、将A粘在S有胶水的表面上(此时A和S之间有胶...

网友评论

    本文标题:Service详解_绑定服务实现

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