好的,由于上周拖更,本次带来Android进程通信详解–AIDL,虽然网上关于这个内容的资料丰富且详细,但一千个读者就有一千个哈姆雷特不是嘛,对于任何东西都要有自己的见解。
关于AIDL,Service就是我们不得不提的东西了,它是Android四大组件之一,太基础的东西这里我们就不再赘述,下面还是给出一些我对于Service的认识:
上图为Service的生命周期
从图里不难看出,Service有两种启动方式: ①用startservice启动服务,第一次会调用oncreate–>onStartCommand,而第二次就会直接到onStartCommand ②用bindservice启动服务,第一次会调用oncreate–>onBind,后面直接到onBind 注意: 如果即startservice又bindservice了,那么单独stopservice和unBindService都没有效果,必须两个也都执行。
没有必然关系,service和activity一样运行于主线程,只是他没有view不能被我们所看见。 所以如果我们在service里执行长时间耗时操作是会导致ANR的,这里我就不做演示,大家可以自行检测。 然而,当我们给service注册时,加入process标签,加入:remote属性,这时这个service就成为了远程服务,它就和我们的activity处于截然不同的两个进程中,这个时候再执行耗时操作并不会让我们的程序报ANR,应该两个进程的执行并不会相互影响。与此同时,如果之前我们使用的binder是本地服务的binder,这个时候我们再次bindservice就会导致程序崩溃,是因为service已经是远程服务了,而binder还是本地binder,不同的两个进程自然不能相互调用。这个时候就会需要我们用到android的IPC机制的AIDL了。下文详述。
Binder是Android中一个很重要且很复杂的概念,它在系统的整体运作中发挥着极其重要的作用,不过我并不打算从深层次分析Binder机制,有两点原因:1是目前网上关于Binder无论从底层到应用层的有关解析文章实在是太多了,2是对Binder机制进行深入底层乃至驱动的分析这一过程相当困难且相当耗时,而且我也没那个实力和精力去解读底层代码(很痛苦)。所以我只给大家给出一些总领性的分析,和几篇关于Binder分析的很好的文章,希望读者们能从这些文章里找到你们想要的答案!
什么是Binder
直观来说,Binder是Android中的一个类,它继承了IBinder接口从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,etc)和相应ManagerService的桥梁从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当你bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务 Android需要建立一套新的IPC机制来满足系统对通信方式,传输性能和安全性的要求,这就是Binder。Binder基于 Client-Server通信模式,传输过程只需一次拷贝,为发送发添加UID/PID身份,既支持实名Binder也支持匿名Binder,安全性高。Binder的工作机制图(来源:Android开发艺术探索):
在写出用法之前,必须理清楚几个概念,这对于后文的理解非常重要: 进程:一个程序或一个应用或后台,占有内存空间,是一个执行单元。 线程:CPU调度的最小单元,多个线程可存在于一个进程中。 在安卓应用中,一个应用的不同组件,如果他们运行在不同进程中,那么和它们分别属于两个应用没有本质区别,关于这点需要深刻理解,这也是进程通信的基础。 下文的例子使用的就是activity和service进行通信,给service单独设置了进程:
<service android:name="com.example.dosomeinterestingthings.WxjAidlService" android:process=":remote"> </service>1、创建一个service用来监听客户端的连接请求。 2、创建AIDL文件将暴露给客户端接口在这个AIDL文件中声明。 3、在service中实现这个AIDL接口即可。
1、绑定服务端的service。 2、成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就能调用方法了。
这里给大家抱歉一下,我两个电脑的Android studio都坏了,不能给大家贴AS的创建步骤,待我修复问题后,再补上AS版的,这里先用eclipse讲解过程。(^__^) 嘻嘻……
上图为项目结构
创建步骤: 1、 创建com.example.aidl包 2、 在包内创建Book.java、Book.aidl、IWxjAidl.aidl三个文件 3、 Build project(执行这步后才会有gen文件夹里自动生成的IWxjAidl.java这个文件) 注意第三步需要选Eclipse的工具栏的project项的Build project
Book类:
public class Book implements Parcelable{ public int bookId; public String bookName; public Book(int bookId,String bookName){ this.bookId = bookId; this.bookName = bookName; } //反序列构造器 public Book(Parcel source) { // TODO Auto-generated constructor stub bookId = source.readInt(); bookName = source.readString(); } @Override public int describeContents() { // TODO Auto-generated method stub return 0; } @Override public void writeToParcel(Parcel dest, int flags) { // TODO Auto-generated method stub dest.writeInt(bookId); dest.writeString(bookName); } //反序列化 public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() { @Override public Book createFromParcel(Parcel source) { // TODO Auto-generated method stub return new Book(source); } @Override public Book[] newArray(int size) { // TODO Auto-generated method stub return new Book[size]; } }; }注意:
使用AIDL进行进程通信,所有的信息都要经过序列化和反序列化,也就意味着AIDL并不是支持所有类型的数据的。 一般情况,AIDL支持如下: 1、 基本数据类型(int、long、char、boolean、double等) 2、 String和CharSequence 3、 List:ArrayList,内部元素也需要被AIDL支持 4、 Map:HashMap,内部元素包括Key都要被AIDL支持 5、 Parcelable:所有实现Parcelable接口的对象 6、 AIDL:AIDL接口本身也可以在AIDL里声明 自定义的Parcelable对象和AIDL对象必须要显示的import进来
这样,我们需要的接口aidl和系统生成的对应java文件我们就全部生成好了。
用service实现AIDL接口:
public class WxjAidlService extends Service { private static final String TAG = "wxj"; //支持并发读写的list private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>(); private Binder mBinder = new IWxjAidl.Stub(){ @Override public List<Book> getBooKList() throws RemoteException { // TODO Auto-generated method stub return mBookList; } @Override public void addBook(Book book) throws RemoteException { // TODO Auto-generated method stub mBookList.add(book); } }; @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return mBinder; } @Override public void onCreate() { // TODO Auto-generated method stub super.onCreate(); mBookList.add(new Book(1,"Android")); mBookList.add(new Book(2,"wxj")); } }这里采用了CopyOnWriteArrayList,它支持并发读写操作。注意前文我们说到AIDL支持的List只有ArrayList,但这里我们用CopyOnWriteArrayList是因为binder的底层机制是在线程池里执行的,所以会存在多个客户端多个线程同时访问的情况,而且AIDL所支持的是抽象的List而List只是一个接口,在Binder中会按照List的规范去访问数据并最终形成一个ArrayList传递给客户端,所以采用CopyOnWriteArrayList是完全可以的,类似的情况还有ConcurrentHashMap。
在activity里绑定service
public class MainActivity extends Activity { private static final String TAG = "MainActivity"; private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { } @Override public void onServiceConnected(ComponentName name, IBinder service) { //实例化Stub里的Proxy对象 IWxjAidl wxj = IWxjAidl.Stub.asInterface(service); try { List<Book> list = wxj.getBooKList(); Log.i(TAG, "book list , list type:"+list.getClass().getCanonicalName()); Log.i(TAG, "book list:" + list.toString()); //add a new book Book newbook = new Book(3,"xiyouji"); wxj.addBook(newbook); Log.i(TAG, "book list:" + wxj.getBooKList().toString()); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //bindservice Intent intent = new Intent(this,WxjAidlService.class); bindService(intent, conn, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { // TODO Auto-generated method stub unbindService(conn); super.onDestroy(); } }运行log结果如下:
这样就完成了一次完整AIDL进行进程通信的过程了。
可是这还远远不够,上面的所有工作,我们都是客户端主动去请求远程,调用方法去获取远程的信息,那么我们能不能在远程里给出接口,每当有书添加进来的时候就让客户端选择是否需要接收这些信息呢?答案当然是肯定的,我们接着分析:
这里我们要使用的就是一种典型的观察者模式,每个感兴趣的用户都观察新书,每当有新书加入的时候,远程就告诉用户,这时我们需要在AIDL里提供一个接口,每个用户可以实现这个接口来接收远程的提醒,当然用户也可以随时取消这种提醒。这里我们创建INewBookListener.aidl文件,里面有新书添加方法:
package com.example.aidl; import com.example.aidl.Book; interface INewBookListener{ void onNewAddBook(in Book book); }同样的我们build project步骤同上文所述,这样我们就能得到INewBookListener.java这个映射文件
然后,我们修改上一个AIDL文件,给出两个方法让用户选择是否接收这个提醒功能的函数
//观察者模式 用户决定是否需要接受新的书信息 void accpetListener(INewBookListener listener); void unaccpetListener(INewBookListener listener);再build project,然后来到我们的service类,注册这个提醒,并且我们启动一个线程让它5S一次自动添加一本书
public class WxjAidlService extends Service { ······ // 标识位 private static final boolean flag = false; private CopyOnWriteArrayList<INewBookListener> mListenerList = new CopyOnWriteArrayList<INewBookListener>(); private Binder mBinder = new IWxjAidl.Stub() { ······ @Override public void accpetListener(INewBookListener listener) throws RemoteException { // TODO Auto-generated method stub if (!mListenerList.contains(listener)) { mListenerList.add(listener); } else { Log.e(TAG, "has added!"); } } @Override public void unaccpetListener(INewBookListener listener) throws RemoteException { // TODO Auto-generated method stub if (mListenerList.contains(listener)) { mListenerList.remove(listener); } else { Log.e(TAG, "not found!"); } } }; ······ @Override public void onCreate() { // TODO Auto-generated method stub super.onCreate(); mBookList.add(new Book(1, "Android")); mBookList.add(new Book(2, "wxj")); // 开启五秒自动添加线程 new Thread(new ServiceWorker()).start(); } private class ServiceWorker implements Runnable { @Override public void run() { // TODO Auto-generated method stub while (!flag) { try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } int bookId = mBookList.size() + 1; Book newBook = new Book(bookId, "new Book create" + bookId); try { onNewAddBook(newBook); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } public void onNewAddBook(Book book) throws RemoteException { mBookList.add(book); Log.d(TAG, "new book add,listener" + mListenerList.size()); for (int i = 0; i < mListenerList.size(); i++) { INewBookListener listener = mListenerList.get(i); Log.d(TAG, "notify listener:" + listener); listener.onNewAddBook(book); } } ······ }这么一来,service里面我们给出了添加新书的方法了,然后我们在activity里面只要注册这个提醒功能,并且用handler将信息从binder线程池提取到主线程里显示就行了,还有,我们需要在activity销毁的时候,取消这个提醒
private INewBookListener mINewBookListener = new INewBookListener.Stub() { @Override public void onNewAddBook(Book book) throws RemoteException { // TODO Auto-generated method stub mHandler.obtainMessage(1,book).sendToTarget(); } }; wxj.accpetListener(mINewBookListener); private Handler mHandler = new Handler(){ public void handleMessage(android.os.Message msg) { switch (msg.what) { case 1: Log.d(TAG, "get a new book"+msg.obj); break; default: break; } }; }; protected void onDestroy() { // TODO Auto-generated method stub if (mRemote!=null&&mRemote.asBinder().isBinderAlive()) { try { //activity销毁时,取消这个提醒 mRemote.unaccpetListener(mINewBookListener); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } } unbindService(conn); super.onDestroy(); }下图是运行结果:
和我们预想的一样,每5S添加了一本书,并且用户也能正确得到提醒。但是,当我们用back键销毁activity时,却得到了如下结果:
我注销了这个提醒,但是,之后还能收到这个提醒,这就很神奇了我第一次也非常纳闷,为什么我们明明打印了注销日志,却还是可以收到提醒,然而形成这个场景的原因我们还是得从binder的转换机制说起,因为对象在跨进程过程里是不能直接传输的,binder会把客户端传过来的listener对象进行序列化和反序列化,这样一来,即使我们在activity里注册的listener和注销的listener是同一对象,但是经过binder传输之后就会产生两个完全不一的对象,这就是为什么AIDL定义的对象都必须实现Parcelable的原因,那么我们如何真正的去注销这个提醒功能呢?
下面我们就来使用RemoteCallBackList:
查看API,我们可以发现这个类是系统专门提供的用于删除跨进程listener的接口。RemoteCallBackList是一个泛型,支持管理任意的AIDL接口 在它内部有一个Map专门用来保存所有的AIDL回调,这个Map的key是IBinder类型,value是CallBack类型
ArrayMap<IBinder,CallBack> mCallBacks = new ArrayMap<IBinder,CallBack>(); //CallBack封装了真正的远程listener IBinder key = listener.asBinder() Callback value = new Callback(listener,cookie)这样的话,虽然经过binder传输的listener是不同的,但是他们底层的binder却是同一个对象,当用户需要注销的时候,我们只要遍历远程的listener,找出和需要注销的listener具有相同的binder的key的listener并删掉就可以了,并且这个类还帮我们完成了线程同步和自动注销的功能,真是省事省心。 然后,我们进行代码修改:
private RemoteCallbackList<INewBookListener> mListenerList = new RemoteCallbackList<>(); @Override public void accpetListener(INewBookListener listener) throws RemoteException { // TODO Auto-generated method stub mListenerList.register(listener); } @Override public void unaccpetListener(INewBookListener listener) throws RemoteException { // TODO Auto-generated method stub mListenerList.unregister(listener); } public void onNewAddBook(Book book) throws RemoteException { mBookList.add(book); int n = mListenerList.beginBroadcast(); for(int i = 0;i<n;i++){ INewBookListener l = mListenerList.getBroadcastItem(i); if(l!=null){ l.onNewAddBook(book); } } mListenerList.finishBroadcast(); }这次运行结果就正常了,注销之后,我们再也收不到提醒消息了!
到这里,AIDL的基本使用方法就介绍完了,但是还有一些需要我们注意的点,下面给大家说明。
到这里,关于AIDL你所需要知道的一切基本讲述完了,希望读者能从中获益,也可以欢迎各位指出文章里的错误!