IPC机制我写过三个系列:
android IPC机制(一)
android IPC机制(二)
android IPC机制(三)
基本上是从最简单的IPC讲起,从最初的Serializable & Parcelable,到Binder对象,最后到Messeager对象。今天主要是来总结一下AIDL。学习的过程是一个循序渐进的过程,以前有很多东西不了解,随着工作的深入,慢慢会有自己的体会和见解。
AIDL是什么?
AIDL全称是Andorid Interface Definition Language(Andorid接口定义语言),是Andorid中独有的IPC通信机制。在Andorid中,进程之间是无法相互通信的,AIDL的出现就是为了解决此类问题,并提供了统一的模板,使得此类复杂的问题变得清晰简单。我们只需要按照规定编写aidl接口,然后系统会自动给我们生成相应的java文件。
对于IPC进程间通信,我这里使用一张图来描述一下他们之间大致的关系:
我们的Client和Server属于不同的进程,通过bindService进行绑定,并返回AIDL对象作为通信的句柄,client进程通过AIDL对象,最终通过Binder对象完成信息传递到Server进程,Server进程也可以通过Binder对象将信息传递给Client进程,最终完成进程间通信,一下就是根据这个步骤来总结通信的。
1. 支持的数据类型
基本数据类型(int,long,char,boolean,double,char,byte,long);
String和CharSequence;
List:只支持ArrayList,里面有个元素都必须能够被AIDL支持;
Map:只支持HashMap,里面的每个元素都必须被AIDL,包括key和value;
Parcelable:所有实现了Paecelable接口;
AIDl:所有AIDL接口本身也可以在AIDL文件中使用。
自己自定义的Parcelable对象和AIDL对象必须要显示import进来,不管它们是否和当前的AIDL文件位于同一个包内。
出了基本类型之外,其他参数类型必须标上方向:in、out、inout。
in 表示输入型参数
out 表示输出型参数
inout 表示输入输出型参数
与传统的接口相比,aidl接口只支持方法,不支持静态常量
如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并将其声明为Parcelable类型。
2. 创建服务端和客户端
下面写一个简单例子来描述AIDL的工作流程:
我们首先创建了两个项目:aidlClient和aidlServer:
在aidlClient中,代码结构如下:
其中我们可以看出java代码中有Book
实体类,实现了android.os.Parcelable
接口,具体实现为:
public class Book implements Parcelable {
public String name ;
public Book(String name) {
this.name = name;
}
protected Book(Parcel in) {
name = in.readString();
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
'}';
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
}
}
同样我们创建AIDL文件BookAIDL.aidl
,定义addBook
和getBookList
两个方法:
// BookAIDL.aidl
package com.xinghe.aidlclient;
// Declare any non-default types here with import statements
import com.xinghe.aidlclient.entity.Book ;
import com.xinghe.aidlclient.OnNewBookArrivedListener ;
interface BookAIDL {
void addBook(in Book book);
List<Book> getBookList() ;
}
因为我们用到了Book这个实体类,所以我们也要实现Book这个aidl文件
//Book.aidl
package com.xinghe.aidlclient.entity;
parcelable Book ;
其中需要注意的是Book.adil的路径需要和src/main中路径一致:
最后我们需要复制Book.java
文件、Book.aidl文件和BookAIDL.aidl文件到服务端aidlServer端:
client端代码接口 | server端代码结构 |
---|---|
两端的代码结构已经清晰,现在需要make module一下,使系统自动为我们创建adil文件。自动生成的文件在:/projectName/build/generated/source/aidl/debug/packagename/aidl_file_name
3. 启动服务端,客户端请求
在aidlServer中,我们创建一个服务启动服务端:
class BookManagerService : Service() {
private val TAG: String = BookManagerService::class.java.simpleName
private val mBookList = CopyOnWriteArrayList<Book>()
override fun onCreate() {
super.onCreate()
Log.d(TAG, "BookManagerService service has bean created")
mBookList.add(Book("java"))
mBookList.add(Book("kotlin"))
}
override fun onBind(intent: Intent?): IBinder? {
return mBinder
}
private val mBinder = object : BookAIDL.Stub() {
override fun addBook(book: Book?) {
mBookList.add(book!!)
}
override fun getBookList(): MutableList<Book> {
return mBookList.toMutableList()
}
}
private fun whenNewBookComing(book: Book) {
}
在AndroidManifest.xml
中注册,并设置为可导出模式,因为需要被其他程序访问得到:
<service android:name="com.xinghe.aidlserver.BookManagerService"
android:exported="true"/>
然后找地方启动该Service:
fun startService(view: View) {
startService(Intent(this,BookManagerService::class.java))
}
此时需要注意的服务端包名和此Service名称,分别为:
//packname : com.xinghe.aidlserver
//serviceName : com.xinghe.aidlserver.BookManagerService
4. 客户端绑定
我们在客户端绑定服务端,并执行addBook
和getBookList
方法。由于Service不属于本程序,因此需要Component去绑定远程Service:
class MainActivity : AppCompatActivity() {
private var mBookAIDL: BookAIDL?=null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startBindService()
init()
}
private fun init() {
id_add_book.setOnClickListener {
if (null == mBookAIDL) {
Log.d("TAG","book aidl is null")
return@setOnClickListener
}
mBookAIDL!!.addBook(Book("newBook"))
}
id_get_book.setOnClickListener {
if(null == mBookAIDL) {
Log.d("TAG","book aidl is null")
}
Log.d("TAG","book list : ${mBookAIDL!!.bookList}")
}
}
//开始绑定远程的Service
private fun startBindService() {
val intent = Intent()
//启动第三方Service 使用Component
intent.component = ComponentName("com.xinghe.aidlserver","com.xinghe.aidlserver.BookManagerService")
val result = bindService(intent,serviceConnection, Context.BIND_AUTO_CREATE)
Log.d("bindService","bind result : $result")
}
private val serviceConnection = object:ServiceConnection{
override fun onServiceDisconnected(name: ComponentName?) {
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
mBookAIDL = BookAIDL.Stub.asInterface(service)
}
override fun onDestroy() {
unbindService(serviceConnection)
super.onDestroy()
}
5. 数据通信
我们首先需要启动server端,在server启动BookService服务,然后再启动客户端Service。
结果为:
可以看出,我们的服务端与客户端通信完成了,两个不同的程序之间完成了一次通信,也证明了aidl有效果了。
AIDL的数据监听
目标:如果服务端存在数据更新,那么客户端需要及时被通知。就相当于一般所说的监听器,但是在多进程中,无法使用普通的Interface,所以也需要创建一个类似接口的aidl文件。如果存在图书更新,那么此时我们创建一个图书更新的aidl文件接口。
//OnNewBookArrivedListener.aidl
package com.xinghe.aidlclient;
import com.xinghe.aidlclient.entity.Book ;
interface OnNewBookArrivedListener {
void whenNewBookArrived(in Book book);
}
此时我们的BookAIDL.aidl
需要更改一下:
// BookAIDL.aidl
package com.xinghe.aidlclient;
// Declare any non-default types here with import statements
import com.xinghe.aidlclient.entity.Book ;
import com.xinghe.aidlclient.OnNewBookArrivedListener ;
interface BookAIDL {
void addBook(in Book book);
List<Book> getBookList() ;
void registerBookArrivedListener(OnNewBookArrivedListener listener);
void unRegisterBookArrivedListener(OnNewBookArrivedListener listener);
}
新增了注册监听的方法和反注册的方法,然后替换掉旧的BookAIDL.aidl
文件,重新写入BookManagerService逻辑:
class BookManagerService : Service() {
private val TAG: String = BookManagerService::class.java.simpleName
private val mBookList = CopyOnWriteArrayList<Book>()
//监听器集合
private val mOnNewBookArrivedListenerList = CopyOnWriteArrayList<OnNewBookArrivedListener>()
private val mIsServiceDestroyed = AtomicBoolean(false)
override fun onCreate() {
super.onCreate()
Log.d(TAG, "BookManagerService service has bean created")
mBookList.add(Book("java"))
mBookList.add(Book("kotlin"))
//开一个子线程,子线程每5s添加一本书
startToWork()
}
override fun onBind(intent: Intent?): IBinder? {
return mBinder
}
private val mBinder = object : BookAIDL.Stub() {
//注册监听器
override fun registerBookArrivedListener(listener: OnNewBookArrivedListener?) {
if (!mOnNewBookArrivedListenerList.contains(listener)) {
mOnNewBookArrivedListenerList.add(listener)
}
Log.d("registerListener", "the current list size : ${mOnNewBookArrivedListenerList.size}")
}
//反注册监听器
override fun unRegisterBookArrivedListener(listener: OnNewBookArrivedListener?) {
if (mOnNewBookArrivedListenerList.contains(listener)) {
mOnNewBookArrivedListenerList.remove(listener)
}
Log.d("unRegisterListener", "the current list size : ${mOnNewBookArrivedListenerList.size}")
}
override fun addBook(book: Book?) {
mBookList.add(book!!)
}
override fun getBookList(): MutableList<Book> {
return mBookList.toMutableList()
}
}
//当新增Book时,通知到每一个监听器
private fun whenNewBookComing(book: Book) {
val size = mOnNewBookArrivedListenerList.size
for (i in 0 until size) {
mOnNewBookArrivedListenerList[i]?.whenNewBookArrived(book)
}
}
//子线程添加书籍
private fun startToWork() {
Thread() {
kotlin.run {
while (!mIsServiceDestroyed.get()){
try{
Thread.sleep(5000)
}catch (e:InterruptedException) {
e.printStackTrace()
}
val newBook = Book("new Book${System.currentTimeMillis()}")
mBookList.add(newBook)
try{
whenNewBookComing(newBook)
}catch (e: RemoteException){
e.printStackTrace()
}
}
}
}.start()
}
}
然后再客户端注册一下我们的监听器:
private fun startBindService() {
val intent = Intent()
//启动第三方Service 使用Component
intent.component = ComponentName("com.xinghe.aidlserver","com.xinghe.aidlserver.BookManagerService")
val result = bindService(intent,serviceConnection, Context.BIND_AUTO_CREATE)
Log.d("bindService","bind result : $result")
}
private val serviceConnection = object:ServiceConnection{
override fun onServiceDisconnected(name: ComponentName?) {
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
mBookAIDL = BookAIDL.Stub.asInterface(service)
//注册监听器
mBookAIDL?.registerBookArrivedListener(onNewBookArrivedListener)
}
}
override fun onDestroy() {
unbindService(serviceConnection)
//取消注册事件
if(mBookAIDL?.asBinder()?.isBinderAlive!!){
mBookAIDL?.unRegisterBookArrivedListener(onNewBookArrivedListener)
}
super.onDestroy()
}
//监听事件
private val onNewBookArrivedListener = object : OnNewBookArrivedListener.Stub() {
override fun whenNewBookArrived(book: Book?) {
Log.d("whenNewBookArrived","new book : $book")
}
}
重新启动server之后,在客户端启动之后,可以看到
可以看到我们的监听已经起作用了。
但是你退出时,本应该退出的,但是我们可以日志:
监听器中数据依旧还存在一个,意味着我们并没有unRegisterBookArrivedListener完成。
为什么呢?明明已经走到这一步了。这里的原因是因为我们此时用到的通信工具是Binder,Binder将客户端传递过来的对象重新转化并生成一个新的对象。虽然我们在注册和反注册过程中使用的是同一个客户端对象,但是通过Binder传递到服务端之后,却会产生新的对象。对象是不能跨进程传输,但是可以通过序列化/反序列化在进程之间传输。这也就是为什么AIDL中自定义对象都必须实现Parcelable接口的原因。
RemoteCallbackList
那么怎样才能实现正常的注册/反注册功能呢? 答案就是RemoteCallbackList。这是一个andorid系统的一个类,专门用于提供用于删除进程的listener接口。RemoteCallbackList是一个泛型,支持管理任意的AIDL接口,基本代码如下:
public class RemoteCallbackList<E extends IInterface>
工作原理很简单,在其内部有一个ArrayMap保存了所有的AIDL回调,这个Map的key是IBinder类型的,value是Callback类型的,但是Callback中封装了真正的listener:
private final class Callback implements IBinder.DeathRecipient {
final E mCallback;
final Object mCookie;
Callback(E callback, Object cookie) {
mCallback = callback;
mCookie = cookie;
}
public void binderDied() {
synchronized (mCallbacks) {
mCallbacks.remove(mCallback.asBinder());
}
onCallbackDied(mCallback, mCookie);
}
}
多次跨进程传输客户端的同一个对象在服务器端生成不同的对象,但是这些新生成的对象有一个共同点,那就是他们底层的Binder对象是同一个,按照这个特性,就可以实现我们注册/反注册AIDL接口的方案。当客户端反注册时,遍历服务端所有的listener,找出那个和注册具有相同的Binder对象的服务端listener,然后移除即可,这就是RemoteCallbackList为我们所有的事情。同事RemoteCallbackList还有一个很有用的功能,那就是客户端进程终止之后,它可以自动移除客户端所有注册的listener,原因是这个CallBack实现了IBinder.DeathRecipient,在客户端进程终止后,执行binderDied方法,该方法中会移除listener对象。
那么我们需要对Server的BookManagerService
做一下改进,具体如下:
private val mOnNewBookRemoteCallback = RemoteCallbackList<OnNewBookArrivedListener>()
private val mBinder = object : BookAIDL.Stub() {
//注册监听器
override fun registerBookArrivedListener(listener: OnNewBookArrivedListener?) {
//if (!mOnNewBookArrivedListenerList.contains(listener)) {
// mOnNewBookArrivedListenerList.add(listener)
//}
mOnNewBookRemoteCallback.register(listener)
Log.d("registerListener", "the current list size : ${mOnNewBookArrivedListenerList.size}")
}
//反注册监听器
override fun unRegisterBookArrivedListener(listener: OnNewBookArrivedListener?) {
//if (mOnNewBookArrivedListenerList.contains(listener)) {
// mOnNewBookArrivedListenerList.remove(listener)
//}
mOnNewBookRemoteCallback.unregister(listener)
Log.d("unRegisterListener", "the current list size : ${mOnNewBookArrivedListenerList.size}")
}
override fun addBook(book: Book?) {
mBookList.add(book!!)
}
override fun getBookList(): MutableList<Book> {
return mBookList.toMutableList()
}
}
然后需要更改的是监听通知方法whenNewBookComing
,具体如下:
private fun whenNewBookComing(book: Book) {
// val size = mOnNewBookArrivedListenerList.size
// for (i in 0 until size) {
// mOnNewBookArrivedListenerList[i]?.whenNewBookArrived(book)
// }
val size = mOnNewBookRemoteCallback.beginBroadcast()
for (i in 0 until size) {
mOnNewBookRemoteCallback.getBroadcastItem(i)?.whenNewBookArrived(book)
}
mOnNewBookRemoteCallback.finishBroadcast()
}
好了,大功告成了,重新启动Server,此时Client不需要做任何改动只需要重新绑定即可,然后我们就可以注册和反注册我们的listener了。