Android 系统中充斥着各种IPC。常见的例子:对系统状态栏的控制(StatusBar
和NavigationBar
)、对键盘的控制等等。了解IPC 的机制,对于Android 源码的理解也可以加深。以前有做过一些简单的aidl 的IPC,做一个简单回顾。
多进程回顾
进程和线程的区别?进程是一个执行单元,而线程是一个调度的单元。形象一点,一个进程代表了一个app,一个app 运行的时候会有一个主线程(UI线程)以及其它的一些线程,比如耗时操作放在其它线程等。
在一个app(进程)中处理的时候,更多的时候考虑的是线程同步(使用synchronized、锁等等)。在多个app (进程)之间处理的时候就需要进程间通信了。举个例子,做了一个用来清理内存的app(单独app)。现在做了一个新的app 想调用,就可以使用进程间通信了。
简单aidl
调用流程
1.书写aidl
文件,build
以后会生成java
文件。
2.创建自己的Service
。
3.绑定Service
。
4.调用
分析aidl
的调用
写完.aidl
以后,build
一下就会自动生成.java
文件。从上面的流程来看,绑定Service
的时候可以得到相应的IAddAidl
对象,最后调用的也是IAddAidl
对象。
1.绑定Service
的时候得到的IAddAidl
是什么。
|
|
注意这里有一个DESCRIPTOR
,代表的是Binder
的唯一标志符。bindService
回调onServiceConnected
返回的IBinder
对象,如果Service
和Activity
(或者Application
)在同一个进程,则返回的是Service.onBind
返回的对象,不在同一个进程中返回的是BinderProxy
对象。即如果Client
和Server
运行在不同的进程,则需要创建Proxy
对象。
2.创建Proxy
的逻辑,Proxy
实现了IAddAidl
。
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端的数据进行修改)。
举个例子。
|
|
aidl
中导入Parcelable
对象问题。
(1).需要在aidl
目录下先创建aidl
文件
|
|
(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();
得到返回数据。
|
|
2.Stub#onTransact
运行在服务端的Binder
线程,意思也就是这个是运行在服务端的。查看case TRANSACTION_add:
中代码,_arg0 = data.readInt();
获取第一个参数,_arg1 = data.readInt();
获取第二个参数。int _result = this.add(_arg0, _arg1);
调用实现的方法。reply.writeInt(_result);
写入数据。
|
|
Binder 线程池
Binder
通信时的Thread
问题。客户端通过Binder 调用服务端的方法时,方法会运行在服务端的Binder 线程池中,此时客户端会阻塞。如果是在主线程中调用需要注意ANR 的问题。反过来一样。
双向通信
客户端也可以做服务端为服务端提供服务,同理服务端也可以做客户端调用相应的服务。可以参看Parcel#writeStrongBinder
和Parcel#readStrongBinder
。