写文章已经4周年了,虽然中间很多时候都在偷懒,但也写了100篇文章,虽然回过头看有些文章写得好像自己现在也很嫌弃。但是确实怎么长时间的偷懒并坚持着,让我感觉确实有点欣慰,虽然这个赞啊阅读量啊这些都不高,但是我相信坚持下去肯定会有收获,至少不去努力什么都不会有。
既然是4周年,那就趁着这时候,赏自己一篇文章。我看平时开发也没涉及到复杂的夸进程通信,但是我两年前是有看过Binder机制的,但是我没笔记,所以导致了,我现在要重新去看,我又会浪费很多时间,所以论写文章的重要性,看自己的写的文章更能恢复记忆。
既然基本全忘了,那就大不了重头再来一遍嘛。
一. AIDL的Demo
写个Demo用AIDL来实现夸进程通信,我创建两个module
aidldemo充当客户端,binderservicedemo充当服务端。
1. 定义AIDL
在main下创建个aidl文件夹,然后创建个AIDL文件。
这个东西有什么用呢?他就是个接口,有一定编程经验的人都知道,就是面向接口编程的一种感觉,这个我不知道怎么解释,就是引用的时候,我们是对接口去操作,而不是对实体类,一般开发都是这样。
相当于我客户端操作你服务端,就是通过这个接口去操作,你接口告诉我你有哪些行为,我去某个方法,具体的实现你内部自己做,我不管。这个接口就是这样一个效果,所以,两端都要持有这个接口的引用,这个aidl就要在两端都存在
包名和类名都要一样,因为是同一个对象。一个小技巧,我这是演示所以这样写,实际不会这样,不然你两个项目都有重复内容,可以单独抽出来,然后两个项目在gradle去引用。
interface IMyBook {
// 定义一个Book接口,有个返回String的方法
String getBookName(int count);
}
2. 服务端
写个服务端的代码
public class BookService extends Service {
@Override
public IBinder onBind(Intent intent) {
Log.v("mmp", "service onBind");
return new BookBinder();
}
protected class BookBinder extends IMyBook.Stub{
@Override
public String getBookName(int count) throws RemoteException {
// todo 处理逻辑
if(count < 18){
return "Study";
}
return "YouKnow";
}
}
}
就很简单,这里实现接口,然后详细写getBookName方法的实现,客户端绑定这个Service的时候会调用onBind方法,就返回一个IMyBook的实体对象。
在manifest中注册服务
<service android:name="com.kylim.binderservicedemo.BookService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BookService" />
</intent-filter>
</service>
3. 客户端
public class MainActivity extends Activity {
private IMyBook book;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn = findViewById(R.id.btn);
initService();
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
String result = book.getBookName(19);
Log.v("mmp", "跨进程获取结果:"+result);
} catch (Exception e) {
e.printStackTrace();
Log.v("mmp", "跨进程获取结果是失败:"+e.getMessage());
}
}
});
}
private void initService(){
Intent intent = new Intent("android.intent.action.BookService");
intent.setPackage("com.kylim.binderservicedemo");
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.v("mmp", "onServiceConnected");
// todo 绑定
book = IMyBook.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.v("mmp", "onServiceDisconnected");
// todo 解绑
}
}, BIND_AUTO_CREATE);
}
}
然后先运行服务的应用,再运行客户端的应用,点按钮就能实现夸进程通信了
可以看到是不同的进程。
二. AIDL的生成
上面Demo那就很简单,就是A进程和B进程都引用接口I,A通过I去和B通信,没什么好讲的。但是AIDL其实是Android提供的工具,是为了方便开发而用,它把整个过程抽象成一个接口,但是具体的他会在编译时生成java代码。
我之前写过一篇文章是手动编译https://www.jianshu.com/p/f811f36e2997,看里面的编译流程图
他是通过sdk的这个工具去操作的,那篇文章我没写,但是原理一样,敲命令去实现
我们可以看看生成的java代码
public interface IMyBook extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.kylim.aidldemo.IMyBook
{
private static final java.lang.String DESCRIPTOR = "com.kylim.aidldemo.IMyBook";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.kylim.aidldemo.IMyBook interface,
* generating a proxy if needed.
*/
public static com.kylim.aidldemo.IMyBook asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.kylim.aidldemo.IMyBook))) {
return ((com.kylim.aidldemo.IMyBook)iin);
}
return new com.kylim.aidldemo.IMyBook.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
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getBookName:
{
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
java.lang.String _result = this.getBookName(_arg0);
reply.writeNoException();
reply.writeString(_result);
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.kylim.aidldemo.IMyBook
{
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 getBookName(int count) 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);
_data.writeInt(count);
mRemote.transact(Stub.TRANSACTION_getBookName, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_getBookName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public java.lang.String getBookName(int count) throws android.os.RemoteException;
}
这个代码,你也可以不用aidl,可以手敲,就是说aidl帮我们生成,所以很方便。
可以看出,客户端是通过Stub的asInterface方法去获取到这个接口的
public static com.kylim.aidldemo.IMyBook asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.kylim.aidldemo.IMyBook))) {
return ((com.kylim.aidldemo.IMyBook)iin);
}
return new com.kylim.aidldemo.IMyBook.Stub.Proxy(obj);
}
这里就有意思了,他先调用传进来的IBinder去查这个接口的一个标识:DESCRIPTOR。如果有的话就返回,否则创建一个Proxy。
一步步来,IBinder的实现类是Binder,看它的queryLocalInterface方法
public IInterface queryLocalInterface(String descriptor) {
if (mDescriptor != null && mDescriptor.equals(descriptor)) {
return mOwner;
}
return null;
}
这两个参数都是attachInterface方法来的
public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
mOwner = owner;
mDescriptor = descriptor;
}
这个方法是Stub的构造方法调用的
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
看上面服务端BookService的代码,构造方法就是在那里new的时候调用的。
这说明什么,说明是同一个对象的时候,说明你在onServiceConnected方法中获取的IBinder是你在Service的onBind中return的对象的时候,这两个参数才对得上。但是我Demo里面这两个地方是同一个对象吗?不是,为什么,因为他们属于不同的进程,怎么去知道他们两个是不是同一个对象,那就需去看onBind方法怎么去调用到onServiceConnected这个回调的,这篇文章就先不讲,总之先简单了解不是同个对象。
既然不是同个对象,那就走return new com.kylim.aidldemo.IMyBook.Stub.Proxy(obj) 这行代码
private static class Proxy implements com.kylim.aidldemo.IMyBook
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public java.lang.String getBookName(int count) 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);
_data.writeInt(count);
mRemote.transact(Stub.TRANSACTION_getBookName, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
看得出,代码中onServiceConnected回调里面,IMyBook.Stub.asInterface(service)方法返回的是这个Proxy 对象,这是一个代理对象。
这又要涉及到代理模式了,简单的这样想,我进程A要操作进程B,但是他们之间的通信很复杂,我封装这一块内容,抽象当成,我进程A调用Proxy 代理,Proxy 做了一些列复杂操作,把消息传给进程B,我A不管你做什么操作, 反正有什么我就把这事交给你,剩下的你自己内部解决。就像你的领导给你需求,他管你怎么处理,他只管结果。
当按按钮时就调用到这个代理的getBookName方法。
嗯?难道要翻车?直接看源码,到这步卡住了,我保证没做过功课,一直跟着源码走,到这步有点卡住了,有点看不懂这里面做的是什么骚操作
但是不要慌,一点一点来分析。
首先这个obtain()和recycle()方法,就是一个复用池,复用这个Parcel对象,这是android代码里面常用的做法,Message也是这样玩的,看多代码就知道了,套路都一样。
还能看出_data相当于Request,_result相当于Response。Parcel,一看就是和序列化有关的,那核心的代码就是
mRemote.transact(Stub.TRANSACTION_getBookName, _data, _reply, 0);
看看Binder的transact方法
public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);
if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}
这个if (false)打印是什么操作,测试的没来得及删吗。接着往下看onTransact
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply,
int flags) throws RemoteException {
if (code == INTERFACE_TRANSACTION) {
reply.writeString(getInterfaceDescriptor());
return true;
} else if (code == DUMP_TRANSACTION) {
ParcelFileDescriptor fd = data.readFileDescriptor();
String[] args = data.readStringArray();
if (fd != null) {
try {
dump(fd.getFileDescriptor(), args);
} finally {
IoUtils.closeQuietly(fd);
}
}
// Write the StrictMode header.
if (reply != null) {
reply.writeNoException();
} else {
StrictMode.clearGatheredViolations();
}
return true;
} else if (code == SHELL_COMMAND_TRANSACTION) {
ParcelFileDescriptor in = data.readFileDescriptor();
ParcelFileDescriptor out = data.readFileDescriptor();
ParcelFileDescriptor err = data.readFileDescriptor();
String[] args = data.readStringArray();
ShellCallback shellCallback = ShellCallback.CREATOR.createFromParcel(data);
ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(data);
try {
if (out != null) {
shellCommand(in != null ? in.getFileDescriptor() : null,
out.getFileDescriptor(),
err != null ? err.getFileDescriptor() : out.getFileDescriptor(),
args, shellCallback, resultReceiver);
}
} finally {
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(out);
IoUtils.closeQuietly(err);
// Write the StrictMode header.
if (reply != null) {
reply.writeNoException();
} else {
StrictMode.clearGatheredViolations();
}
}
return true;
}
return false;
}
看错了,不好意思,这里是调用onTransact没错,但是子类有重写这个方法,所以应该是先看Stub中的onTransact,因为onBind中传过来的是IMyBook的Stub,没看懂的可以自己多看几遍,确实有点绕。
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getBookName:
{
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
java.lang.String _result = this.getBookName(_arg0);
reply.writeNoException();
reply.writeString(_result);
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
在getBookName中传入的code是TRANSACTION_getBookName,所以走这个判断
case TRANSACTION_getBookName:
{
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
java.lang.String _result = this.getBookName(_arg0);
reply.writeNoException();
reply.writeString(_result);
return true;
}
这个是服务端的对象,所以调用getBookName方法就是执行了服务端那边的getBookName方法。
是有点绕啊,Proxy对象是属于客户端进程的,但他内部的mRemote是属于服务端的对象。
他们之间的引用关系 客户端进程->Proxy->mRemote; mRemote->服务端进程
从源码可以看出,AIDL就是定义了通信的接口,真正的底层通信,还是Binder机制来实现的,所以“AIDL是多进程通信的方法”,这句话应该是错的把,真正做通信的应该是Binder,是Demo中onBind方法和onServiceConnected回调他们之间的联系。后面会写一篇Binder机制的源码分析。我是一面看源码一面写这篇文章的,如果有写得不对的地方,希望能指出,我再找个机会认真看一遍。