深入理解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. 绑定服务
下图是效果:
并且可以在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(); } }}
下图是效果:
上图中,首先获取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地址
- 深入理解Service(二)——绑定服务
- 深入剖析Android四大组件(二)——Service服务之启动与绑定
- 深入剖析Android四大组件(二)——Service服务之启动与绑定
- 深入剖析Android四大组件(二)——Service服务之启动与绑定
- 深入理解Service(一)——服务生命周期
- Android service(二)绑定服务
- 深入理解Service(三)——前台服务和IntentService
- 深入理解javascript事件处理函数绑定三部曲(二)——传统处理函数绑定模型
- 绑定服务——Bound Service
- Android 开发指南(二) 服务绑定 Bound Service
- Service服务(非绑定与绑定)
- 三、基本组件(二)深入理解Service
- Android:Service(二)——以绑定方式启动Service
- Android---服务(Service)的绑定服务
- 深入理解Android(04)——深入理解属性服务
- Android:Service(三)——Aidl绑定远程服务
- Android Service 服务(二)—— BroadcastReceiver
- Android Service 服务(二)—— BroadcastReceiver
- 蓝鸥iOS从零基础到精通就业-OC语言入门 属性1
- 网站浏览器崩溃原因分析
- reduceRight、reverse、shift、slice
- Java虚拟机类加载机制
- java日志框架slf4j与log4j
- 深入理解Service(二)——绑定服务
- 一张图让你了解五险一金
- Error: Could not find or load main class
- 文字无缝滚动效果及scrollHeight,offsetHeight等区别
- 蓝鸥iOS从零基础到精通就业-OC语言入门 属性2
- 236. Lowest Common Ancestor of a Binary Tree
- 442. Find All Duplicates in an Array
- windows7系统安装curl
- Windows下安装tensorflow步骤