前言
前两篇文章写给Android应用工程师的Binder原理剖析(转载一)
写给Android应用工程师的Binder原理剖析(转载二)
都转载自张磊的对Binder原理剖析的文章。
作者:张磊BARON
这篇文章是自己基于文章中对Binder原理剖析的理解而写的一个例子。
六 手动编码实现跨进程调用
通常我们在做开发时,实现进程间通信用的最多的就是 AIDL。当我们定义好 AIDL 文件,在编译时编译器会帮我们生成代码实现 IPC 通信。借助 AIDL 编译以后的代码能帮助我们进一步理解 Binder IPC 的通信原理。
先看一下代码清单:
通过工程结构可以3个组成部分:
- client中是主进程的MainActivity,它通过 Binder 代理对象传输Book对象到server端,或者说调用server端提供的代理对象的方法。
- server中实现了另外一个进程的Service,它包含了一个Binder实体对象,Binder实体对象实现了具体提供给client的方法
server端还包含了IInterface,Stub和Proxy,(在build中生成),它实现了提供给client端的Binder代理对象 - 传输的Book类
为了还原最初的AIDL在 IPC 通信中的运用,这里创建了IBookManagerInterface.aidl,然后编译生成包含一个IBookManagerInterface接口、一个 Stub 静态的抽象类和一个 Proxy 静态类的IBookManagerInterface.java。Proxy 是 Stub 的静态内部类,Stub 又是 IBookManagerInterface 的静态内部类。
这样编译器生成的代码对开发者并不好理解,但是
>Android 之所以这样设计其实是有道理的,因为当有多个 AIDL 文件的时候把 IBookManagerInterface、Stub、Proxy 放在同一个文件里能有效避免 Stub 和 Proxy 重名的问题。
6.1 各 Java 类职责描述
在正式编码实现跨进程调用之前,先介绍下实现过程中用到的一些类。了解了这些类的职责,有助于我们更好的理解和实现跨进程通信。
- IBinder : IBinder 是一个接口,代表了一种跨进程通信的能力。只要实现了这个借口,这个对象就能跨进程传输。
- IInterface : IInterface 代表的就是 Server 进程对象具备什么样的能力(能提供哪些方法,其实对应的就是 AIDL 文件中定义的接口)
- Binder : Java 层的 Binder 类,代表的其实就是 Binder 本地对象。BinderProxy 类是 Binder 类的一个内部类,它代表远程进程的 Binder 对象的本地代理;这两个类都继承自 IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder 驱动会自动完成这两个对象的转换。
- Stub : AIDL 的时候,编译工具会给我们生成一个名为 Stub 的静态内部类;这个类继承了 Binder, 说明它是一个 Binder 本地对象,它实现了 IInterface 接口,表明它具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要开发者自己实现。
6.2 IPC通信的Book对象
Book的对象需要在进程间通过Binder进行传输,那么Book类必须要实现Parcelable,就是将其序列化。
package com.demo.xzhangipcdemo;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
public String mBookName = "";
public String mBookAuthor = "";
public Book(String name, String author) {
this.mBookName = name;
this.mBookAuthor = author;
}
protected Book(Parcel in) {
mBookName = in.readString();
mBookAuthor = in.readString();
}
public static final Creator CREATOR = new Creator() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mBookName);
dest.writeString(mBookAuthor);
}
}
注:关于序列化的知识,可以看《Android中Serializable和Parcelable序列化》
6.3 AIDL
创建AIDL文件并不是最终的目的,目的是通过AIDL生成IInterface,Stub和代理Proxy。IBookManagerInterface.aidl中需要定义提供给client端Binder代理对象的方法。
另外因为Book类需要在进程中进行传输,所以要创建Book对应的AIDL
Book.aidl
package com.demo.xzhangipcdemo;
parcelable Book;
IBookManagerInterface.aidl
// IBookManagerInterface.aidl
package com.demo.xzhangipcdemo;
import com.demo.xzhangipcdemo.Book;
// Declare any non-default types here with import statements
interface IBookManagerInterface {
void addBook(in Book book);
List getBooks();
}
6.4 IInterface
有了AIDL文件,build完后会生成对应的IInterface,即IBookManagerInterface.java。如果只是会用,IBookManagerInterface不需要深入理解。
可以看出,Proxy 是 Stub 的静态内部类,Stub 又是 IBookManagerInterface 的静态内部类。所以将IBookManagerInterface拆成三部分来看比较好理解:
1. IBookManagerInterface
前面说过IInterface代表的就是服务端进程具体什么样的能力,IBookManagerInterface继承了IInterface,它提供了两个个能力:
addBook(Book book)
List getBooks()
2. Stub
只定义服务端具备什么样的能力是不够的,既然是跨进程调用,那么接下来我们得实现一个跨进程调用对象 Stub。Stub 继承 Binder, 说明它是一个 Binder 本地对象;实现 IInterface 接口,表明具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要调用方自己实现。
public static abstract class Stub extends Binder implements IBookManagerInterface {
private static final String DESCRIPTOR = "com.demo.xzhangipcdemo.IBookManagerInterface";
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
public static IBookManagerInterface asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof IBookManagerInterface))) {
return ((IBookManagerInterface) iin);
}
return new IBookManagerInterface.Stub.Proxy(obj);
}
@Override
public IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
Book _arg0;
if ((0 != data.readInt())) {
_arg0 = Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_getBooks: {
data.enforceInterface(DESCRIPTOR);
List _result = this.getBooks();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements IBookManagerInterface {
//Proxy
}
static final int TRANSACTION_addBook = (IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getBooks = (IBinder.FIRST_CALL_TRANSACTION + 1);
}
Stub 类中我们重点介绍下 asInterface()
和 onTransact()
。
asInterface()
先说说 asInterface(),当 Client 端在创建和服务端的连接,调用 bindService 时需要创建一个 ServiceConnection 对象作为入参。在 ServiceConnection 的回调方法 onServiceConnected 中会通过这个 asInterface(IBinder binder) 拿到 IBookManagerInterface对象,这个 IBinder 类型的入参 binder 是驱动传给我们的,正如你在代码中看到的一样,方法中会去调用 binder.queryLocalInterface() 去查找 Binder 本地对象,如果找到了就说明 Client 和 Server 在同一进程,那么这个 binder 本身就是 Binder 本地对象,可以直接使用。否则说明是 binder 是个远程对象,也就是 BinderProxy。因此需要我们创建一个代理对象 Proxy,通过这个代理对象来是实现远程访问。
MainActivity.java
private IBookManagerInterface mBookManagerInterface;
@Override
protected void onResume() {
super.onResume();
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBookManagerInterface = IBookManagerInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBookManagerInterface = null;
}
};
this.bindService(new Intent(this, BookManagerService.class), serviceConnection, Context.BIND_AUTO_CREATE);
}
onTransact()
onTransact()从字面来看是一个回调,在理解onTransact()之前需要理解Proxy,因为它是从这里回调的。
3. Proxy
Proxy是个代理类,既然是代理类自然需要实现 IBookManagerInterface 接口。
private static class Proxy implements IBookManagerInterface {
private IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
// ...
@Override
public void addBook(Book book) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = 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 List getBooks() throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
List _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
Proxy是直接和client进行通信的,也就是MainActivtiy中调用BookManagerInterface.addBook()或getBooks()是调用的Proxy的相应方法。
if (mBookManagerInterface != null) {
try {
mBookManagerInterface.addBook(new Book("test", "xzhang"));
} catch (RemoteException e) {
e.printStackTrace();
}
}
- 如果 Client 和 Server 在同一个进程,那么直接就是调用这个方法。
- 如果是远程调用,Client 想要调用 Server 的方法就需要通过 Binder 代理来完成,也就是上面的 Proxy。
我们这个例子中,当MainActivity调用addBook()和getBooks()需要经过三个步骤:
- 进入Proxy,在 Proxy 中的 addBook() 方法中首先通过 Parcel 将数据序列化,然后调用 remote.transact()。正如前文所述 Proxy 是在 Stub 的 asInterface 中创建,能走到创建 Proxy 这一步就说明 Proxy 构造函数的入参是 BinderProxy,即这里的 remote 是个 BinderProxy 对象。
- 通过一系列的函数调用,Client 进程通过系统调用陷入内核态,Client 进程中执行 addBook() 的线程挂起等待返回;驱动完成一系列的操作之后唤醒 Server 进程,调用 Server 进程本地对象的 onTransact()。
- 最终又走到了 Stub 中的 onTransact() 中,onTransact() 根据函数编号调用相关函数(在 Stub 类中为 BookManagerInterface 接口中的每个函数中定义了一个编号,在跨进程调用的时候,不会传递函数而是传递编号来指明要调用哪个函数)。在这个例子中,调用了 Binder 本地对象的 addBook() 传递Book对象,以及getBooks()将结果返回给驱动,驱动唤醒 Client 进程里刚刚挂起的线程并将结果返回。
这样一次跨进程调用就完成了。
6.5 Service
再回头来看Stub中的onTransact()
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
Book _arg0;
if ((0 != data.readInt())) {
_arg0 = Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_getBooks: {
data.enforceInterface(DESCRIPTOR);
List _result = this.getBooks();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
通过函数的编号,最终调用到this.addBook(book)和this.getBooks(),this其实是IBookManagerInterface,它的addBook()和getBooks()是抽象方法,具体实现在service中。
BookManagerService.java
package com.demo.xzhangipcdemo.server;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import com.demo.xzhangipcdemo.Book;
import com.demo.xzhangipcdemo.IBookManagerInterface;
import java.util.ArrayList;
import java.util.List;
public class BookManagerService extends Service {
@Override
public void onCreate() {
super.onCreate();
if (null == mBooks) {
mBooks = new ArrayList<>();
}
}
private List mBooks;
private IBinder mIBinder = new IBookManagerInterface.Stub() {
@Override
public void addBook(Book book) throws RemoteException {
if (null != mBooks) {
mBooks.add(book);
}
}
@Override
public List getBooks() throws RemoteException {
return mBooks;
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mIBinder;
}
}
Service的注册,Service运行在独立的demo进程:
<?xml version="1.0" encoding="utf-8"?>
OK,现在基本通了吧!
小结
以上就是通过AIDL实现进程间通信的流程分析,但是如果你只是想停留在会用的程度。只需要在创建AIDL文件后,编译生成IBookManagerInterface接口,接下来实现具体的Service就OK了。
完整的代码我放到 GitHub 上了,有兴趣的小伙伴可以去看看。源码地址:github.com/xzhang76/XzhangIPCDemo
最后建议大家在不借助 AIDL 的情况下手写实现 Client 和 Server 进程的通信,加深对 Binder 通信过程的理解。
共同学习,写下你的评论
评论加载中...
作者其他优质文章