深入理解Service(二)——绑定服务

来源:互联网 发布:淘宝收藏店铺看不到 编辑:程序博客网 时间:2024/06/05 00:12

在上一篇博客深入理解Service(一)——服务生命周期中了解了Service会有启动和绑定两种状态,对应着这两种状态会有不同的生命周期:开启服务与停止服务的方式均有所不同。这篇博客主要介绍绑定服务相关的内容,以及如果服务与调用者,比如Activity处于不同进程时该如何操作,即通常意义上所说的IPC。

绑定服务的实现

什么时候需要使用绑定服务呢?我的理解是调用者在启动完服务后,还需要以后与服务打交道,还要控制服务,而不是像启动服务一样任服务自由自在不受控制。常见的一个场景,Service播放音乐,界面上有上一曲,下一曲、暂停、播放等按钮,通过这些按钮还可以控制Service操作,这种情景就是使用的绑定,本篇博客的例子也将实现一个简单的demo。

1.定义服务

绑定服务,需要重写onBind()方法,并且返回一个IBinder对象,该IBinder客户端再绑定服务成功后可以获取一个该对象,因此客户端可以通过IBinder来控制服务。IBinder是一个接口,一般我们不会直接实现该接口,而是会继承Binder类,Binder类实现了IBinder类。
下面是该播放服务的示例:

** * 播放音乐服务 */public class PlayMusicService extends Service {    public PlayMusicService() {    }    public class PlayMusicBinder extends Binder {        /**         * 播放音乐         */        public void play() {            playMusic();        }        /**         * 暂停播放         */        public void pause() {            pauseMusic();        }        /**         * 暂停播放         */        public void stop() {            stopMusic();        }    }    private PlayMusicBinder mBinder = new PlayMusicBinder();    private MediaPlayer mediaPlayer;    @Override    public void onCreate() {        super.onCreate();        mediaPlayer = MediaPlayer.create(this, R.raw.diamonds);    }    @Override    public IBinder onBind(Intent intent) {        return mBinder;    }    @Override    public void onDestroy() {        super.onDestroy();        if (mediaPlayer != null)            mediaPlayer.release();    }    private void playMusic() {        mediaPlayer.start();    }    private void pauseMusic() {        mediaPlayer.pause();    }    private void stopMusic() {        mediaPlayer.stop();    }}

其中PlayMusicBinder继承自Binder类,并且提供了三个接口,分别是播放音乐、暂停音乐以及停止播放;在onBind()方法中返回这样一个实例。播放音乐用到了MediaPlayer,音乐文件直接直接放在了res/raw/目录下。至此,服务端就完成了,主要工具就是编写一个Binder类提供给客户端操作的接口。
从上面可以看到,定义服务端主要包括两步:
1. 扩展Binder类,实现接口
2. 在onBind()方法返回一个IBinder实例

2.定义调用者(Activity)

在定义完服务端后,再来定义客户端,bindService方法中第二个参数是一个ServiceConnection接口,当服务连接成功以及失去连接时会分别回调两个方法。下面是Activity的定义:

public class PlayMusicActivity extends AppCompatActivity {    private PlayMusicService.PlayMusicBinder musicBinder;    private ServiceConnection connection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            //在服务连接成功后保存PlayeMusicBinder对象,控制服务端            musicBinder = (PlayMusicService.PlayMusicBinder) service;        }        @Override        public void onServiceDisconnected(ComponentName name) {            musicBinder = null;        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_play_music);    }    @Override    protected void onStart() {        super.onStart();        bindService(new Intent(this, PlayMusicService.class), connection, Service.BIND_AUTO_CREATE);    }    @Override    protected void onDestroy() {        super.onDestroy();        unbindService(connection);    }    public void playMusic(View view) {        if (musicBinder != null)            musicBinder.play();    }    public void pauseMusic(View view) {        if (musicBinder != null)            musicBinder.pause();    }    public void stopMusic(View view) {        if (musicBinder != null)            musicBinder.stop();    }}

从上面的代码可以看出,在onStart中绑定服务,一旦服务绑定成功,则保存PlayMusicBinder对象。界面上有三个按钮,分别是播放、暂停和停止,接下来就可以控制后台服务播放音乐了。
从上面可以看到,定义客户端主要有三步:
1. 定义ServiceConnection对象,在onServiceConnect回调中保存IBinder对象
2. 绑定服务
3. 使用onServiceConnect回调中保存的IBinder对象操作Service

在服务中扩展Binder这种方式适用于服务端和客户端在同一个进程中,那么如果客户端和服务端在不同进程中,那么该如何实现了?也就是通常意义上的IPC,IPC的方式有很多,比如管道、共享内存、Socket等等,但是在Android中有一种特殊的机制:Binder机制。下面首先介绍Binder机制的两种表现形式,Messenger和定义AIDL接口。下面分别就这两种方法就行介绍。

Messenger

Messenger对Binder进行了封装,适用于服务端单线程的情况,因为服务端会定义一个Handler用于顺序处理客户端发送过来的Message,在服务的onBind中返回内部的Binder。
Messenger用于发送消息,Handler用于处理消息,一般我们使用服务端作为处理消息,所以服务端需要Messenger和Handler,并且在onBind方法中返回Messenger内部的Binder,这样客户端就可以根据绑定服务后得到Messenger,可看做是服务端的Messenger,然后就可以通过它来发送消息了;那么如果服务端会在处理完客户端的消息后回复客户端,并且客户端也需要处理这个消息,那么该如何实现呢?
根据上面的分析,既然服务端需要回复客户端,也就得拿到客户端的Messenger,然后客户端需要处理消息,也需要一个Handler,所以说客户端也需要一个Messenger和一个Handler。下面我们以一个例子来详细说明,客户端向服务端发送两个整形数,服务端将和返回给客户端。

1.服务端的定义

/** * 使用Messenger,适合应用与服务在不同进程中 */public class MessengerService extends Service {    //Handler用于处理客户端发送来的消息,不存在线程安全问题    private Handler mHandler = new Handler() {        @Override        public void handleMessage(Message msg) {            switch (msg.what) {                case 0:                    int a = msg.arg1;                    int b = msg.arg2;                    int sum = a + b;                    //得到客户端的Messenger,回复消息                    Messenger replyTo = msg.replyTo;                    Message message = Message.obtain();                    message.what = 1;                    message.arg1 = sum;                    try {                        replyTo.send(message);                    } catch (RemoteException e) {                        e.printStackTrace();                    }                    break;            }        }    };    private Messenger messenger = new Messenger(mHandler);    public MessengerService() {    }    @Override    public IBinder onBind(Intent intent) {        //返回Messenger内部的Binder        return messenger.getBinder();    }}

从上面的代码可以看到,服务端主要包括三步:
1. 定义Handler用于处理消息
2. 根据Handler声明Messenger对象
3. 在onBind方法中返回Messenger的Binder

如何将一个服务开启在另一个进程呢?
这个只需要在清单文件中注册服务时,添加一个process属性即可,如下:

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

process属性可以为服务指定任意进程,remote前面的“:”的含义是使完整的进程名使用包名+remote。

2.客户端的定义

在定义完服务端之后,我们再来看下客户端的定义,客户端主要完成绑定服务,发送数据以及接受数据的功能。如下:

public class MainActivity extends AppCompatActivity {    private TextView resultShowTv;    private Messenger messenger;    private ServiceConnection connection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            //得到服务端的Messenger,发送消息            messenger = new Messenger(service);            Message msg = Message.obtain(handler, 0, 5, 10);            msg.replyTo = replyTo;//将客户端的Messenger对象赋给replyTo字段           try {                messenger.send(msg);            } catch (RemoteException e) {                e.printStackTrace();            }        }        @Override        public void onServiceDisconnected(ComponentName name) {        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        resultShowTv = (TextView) findViewById(R.id.result_show_tv);    }    //客户端中接受消息的Messenger    private Handler handler = new Handler() {        @Override        public void handleMessage(Message msg) {            switch (msg.what) {                case 1:                    int sum = msg.arg1;                    resultShowTv.setText(String.valueOf(sum));                    break;            }        }    };    private Messenger replyTo = new Messenger(handler);    public void messenger(View view) {        Intent bindService = new Intent(this, MessengerService.class);        bindService(bindService, connection, BIND_AUTO_CREATE);    }}

从上面的代码可以看到,客户端主要有三步:
1. 编写ServiceConnection类,在onServiceConnect回调中得到服务端的Messenger,一旦得到了该对象,就发送Message对象,并且将客户端这边的Messenger带了过去
2. 编写客户端处理消息的Handler以及Messenger
3. 绑定服务

下图是效果:
Messenger
并且可以在Android Studio上看到两个进程名,如下:
进程服务
从上图可以看到,一个进程名为com.xks.binderdemo,一个进程名为com.xks.binderdemo.remote,这就是清单文件中“:”的效果。

使用AIDL

AIDL是Android Interface Define Language(Android接口定义语言)的简称。所谓接口定义,就是在通信双方定义一个规则,你可以这样发数据,我就可以接受数据。
使用AIDL时有如下三步:
1. 定义.aidl文件
在.aidl文件中编写接口
2. 实现接口
Android SDK工具会根据.aidl文件生成一个Java类,而开发者需要做的是继承其内部的一个Stub抽象类并实现.aidl中的接口
3. 向客户端公开接口
在onBind方法中返回Binder

1.定义.aidl文件

.aidl文件语法类似于Java,只能位于src/目录下,每个.aidl文件都必须定义单个接口,并且只需包含接口声明和方法签名。
默认情况下,AIDL支持以下类型:
- Java的基本类型,如int、float、double、boolean等
- String类型和Charsequence
- List
List包含元素的类型必须是AIDL支持的,并且另一端实际接收到的ArrayList类型,但生成的方法使用的是List接口
- Map
Map中的所有元素的类型必须是AIDL支持的,另一端实际接收到的是HashMap类型,但生成的方法使用的Map接口
- Parcelable类型

另外,如果需要使用非以上类型,都必须使用import语句,即使这些类型与AIDL位于同一个包中。
定义服务接口时,请注意:
- 方法可带零个或多个参数,返回值或空值
- 所有非基本类型的参数都㤇指示数据走向的方向标记。可以是in、out或inout。基本类型默认为in,不能是其他方向

注意:应该将方向限定为真正需要的方向

  • .aidl文件中包括的所有代码注释都包含在生成的IBinder接口中(import和package语句之前的注释除外)
  • 只支持方法;不能公开AIDL中的静态字段。

下面为了演示例子,首先定义一个Book类,主要有两个字段,isbn和name,实现了Parcelable接口,如下:

public class Book implements Parcelable {    private int isbn;    private String name;    @Override    public int describeContents() {        return 0;    }    @Override    public void writeToParcel(Parcel dest, int flags) {        dest.writeInt(this.isbn);        dest.writeString(this.name);    }    public Book() {    }    protected Book(Parcel in) {        this.isbn = in.readInt();        this.name = in.readString();    }    public static final Creator<Book> CREATOR = new Creator<Book>() {        @Override        public Book createFromParcel(Parcel source) {            return new Book(source);        }        @Override        public Book[] newArray(int size) {            return new Book[size];        }    };    @Override    public String toString() {        return "Book{" +                "isbn=" + isbn +                ", name='" + name + '\'' +                '}';    }}

实现Parcelable接口的类需要在其内部提供一个CREATOR的常量,然后在src/目录下定义Book的aidl文件,如下:

// Book.aidlpackage com.xks.binderdemo;parcelable Book;

接下来,定义BookManager.aidl文件,如下:

// IMyAidlInterface.aidlpackage com.xks.binderdemo;// Declare any non-default types here with import statementsimport com.xks.binderdemo.Book;interface BookManager {    /**     * 打印书籍     */    String printBook(in Book book);    /**     * 添加书籍     */    void addBook(in Book book);    /**     *得到书籍数量     */    int getBookSize();    /**     * 根据索引取书     */    Book get(int index);}

主要有四个接口,分别是打印书籍,添加书籍,得到书籍数量以及根据索引取书,在编写完.aidl文件后,同步一下,可以得到一个BookManager的接口,如下:

/* * This file is auto-generated.  DO NOT MODIFY. * Original file: E:\\Workspace\\Android\\BlogDemo\\servicedemo\\src\\main\\aidl\\com\\xks\\binderdemo\\BookManager.aidl */package com.xks.binderdemo;public interface BookManager extends android.os.IInterface {    /**     * Local-side IPC implementation stub class.     */    public static abstract class Stub extends android.os.Binder implements com.xks.binderdemo.BookManager {        private static final java.lang.String DESCRIPTOR = "com.xks.binderdemo.BookManager";        /**         * Construct the stub at attach it to the interface.         */        public Stub() {            this.attachInterface(this, DESCRIPTOR);        }        /**         * Cast an IBinder object into an com.xks.binderdemo.BookManager interface,         * generating a proxy if needed.         */        public static com.xks.binderdemo.BookManager asInterface(android.os.IBinder obj) {            if ((obj == null)) {                return null;            }            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);            if (((iin != null) && (iin instanceof com.xks.binderdemo.BookManager))) {                return ((com.xks.binderdemo.BookManager) iin);            }            return new com.xks.binderdemo.BookManager.Stub.Proxy(obj);        }        @Override        public android.os.IBinder asBinder() {            return this;        }        @Override        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {            switch (code) {                case INTERFACE_TRANSACTION: {                    reply.writeString(DESCRIPTOR);                    return true;                }                case TRANSACTION_printBook: {                    data.enforceInterface(DESCRIPTOR);                    com.xks.binderdemo.Book _arg0;                    if ((0 != data.readInt())) {                        _arg0 = com.xks.binderdemo.Book.CREATOR.createFromParcel(data);                    } else {                        _arg0 = null;                    }                    java.lang.String _result = this.printBook(_arg0);                    reply.writeNoException();                    reply.writeString(_result);                    return true;                }                case TRANSACTION_addBook: {                    data.enforceInterface(DESCRIPTOR);                    com.xks.binderdemo.Book _arg0;                    if ((0 != data.readInt())) {                        _arg0 = com.xks.binderdemo.Book.CREATOR.createFromParcel(data);                    } else {                        _arg0 = null;                    }                    this.addBook(_arg0);                    reply.writeNoException();                    return true;                }                case TRANSACTION_getBookSize: {                    data.enforceInterface(DESCRIPTOR);                    int _result = this.getBookSize();                    reply.writeNoException();                    reply.writeInt(_result);                    return true;                }                case TRANSACTION_get: {                    data.enforceInterface(DESCRIPTOR);                    int _arg0;                    _arg0 = data.readInt();                    com.xks.binderdemo.Book _result = this.get(_arg0);                    reply.writeNoException();                    if ((_result != null)) {                        reply.writeInt(1);                        _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);                    } else {                        reply.writeInt(0);                    }                    return true;                }            }            return super.onTransact(code, data, reply, flags);        }        private static class Proxy implements com.xks.binderdemo.BookManager {            private android.os.IBinder mRemote;            Proxy(android.os.IBinder remote) {                mRemote = remote;            }            @Override            public android.os.IBinder asBinder() {                return mRemote;            }            public java.lang.String getInterfaceDescriptor() {                return DESCRIPTOR;            }            /**             * 打印书籍             */            @Override            public java.lang.String printBook(com.xks.binderdemo.Book book) throws android.os.RemoteException {                android.os.Parcel _data = android.os.Parcel.obtain();                android.os.Parcel _reply = android.os.Parcel.obtain();                java.lang.String _result;                try {                    _data.writeInterfaceToken(DESCRIPTOR);                    if ((book != null)) {                        _data.writeInt(1);                        book.writeToParcel(_data, 0);                    } else {                        _data.writeInt(0);                    }                    mRemote.transact(Stub.TRANSACTION_printBook, _data, _reply, 0);                    _reply.readException();                    _result = _reply.readString();                } finally {                    _reply.recycle();                    _data.recycle();                }                return _result;            }            /**             * 添加书籍             */            @Override            public void addBook(com.xks.binderdemo.Book book) throws android.os.RemoteException {                android.os.Parcel _data = android.os.Parcel.obtain();                android.os.Parcel _reply = android.os.Parcel.obtain();                try {                    _data.writeInterfaceToken(DESCRIPTOR);                    if ((book != null)) {                        _data.writeInt(1);                        book.writeToParcel(_data, 0);                    } else {                        _data.writeInt(0);                    }                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);                    _reply.readException();                } finally {                    _reply.recycle();                    _data.recycle();                }            }            @Override            public int getBookSize() throws android.os.RemoteException {                android.os.Parcel _data = android.os.Parcel.obtain();                android.os.Parcel _reply = android.os.Parcel.obtain();                int _result;                try {                    _data.writeInterfaceToken(DESCRIPTOR);                    mRemote.transact(Stub.TRANSACTION_getBookSize, _data, _reply, 0);                    _reply.readException();                    _result = _reply.readInt();                } finally {                    _reply.recycle();                    _data.recycle();                }                return _result;            }            /**             * 根据索引取书             */            @Override            public com.xks.binderdemo.Book get(int index) throws android.os.RemoteException {                android.os.Parcel _data = android.os.Parcel.obtain();                android.os.Parcel _reply = android.os.Parcel.obtain();                com.xks.binderdemo.Book _result;                try {                    _data.writeInterfaceToken(DESCRIPTOR);                    _data.writeInt(index);                    mRemote.transact(Stub.TRANSACTION_get, _data, _reply, 0);                    _reply.readException();                    if ((0 != _reply.readInt())) {                        _result = com.xks.binderdemo.Book.CREATOR.createFromParcel(_reply);                    } else {                        _result = null;                    }                } finally {                    _reply.recycle();                    _data.recycle();                }                return _result;            }        }        static final int TRANSACTION_printBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);        static final int TRANSACTION_getBookSize = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);        static final int TRANSACTION_get = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);    }    /**     * 打印书籍     */    public java.lang.String printBook(com.xks.binderdemo.Book book) throws android.os.RemoteException;    /**     * 添加书籍     */    public void addBook(com.xks.binderdemo.Book book) throws android.os.RemoteException;    public int getBookSize() throws android.os.RemoteException;    /**     * 根据索引取书     */    public com.xks.binderdemo.Book get(int index) throws android.os.RemoteException;}

从上面可以看到BookManager内部有一个叫做Stub的抽象类实现了BookManager接口,并且将接口方法都变成了abstract,子类必须实现这些方法,至于其他Binde通信的细节,Android都已经帮我们实现了。
Stub中有个adInterface的方法,该方法主要用于客户端从IBinder对象得到BookManager接口,这样就可以通过该接口与服务端通信。

实现接口

在定义完接口后,在BookService中实现BookManager.Stub类,实现那几个抽象方法,如下:

public class BookService extends Service {    private List<Book> bookList = new ArrayList<>();    public class BookBinder extends BookManager.Stub {        @Override        public String printBook(Book book) throws RemoteException {            return book.toString();        }        @Override        public void addBook(Book book) throws RemoteException {            bookList.add(book);        }        @Override        public int getBookSize() throws RemoteException {            return bookList.size();        }        @Override        public Book get(int index) throws RemoteException {            return bookList.get(index);        }    }    private BookBinder bookBinder = new BookBinder();    public BookService() {    }    @Override    public IBinder onBind(Intent intent) {        return bookBinder;    }}

可以看到BookService的实现很简单,与单纯使用Binder类似,只不过这里继承的是BookManager.Stub,而不是Binder。最后在onBind方法中返回声明的对象即可。

3.向客户端公开接口

客户端这里使用的是Activity,界面上有四个按钮,分别对应于四个方法,最下面有个TextView用于显示信息,Activity的实现如下:

public class BookActivity extends AppCompatActivity {    private TextView infoShowTv;    private BookManager bookManager;    private ServiceConnection connection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            bookManager = BookManager.Stub.asInterface(service);        }        @Override        public void onServiceDisconnected(ComponentName name) {            bookManager = null;        }    };    private Book[] books = {            new Book(1, "Java编程思想"),            new Book(2, "第一行代码"),            new Book(3, "Anroid开发艺术探索")    };    private int index = 0;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_book);        infoShowTv = (TextView) findViewById(R.id.info_show_tv);    }    @Override    protected void onStart() {        super.onStart();        bindService(new Intent(this, BookService.class), connection, Service.BIND_AUTO_CREATE);    }    @Override    protected void onDestroy() {        super.onDestroy();        unbindService(connection);    }    /**     * 添加书籍     *     * @param view     */    public void addBook(View view) {        if (bookManager != null)            try {                bookManager.addBook(books[index++ % books.length]);            } catch (RemoteException e) {                e.printStackTrace();            }    }    public void printBook(View view) {        if (bookManager != null)            try {                infoShowTv.setText(bookManager.printBook(books[index % books.length]));            } catch (RemoteException e) {                e.printStackTrace();            }    }    public void getSize(View view) {        if (bookManager != null)            try {                infoShowTv.setText(String.valueOf(bookManager.getBookSize()));            } catch (RemoteException e) {                e.printStackTrace();            }    }    public void getFirst(View view) {        if (bookManager != null)            try {                infoShowTv.setText(bookManager.printBook(bookManager.get(0)));            } catch (RemoteException e) {                e.printStackTrace();            }    }}

下图是效果:
AIDL效果图
上图中,首先获取List的尺寸为0,然后加入一本书后,尺寸就变成了1,然后打印,继续加书获取尺寸。
使用AIDL的时候,需要注意的是,调用服务端的方法都是同步调用的,所以如果说如果操作很耗时的话,那么客户端调用需要放到线程中操作;另外,服务端需要注意线程安全问题,因为多个客户端调用时会有并发问题,而Messenger内部就不需要了,因为Handler确保了消息的同步性。

总结

从上面可以看到,绑定服务时,如果服务和客户端在同一个进程中,那么直接在服务端扩展Binder即可;如果服务和客户端不在同一个进程中,那么可以使用Messenger或AIDL。Messenger是进行IPC的最简单方式,并且Android用Handler帮我们实现了一个队列,不需要考虑线程安全问题;而使用AIDL的方式则更加通用,需要开发者自己考虑线程安全问题。
对于进程间通信或客户端和服务端在同一个进程中,底层使用的都是Binder。可以认为只有当客户端得到服务端Binder的一个句柄后,才可以通过该句柄来控制服务端,与服务端进行通信。对上述三种情况分别为:
- 客户端和服务在同一个进程中,客户端在ServiceConnection的onServiceConnected中得到服务端的Binder对象
- 使用Messenger时,客户端同样在ServiceConnection的onServiceConnect回调中得到服务端的Messenger,然后使用Messenger发送消息
- 使用AIDL时,与客户端和服务在同一个进程中一样

本文的代码可以查看我的Github地址

1 0