概述
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),希望各位看官有所收获。
同样,[email protected]gmail.com