IPC基础

Android 系统中充斥着各种IPC。常见的例子:对系统状态栏的控制(StatusBarNavigationBar)、对键盘的控制等等。了解IPC 的机制,对于Android 源码的理解也可以加深。以前有做过一些简单的aidl 的IPC,做一个简单回顾。

多进程回顾

进程和线程的区别?进程是一个执行单元,而线程是一个调度的单元。形象一点,一个进程代表了一个app,一个app 运行的时候会有一个主线程(UI线程)以及其它的一些线程,比如耗时操作放在其它线程等。
在一个app(进程)中处理的时候,更多的时候考虑的是线程同步(使用synchronized、锁等等)。在多个app (进程)之间处理的时候就需要进程间通信了。举个例子,做了一个用来清理内存的app(单独app)。现在做了一个新的app 想调用,就可以使用进程间通信了。

简单aidl调用流程

1.书写aidl文件,build以后会生成java文件。

1
2
3
4
5
6
7
interface IAddAidl {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
int add(int addA, int abbB);
}

2.创建自己的Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class AddService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
IAddAidl.Stub mBinder = new IAddAidl.Stub() {
@Override
public int add(int addA, int addB) throws RemoteException {
return addA + addB;
}
};
}

3.绑定Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
IAddAidl mIAddAidl;
ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mIAddAidl = IAddAidl.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mIAddAidl = null;
}
};
bindService(new Intent(this, AddService.class), mConnection, Context.BIND_AUTO_CREATE);

4.调用

1
mIAddAidl.add(1, 1);

分析aidl的调用

写完.aidl以后,build一下就会自动生成.java文件。从上面的流程来看,绑定Service的时候可以得到相应的IAddAidl对象,最后调用的也是IAddAidl对象。

1.绑定Service的时候得到的IAddAidl是什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// IAddAidl.java
/**
* Cast an IBinder object into an com.egos.samples.aidl.IAddAidl interface, generating a
* proxy if needed.
*/
public static IAddAidl asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
// 通过传过来的IBinder对象来获取IInterface对象
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.egos.samples.aidl.IAddAidl))) {
return ((IAddAidl) iin);
}
// 如果传过来的IBinder对象不满足的时候就会创建一个Proxy
return new IAddAidl.Stub.Proxy(obj);
}

注意这里有一个DESCRIPTOR,代表的是Binder的唯一标志符。bindService回调onServiceConnected返回的IBinder对象,如果ServiceActivity(或者Application)在同一个进程,则返回的是Service.onBind返回的对象,不在同一个进程中返回的是BinderProxy对象。即如果ClientServer运行在不同的进程,则需要创建Proxy对象。

2.创建Proxy的逻辑,Proxy实现了IAddAidl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private static class Proxy implements IAddAidl {
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 int add(int addA, int abbB) 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);
_data.writeInt(addA);
_data.writeInt(abbB);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}

3.最终调用的是Proxy.add()方法。
add方法中最终调用的是mRemote.transact,传递了4个参数,分别是区分需要调用方法的一个id、方法所需要的参数、方法返回的结果以及一个flags。Transact过程就是一个进程间通信。

问题

1.aidl支持的数据类型。

  • Java的原生类型(不包含short,android.os.Parcel不支持)
  • CharSequence和String
  • List和Map(里面的元素必须备aidl支持)
  • Parcelable,实现了Parcelable的对象
  • aidl,aidl接口本身可以在aidl文件中使用。

然后上面还有问题,除了Java的原生类型以外,其它的类型都需要标上方向,in、out或者inout。(测试发现aidl本身也可以不写明)

in表示输入型参数(Server可以获取到Client传递过去的数据,但是不能对Client端的数据进行修改)
out表示输出型参数(Server获取不到Client传递过去的数据,但是能对Client端的数据进行修改)
inout表示输入输出型参数(Server可以获取到Client传递过去的数据,但是能对Client端的数据进行修改)。

举个例子。

1
2
void test2(Map map); // 错误
void test2(in Map map); // 正确

aidl中导入Parcelable对象问题。
(1).需要在aidl目录下先创建aidl文件

1
2
3
// Person.aidl
package com.egos.samples.aidl;
parcelable Person;

(2).在Java文件下创建Person类,注意需要实现Parcelable接口。

进阶

客户端和服务端

Android 的Binder进程间通信,有一个概念是可以把进程间通信看成是客户端和服务端通信。在了解了aidl 的基本用法以后,那么客户端和服务端分别在哪?(这对看framework 的代码非常的重要,不然可能看着就不知道是应用层的调用还是服务端的实现了)
还是看分析aidl的调用中分析的方法asInterface,这个是用来用来将服务端的Binder转化成客户端AIDL 接口类型的对象。当传入的对象不是客户端认识的对象时(此时就代表了是其它进程的对象了),此时会返回Proxy对象,即Proxy为客户端。
1.Proxy#xx()这个代表的是客户端的调用。主要看_data.writeInt(addA);_data.writeInt(abbB);将客户端调用参数写入_data 中,mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);调用服务端方法,最后_result = _reply.readInt();得到返回数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public int add(int addA, int abbB) 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);
_data.writeInt(addA);
_data.writeInt(abbB);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}

2.Stub#onTransact运行在服务端的Binder线程,意思也就是这个是运行在服务端的。查看case TRANSACTION_add:中代码,_arg0 = data.readInt();获取第一个参数,_arg1 = data.readInt();获取第二个参数。int _result = this.add(_arg0, _arg1);调用实现的方法。reply.writeInt(_result);写入数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// IAddAidl.Stub
@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_add: {
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.add(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
if ((_arg2 != null)) {
reply.writeInt(1);
_arg2.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
Binder 线程池

Binder通信时的Thread问题。客户端通过Binder 调用服务端的方法时,方法会运行在服务端的Binder 线程池中,此时客户端会阻塞。如果是在主线程中调用需要注意ANR 的问题。反过来一样。

双向通信

客户端也可以做服务端为服务端提供服务,同理服务端也可以做客户端调用相应的服务。可以参看Parcel#writeStrongBinderParcel#readStrongBinder