1.Binder 介绍
从 Android 层面来说,Binder 就是客户端和服务端进行通信的媒介,当 bindService
的时候,服务端会返回一个包含了服务端业务调用的 Binder 对象,通过这个 Binder 对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于 AIDL 的服务。Binder 主要用于 Service,包括 AIDL 和 Messenger,其实 Messenger 的底层是 AIDL。
2.使用 AIDL
这里主要介绍一下思路,具体参考一下 Android:学习AIDL,这一篇文章就够了(上) 进行项目搭建。
-
一、新建服务端工程
服务端代码:https://github.com/innovatorCL/IPCServer
1.新建 aidl 包,客户端和服务器的包名必须一样。
-
2.新建非基本数据类型的数据文件,包括 xx.java 和 xx.aidl 文件,同时在 Module 的 gradle 文件中声明源码路径包含 aidl,不然会报找不到文件的错误。
sourceSets { main { java.srcDirs = ['src/main/java', 'src/main/aidl'] } }
-
3.新建接口方法 xxx.aidl 文件。所有需要被客户端调用的方法在这个文件中声明。
4.新建一个 Service 作为服务端,在里面是实现接口 xxx.aidl 声明的方法,并实例化成为一个 XXX 对象,在客户端 bind 服务端的时候返回给客户端(其实返回给客户端的是 Android 生成的 XXX.java 里面的内部代理类对象,XXX.Stub.Proxy,客户端通过这个对象调用服务端 Service 的方法。)
/**
*
* 服务端的AIDLService.java
*
* 在服务端实现AIDL中定义的方法接口的具体逻辑,
* 然后在客户端调用这些方法接口,从而达到跨进程通信的目的。
* Created by innovator on 2018/1/18.
*/
public class AIDLService extends Service{
//包含Book的List
private List mBooks = new ArrayList<>();
//由AIDL生成的BookManager类的代理类
private final BookManager.Stub mBookManager = new BookManager.Stub() {
@Override
public List getBooks() throws RemoteException {
synchronized (this){
Log.i("TAG","服务端发送数据给客户端");
if(mBooks != null){
return mBooks;
}
return new ArrayList<>();
}
}
@Override
public void addBook(Book book) throws RemoteException {
Log.i("TAG","服务端接收客户端的数据");
synchronized (this) {
if (mBooks == null) {
mBooks = new ArrayList<>();
}
if (book == null) {
Log.e("TAG", "客户端传了一个空的Book对象");
book = new Book();
}
//因为 getBook 的参数的 tag 是 inout,所以服务端修改了book的参数,客户端的应该也会修改
book.setPrice(6666);
if (!mBooks.contains(book)) {
mBooks.add(book);
}
//打印mBooks列表,观察客户端传过来的值
Log.e("TAG", "服务端的 addBooks() 方法被调用 , 打印服务端收到的数据 : " + mBooks.toString());
}
}
};
@Override
public void onCreate() {
Book book = new Book();
book.setName("在复杂的世界做个明白人");
book.setPrice(2222);
mBooks.add(book);
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e("TAG", String.format("服务端连接上了客户端,连接的 ntent 是 %s", intent.toString()));
return mBookManager;
}
}
在 Manifest.xml 中声明该 Service:
-
二、新建客户端工程
客户端代码:https://github.com/innovatorCL/IPCClient
-
1.新建和服务端同名的 aidl 包,将服务端的 aidl 文件全部 copy 过去。
2.在 MainActivity 中连接服务端 Service ,并获取服务端返回的代理对象,调用服务端的方法传入或者读取服务端的数据。
-
/**
*
* IPC 客户端的 MainActivity.java
*/
public class MainActivity extends Activity {
//由AIDL文件生成的Java类
private BookManager mBookManager;
//标志当前与服务端连接状况的布尔值,false为未连接,true为连接中
private boolean mBound;
//包含Book对象的list
private List mBooks;
private Book book;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i("TAG","连接上了服务端");
mBookManager = BookManager.Stub.asInterface(service);
mBound = true;
//获取代理对象,调用服务端的方法
if(mBookManager != null){
try {
mBooks = mBookManager.getBooks();
Log.i("TAG","获取到了服务端数据:"+mBooks.toString());
}catch (RemoteException e){
e.printStackTrace();
Log.i("TAG","调用服务端方法出现异常:"+e.getMessage());
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBound = false;
Log.i("TAG","断开服务端连接");
}
};
/**
* 按钮的点击事件,点击之后调用服务端的addBookIn方法
*
* @param v
*/
public void addBook(View v){
if(!mBound){
attemptBindService();
Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show();
return;
}
if(mBookManager == null){
return;
}
book = new Book();
book.setName("认知突围");
book.setPrice(88);
//调用服务器端的方法,传入一个Book对象
try{
mBookManager.addBook(book);
Log.i("TAG","调用服务端方法传入一个Book");
}catch (RemoteException e){
e.printStackTrace();
Log.i("TAG","调用服务端方法出现异常:"+e.getMessage());
}
}
/**
* 点击了 addBook 之后看看客户端的数据有没有同步被改
* 验证 inout tag 的功能
*
* 确实是同步更改了 inout 传入的那个对象的值
* @param v
*/
public void getBooks(View v) {
Log.i("TAG","打印 addBooks 之后的数据 :"+book.toString());
}
/**
* 尝试连接上服务器
*/
private void attemptBindService(){
Intent i = new Intent();
i.setAction("com.innovator.aidl");
i.setPackage("com.innovator.ipcserver");
bindService(i,mServiceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStart() {
super.onStart();
if(!mBound){
attemptBindService();
}
}
@Override
protected void onStop() {
super.onStop();
if(mBound){
unbindService(mServiceConnection);
mBound = false;
}
}
}
3. AIDL 工作流程分析
首先介绍几个类:
Stub : 生成的 AIDL 的内部类,继承 Binder, 实现了 IInterface 接口;
Proxy : Stub 的内部类,实现了我们自定义的 AIDL 接口;
BinderProxy : Binder 的内部类,跨进程通信时传递的对象
-
流程一:进程间通信时,Client 端开启服务 bindService, Server 端 onBind 方法返回 Stub 对象,通过 Binder 封装成 BinderProxy 对象 返回,到 Client 端的 onServiceConnected 方法接收到的就是 BinderProxy 对象,再调用 BookManager.Stub.asInterface(service) 封装成 Proxy, BinderProxy 是它的成员变量。
到这里,Client 端已经拿到了 Service 端传递过来的 Binder 对象。
流程二:Client 端调用方法(例:getBookList)时,会先调用 Proxy.getBookList, 这里会调用 mRemote.transact(这个是在客户端进程做的事), mRemote 也就是 BinderProxy 对象,实现跨进程的操作, BinderProxy 里面封装了 Stub 对象,所以会再跨进程调用到 Server 端的 Stub.onTransact(这个是在服务端的 Binder 线程池做的事), Stub.onTransact 内部调用 Service 里自己实现的 getBookList 方法,执行完后跨进程返回到 BinderProxy 中,BinderProxy 再跨进程返回到 Client 端,到此一次方法调用结束。
补充一张进程间方法调用的图。
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /Users/innovator/AndroidStudioProjects/IPCServer/app/src/main/aidl/com/innovator/ipcclient/BookManager.aidl
*/
package com.innovator.ipcclient;
//定义方法接口
public interface BookManager extends android.os.IInterface {
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.innovator.ipcclient.BookManager {
private static final java.lang.String DESCRIPTOR = "com.innovator.ipcclient.BookManager";
/** Construct the stub at attach it to the interface. */
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.innovator.ipcclient.BookManager interface,
* generating a proxy if needed.
*/
public static com.innovator.ipcclient.BookManager asInterface(android.os.IBinder obj) {
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.innovator.ipcclient.BookManager))) {
return ((com.innovator.ipcclient.BookManager)iin);
}
return new com.innovator.ipcclient.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_getBooks: {
data.enforceInterface(DESCRIPTOR);
java.util.List _result = this.getBooks();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.innovator.ipcclient.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.innovator.ipcclient.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
if ((_arg0!=null)) {
reply.writeInt(1);
_arg0.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.innovator.ipcclient.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.util.List getBooks() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.innovator.ipcclient.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
//传参时除了Java基本类型以及String,CharSequence之外的类型
//都需要在前面加上定向tag,具体加什么量需而定
@Override
public void addBook(com.innovator.ipcclient.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();
if ((0!=_reply.readInt())) {
book.readFromParcel(_reply);
}
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBooks = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
//所有的返回值前都不需要加任何东西,不管是什么数据类型
public java.util.List getBooks() throws android.os.RemoteException;
//传参时除了Java基本类型以及String,CharSequence之外的类型
//都需要在前面加上定向tag,具体加什么量需而定
public void addBook(com.innovator.ipcclient.Book book) throws android.os.RemoteException;
}
详细分析参考:
- Android 艺术开发探索,Android 根据 AIDL 文件自动生成对应的 java 文件 (P55 —— P60)
- Android:学习AIDL,这一篇文章就够了(下)
4. IPC 应用场景
- 腾讯系、阿里系等大厂的 APP 之间的通信
- 系统 APP 和系统服务之间的通信
- 推送、IM
- 双进程守护