概述
Service作为Android四大组件之一,我们在Android开发中肯定经常要用到它,而用到Service,则通常需要跟其进行通信,本文旨在归纳所有与Service通信需要用到的所有技术。
Service
既然要讲Service通信,就先来看看Service是什么组件?它的功能有哪些?这里我们来复习一下基础知识=。=
在android开发者官网对Service的介绍如图:
简单翻译过来就是——Service是一种在后台长期运行的组件,这种组件不与用户进行交互。当Service被创建且在后台运行时,即使用户此时切换到其他应用程序(APP),该Service还是会继续运行。其它组件可以通过绑定Service的方式与Service进行通信,甚至可以实现跨进程通信(IPC)。
注意:很多人都会被“后台运行”这几个字给蒙骗了,会以为创建一个Service就等于创建了一个后台运行的线程,于是都把耗时操作往Service里面放,发现经常会出现了主线程卡死的情况。其实是因为Service是运行在主线程中的,所以执行耗时操作时必须新建一个线程。
与Service通信
为表达方便,下面我们统一将与Service进行通信的组件成为client。
Bound Service
大多数情况下,我们都是通过绑定Service的方式实现与该Service的通信。我们需要给要绑定的Service重写onBind()回调方法,该方法会返回一个IBinder对象给client,于是client就可以通过该IBinder对象实现与Service的通信。
说得比较笼统,看官们可能会觉得云里雾里的,接下来我们来详细看看如何使用Bound Service的方式来实现与Service通信,主要有以下三种方式:
直接使用IBinder对象
使用Messenger(信使)
- 使用AIDL
使用IBinder对象
如果要进行通信的Service只服务于我们的应用程序,即与client处于同一进程中运行时,我们应该使用这种方式实现与Service的通信。
去android开发者官网查看IBinder类的文档,有这样一句话
再查一下Binder类,发现Binder类就是Android封装好的IBinder接口的实现类。我们使用IBinder对象实现通信时,应直接使用Binder类的子类。
一般直接使用Binder对象完成与Service通信有如下几个步骤:
创建一个自定义Binder类,继承Binder类,该Binder类必须封装一些接口(API),使得client获取到该Binder类对象时可以调用,从而实现与Service的通信。
在Service中,创建一个该Binder类的实例。
重写Service的onBind()回调方法,返回该实例。
在client中,调用bindService()方法,在方法中传入一个ServiceConnection对象,在该ServiceConnection#onServiceConnected()回调方法中获取传入的Binder实例。
通过该实例调用第1步中封装出来的接口,实现与Service通信
实例代码:(源码来自Android开发者官网)
service:
public class LocalService extends Service {
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
// Random number generator
private final Random mGenerator = new Random();
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
LocalService getService() {
// Return this instance of LocalService so clients can call public methods
return LocalService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/** method for clients */
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
这里LocalBinder类封装getService()方法,使得client获得Binder对象时能通过调用它获得当前LocalService的实例。从而能调用所有LocalService中的public方法(getRandomNumber()方法),从而实现与Service通信。
client:
这里提供了一个Button(按钮),当点击时,判断当前是否与LocalService取得联系,如果是,则调用LocalService#getRandomNumber(),并把结果使用消息提示框显示出来。
public class BindingActivity extends Activity {
LocalService mService;
boolean mBound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// Bind to LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
/** Called when a button is clicked (the button in the layout file attaches to
* this method with the android:onClick attribute) */
public void onButtonClick(View v) {
if (mBound) {
// Call a method from the LocalService.
// However, if this call were something that might hang, then this request should
// occur in a separate thread to avoid slowing down the activity performance.
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
使用Messenger实现跨进程通信
当我们的Service需要给远程的进程进行通信时,即client与Service处于不同进程的时候,我们就涉及到跨进程通信(IPC——interprocess communication)。
Android给我们提供了Messenger(信使)和AIDL(Android Interface Definition Language)用以实现IPC。
实质上,Messenger的本质也是使用了AIDL,只不过Messenger使用了一个队列来维护所有的client请求,即Messenger一次只处理一个client请求。
大多数情况下,我们并不要求我们的Service需同时服务多个client,即并不需要实现多线程运作,这时候就可以使用Messenger,用起来比AIDL方便许多,也不用去考虑Service是否线程安全。
看过Messenger的源码发现,Messenger的内部封装了Handler,就是说使用Messenger进行通信(消息传递)时,实质就是在使用Handler-Looper-MessageQueue的消息处理机制。相信Android的异步消息处理机制大家也都很熟悉了,所以理解起来也比较简单。
(=。=不知道Android异步消息处理机制的童鞋请自行百度~)
使用Messenger实现Service通信时需要完成以下几个步骤:
service实现一个处理客户端发来消息的Handler
使用该Handler创建一个Messenger对象(它是Handler的一个引用)
在onBind()方法中,返回该Messenger对象创建的IBinder对象,通过Messenger#getBinder()可获得
client获取该IBinder对象,并该对象实例化出Service的Messenger对象(它引用到Service的Handler),client使用该Messenger对象向Service发送Message
Service在它的Handler#handleMessage()方法中处理从client发来的消息
看起来挺复杂,其实并不能理解,核心思想就是将Service中的Handler发送到client中,然后client就可以调用该Handler的sendMessage()方法给Service发送消息,Service在内部实现Handler#handlerMessage()方法,就可以处理从client发送过来的消息,从而实现通信。
只是这里Android为了能跨进程通信所以将Handler封装到Messenger中。
看下面这张图,方便大家理解。
下面来看实现代码:(源码来自Android开发者官网)
service:
public class MessengerService extends Service {
/** Command to the service to display a message */
static final int MSG_SAY_HELLO = 1;
/**
* Handler of incoming messages from clients.
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
}
client:
public class ActivityMessenger extends Activity {
/** Messenger for communicating with the service. */
Messenger mService = null;
/** Flag indicating whether we have called bind on the service. */
boolean mBound;
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mBound = false;
}
};
public void sayHello(View v) {
if (!mBound) return;
// Create and send a message to the service, using a supported 'what' value
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// Bind to the service
bindService(new Intent(this, MessengerService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
}
这里只实现了单向传递信息(单向通信),即信息只能从client传向Service,而不能反向传递。 那该如何实现双向通信呢?
其实很简单,我们都知道client能给Service传递消息是因为client拿到了Service的Messenger,并通过它来发送消息给Service,Service内部实现了Handler#handleMessage()方法处理client发来的消息。那要实现Service->client的消息传递,只需如法炮制,在client端实现Handler#handleMessage(),并把client的Messenger传给Service,Service拿到client的Messenger后,就能给client传递消息了。
如何实现?只需在client给Service发送消息的时候,把client的Messenger对象赋给Message#replyTo属性即可。
看官们可自己动手实现一遍
注意:进行跨进程通信时,需在manifest文件中把Service的”android:exported”属性置为true,即
android:exported="true"
使用AIDL实现IPC
在IPC时,如果我们的Service需同时处理多个多个client请求,这时候就需要用到AIDL,这部分也是博主的盲点之一,所以等之后再补上=。=看官们自行到Android开发者官网上看~点这里是链接,不过前提是不被墙。看不了的话可以度娘~。~
通过广播的方式实现与Service通信
除了通过绑定Service的方式与Service通信外,我们还可以通过广播的方式实现与Service通信。
核心思想是——在Service中发送广播,然后在各个与该Service通信的client中注册该广播的监听器,并时间onReceive()方法,处理从Service中发送的消息,从而实现消息传递,实现通信。
这种方式对于Service给多个client发送相同消息的情况比较好用。
这里也不再细说。
到此我们就把所有Service通信的方式都讲述了一遍(如果有别的方式的话希望能评论或私信告知~thx),希望各位看官有所收获。
同样,如果有任何问题可评论或者发邮件跟我联系yetwish@gmail.com