当前位置: 代码迷 >> Android >> Android Service通讯详解
  详细解决方案

Android Service通讯详解

热度:27   发布时间:2016-04-28 00:24:39.0
Android Service通信详解

概述

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

  相关解决方案