前言:四大组件中的service是其中除了activity之外用得最多的可能就是它了,当然,其他两个组件有它们自己的应用场合,这个在每个应用中使用情况可能不同,需要根据应用的需要选择使用相应的组件来完成任务。这篇文章将介绍如何绑定一个服务Service,使得客户端和Service进行通讯。
一、绑定Service
绑定一个服务,首先需要定义一个类继承系统的Service基类,然后必须重写onBind()
方法并返回一个IBinder对象给客户端使用,这个返回的IBinder对象作为这个Service的代理对象,通过它可以使得客户端和这个服务进行通讯。通常,多个不同的客户端可以同时绑定这服务,但是需要注意的是onBind()
方法并不会在每次客户端绑定服务的时候都会调用,它只会在第一个客户端绑定这个服务的时候调用,所以,如果是多个客户端绑定相同的服务,那么只会有一个相同的IBinder对象。因此,绑定服务关键在于定义一个自己的IBinder对象,然后在onBind()
方法中返回给客户端使用。下面我们就做关键的一步,创建IBinder对象。
有以下三种方式可以得到IBinder对象:
- 继承Binder类:这种方式适用于你的服务只有你自己的应用或者和你的这app在相同的进程里面的客户端使用,如果要让不同进程的应用使用你的服务,那么这种方式就行不通了。
- 使用messenger:这种方式适用于你的服务需要在不同的进程间通讯(IPC),但这种方式有一个缺点就是所有处理请求(消息)都是在单一的线程中(没必要再将你的service设置成线程安全的了),也就是说不能处理多线程并发的情况。
- 使用AIDL:使用AIDL实现绑定服务就是为了解决上面两种情况的弊端,一个是service不能在进程间通讯,另一个就是service不能处理多线程的情况,但是,你的应用除非必须要使用AIDL,否则上面两种可以满足需求的情况下就没必要使用AIDL(消耗系统资源)。
1. 继承Binder类
要绑定一个服务(和服务通讯),那么客户端需要得到service的一个实例(引用)或者一个接口,我们知道在客户端使用了bindService()
绑定了一个服务后系统会调用该服务的onBind()
方法(第一次绑定该服务)返回一个IBinder对象给客户端,那么我们可以通过这个IBinder对象(一种通讯接口)和service进行交互(通讯)。通常我们在自定义的Binder类中定义一些public方法,这样,客户端可以通过这个方法获取service的实例或者做其他的一些事情,下面,我们通过一个例子来理解这种方法绑定服务。
LocalService.java
package com.example.lt.boundservice;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.Nullable;
public class LocalService extends Service{
private String[] names = {"吕布","赵子龙","关羽"};
private IBinder myBinder = new MyBinder();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return myBinder;
}
public class MyBinder extends Binder{
public LocalService getService(){
return LocalService.this;
}
}
public String getName(int postion){
return names[postion];
}
}
客户端端绑定服务:
package com.example.lt.boundservice;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private LocalService localService;
private TextView textView;
private boolean mBound;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
}
public void getName(View view){
if(mBound) {
textView.setText(localService.getName(1));
}
}
@Override
protected void onStart() {
super.onStart();
// 绑定服务
Intent intent = new Intent(MainActivity.this,LocalService.class);
mBound = bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 这里的service就是onBind返回的那个IBinder对象
System.out.println("ComponentName:"+name);
LocalService.MyBinder myBinder = (LocalService.MyBinder) service;
localService = myBinder.getService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
System.out.println("onServiceDisconnected");
System.out.println("ComponentName:"+name);
}
};
@Override
protected void onStop() {
super.onStop();
unbindService(conn);
}
}
这里需要注意的是绑定服务可能失败,所以要在确认绑定服务成功的情况下使用service,这种通过绑定启动一个服务的方式会将service和客户端(这里是activty)绑定在一起,所以需要在合适的时间绑定和取消绑定service(管理service的生命周期)。
2. 使用Messenger
如果你的service需要在不同的进程间通讯,那么你可以使用Messenger去提供一个接口给你的service,使得你的service可以在不同的进程间通讯(IPC)。
通常,可以分为如下几个步骤来使用Messenger绑定服务:
- 在你的service里面提供一个Handler,这个Handler用来处理客户端发送过来的消息(响应);
- 创建这个Handler的对象,并用这个Handler对象创建一个Messenger对象(这个Messenger对象持有handler的引用);
- 在onBind()方法中返回这个Messenger对象创建的IBinder对象;
- 客户端绑定服务得到返回的IBinder对象,并用这个对象在客户端初始化Messenger对象,这个对象持有service的Handler对象的引用,这样客户端通过这个Messenger对象发送消息给service。
按照上面的步骤,咋们来写个测试代码来实践一下。由于是测试在进程间进行通讯,所以,我们需要创建两个项目(应用),一个包含那个远程服务,一个用来访问那个远程服务。
创建两个项目(应用),一个叫RemoteService,一个叫RemoteClient
(1)在RemoteService项目中创建远程服务RemoteService
package com.example.lt.messengerdemo;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.support.annotation.Nullable;
import android.widget.Toast;
public class RemoteService extends Service{
private static final int SAY_HELLO = 0;
@Nullable
@Override
public IBinder onBind(Intent intent) {
// 3. 通过Messenger对象返回一个IBinder对象
return mMessenger.getBinder();
}
/**
* 1. 创建一个Handler用来接收,处理客户端的消息
*/
final Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case SAY_HELLO:
Toast.makeText(RemoteService.this,"hello,I am RemoteService",Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
};
/**
* 2. 通过这个Handler创建一个Messenger对象
*/
final Messenger mMessenger = new Messenger(mHandler);
}
在清单文件中注册这个服务,并给一个action,方便客户端通过这个action来绑定这个服务:
<service android:name="com.example.lt.messengerdemo.RemoteService">
<intent-filter>
<action android:name="com.example.lt.messengerdemo.RemoteService"> </action>
</intent-filter>
</service>
可以看到,在这一步中完成了三面的三个步骤,接下来,我们需要在客户端来绑定这个远程服务。
(2)远程客户端绑定服务
package com.example.lt.remoteclient;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private Messenger mMessenger;
private boolean mBound;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onStart() {
super.onStart();
Intent intent = new Intent();
intent.setAction("com.example.lt.messengerdemo.RemoteService");
intent.setPackage("com.example.lt.messengerdemo");
mBound = bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mMessenger = new Messenger(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onStop() {
super.onStop();
unbindService(conn);
}
public void hello(View view) throws RemoteException {
if(mBound) {
Message message = Message.obtain();
message.what = 0;
mMessenger.send(message);
}else{
Toast.makeText(MainActivity.this,"还没接通呢?",Toast.LENGTH_SHORT).show();
}
}
}
这里先在onStart()
方法中绑定远程服务,然后在绑定服务成功后的那个回调方法onServiceConnected
中通过得到的IBinder对象创建Messenger对象,然后用户点击hello按钮后向远程服务发送一个消息(调用hello()
方法),最后在onStop()
方法中取消绑定服务。
注意:android5.0后对service的隐式启动做了一些限制,隐式启动需要设置action和package(service所在的那个应用的包名)。
客户端布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:orientation="horizontal"
android:gravity="center"
android:layout_height="match_parent"
>
<Button
android:onClick="hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="hello" />
</LinearLayout>
这里就不贴出测试结果的动态图了,直接说个测试结果吧,测试结果是我们点击hello按钮后,系统弹出一个“hello,I am RemoteService”吐司,看到这个结果,我们就知道远程服务响应了客户端的请求(处理了接收到的消息)。
二、管理Service的生命周期
我们知道,客户端可以通过bindService()
绑定Service和startService()
启动服务一个非常明显的区别就是前者会将Service的生命周期和客户端生命周期绑定在一起(通常是activity),后者Service的生命周期由这个服务自己管理,所以当我们绑定Service来启动服务的时候,我们需要在合适的时候(不需要和Service通讯)绑定和解除绑定服务,比如:当我们Activity在不可见的时候不需要服务(通讯),可见的时候需要服务(通讯),那么,我们就可以在Activity的onStart()
中绑定服务,在onStop()
中解除绑定;当我们需要Service一直运行在后台,直到Activity被销毁,那么,我们就可以在onCreate()
和onDestory()
中分别绑定和解除绑定服务,但通常不会再onResume()
和onPause()
中绑定和解除绑定服务,下面我们看一张图来理解Service生命周期:
这是android官网的一一张图片,这里这个说明,当客户端通过startService()
方式启动服务的时候,系统会调用服务的onStartCommand()
方法(我们一般在这个方法里面做任务)而不会调用onBind()
方法;当客户端通过bindService()
方法启动服务的时候,系统会调用onBind()
方法将Service和客户端绑定在一起,而不会调用onStartCommand
方法。
到这里,绑定一个服务的前2种方式基本介绍完了,关于AIDL绑定服务,大家可以参考:android使用AIDL实现跨进程通讯(IPC)。
总结:
绑定服务可以有很多种方式,具体通过什么方式,这里有一个原则,如果service只服务于自己的这个应用或者在同一进程(通常不同应用在不同的进程中)中共享,那么通过第一种方式就行了;如果你应用的service需要向远程客户端提供服务,但不需要在这个service中处理多线程并发,那么可以选择messenger方式绑定服务;如果你的service既要在不同的应用(进程)中通讯(IPC),又要在service中处理多线程,那么可以考虑使用AIDL绑定服务。不过,到这里,我好奇的是Messenger绑定远程服务实现IPC是否能够像AIDL一样传递数据(包括自定义类型)呢?还是只能跨进程传递消息?求解。