[TOC]
概述 使用Messenger来进行进程间通信的方法,可以发现,Messenger是以串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个个处理,如果有大量的并发请求,那么用Messenger就不太合适了。
同时,Messenger的作用主要是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情形用Messenger就无法做到了,但是我们可以使用AIDL来实现跨进程的方法调用。AIDL也是Messenger的底层实现,因此Messenger本质上也是AIDL,只不过系统为我们做了封装从而方便上层的调用而已
所以,下面我们重点学习一下AIDL的基础知识。
什么是AIDL AIDL(Android Interface Definition Language) Android接口定义语言 利用它定义客户端与服务均认可的编程接口,以便二者使用进程间通信 (IPC) 进行相互通信。实际上起作用的并不是AIDL文件,而是根据AIDL生成的实例代码,AIDL是安卓替我们设计好的一个模板,根据模板生成Interface的代码。
ADIL的存在就是为了实现进程间的通信,通过AIDL我们可以在不同进程中进行数据通信与逻辑交互。
在Binder的基础上我们可以更加容易地理解AIDL。这里先介绍使用AIDL来进行进程间通信的流程,分为服务端和客户端两个方面。
Server端(服务端) 服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。
Client端(客户端) 客户端所要做事情就稍微简单一些,首先需要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。
上面,只是简单的介绍了AIDL的相关逻辑。真正实现起来,还是比较复杂的。
下面,我们就一步步来实现一个AIDL的跨进程通信的Demo
1.AIDL接口的创建 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 36 37 38 package com.frewen.android.demo;import com.frewen.android.demo.RemoteTicket;import com.frewen.android.demo.IOnNewTicketArrivedListener;interface IRemoteServiceInterface { int getPid () ; List<RemoteTicket> getTicketList () ; void addTicket (in RemoteTicket ticket) ; void registerListener (IOnNewTicketArrivedListener listener) ; void unregisterListener (IOnNewTicketArrivedListener listener) ; void basicTypes (int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) ;}
需要注意:如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。在上面的IRemoteServiceInterface.aidl中,我们用到了RemoteTicket这个类,所以,我们必须要创建RemoteTicket.aidl,然后在里面添加如下内容:
1 2 3 4 5 6 package com.frewen.android.demo;parcelable RemoteTicket;
AIDL中每个实现了Parcelable接口的类都需要按照上面那种方式去创建相应的AIDL文件并声明那个类为parcelable。除此之外,AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out或者inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数,至于它们具体的区别,这个就不说了。我们要根据实际需要去指定参数类型,不能一概使用out或者inout,因为这在底层实现是有开销的。最后,AIDL接口中只支持方法,不支持声明静态常量,这一点区别于传统的接口。
为了方便AIDL的开发,建议把所有和AIDL相关的类和文件全部放入同一个包中,这样做的好处是,当客户端是另外一个应用时,我们可以直接把整个包复制到客户端工程中。
注意。通过测试发现。AIDL相关的文件以及类文件,必须要要放在同一个包里面。不然编译报错。这是一个坑!!!
2、 我们创建一个服务端处理逻辑对象 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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 public class RemoteTicketServer extends IRemoteServiceInterface .Stub { private static final String TAG = "T:RemoteTicketServer" ; private final Context mContext; private CopyOnWriteArrayList<RemoteTicket> mTicketList = new CopyOnWriteArrayList <>(); private RemoteCallbackList<IOnNewTicketArrivedListener> mListenerList = new RemoteCallbackList <>(); public RemoteTicketServer (Context mContext) { this .mContext = mContext; } @Override public int getPid () throws RemoteException { SystemClock.sleep(6000 ); return android.os.Process.myPid(); } @Override public List<RemoteTicket> getTicketList () throws RemoteException { Log.d(TAG, "FMsg:getTicketList() begin" ); SystemClock.sleep(30 * 1000 ); Log.d(TAG, "FMsg:getTicketList() end currentThread = " + Thread.currentThread().getName()); return mTicketList; } @Override public void addTicket (RemoteTicket ticket) throws RemoteException { Log.d(TAG, "FMsg:addTicket() called with: ticket = [" + ticket + "]" ); onNewTicketArrived(ticket); } private void onNewTicketArrived (RemoteTicket ticket) throws RemoteException { mTicketList.add(ticket); final int N = mListenerList.beginBroadcast(); for (int i = 0 ; i < N; i++) { IOnNewTicketArrivedListener listener = mListenerList.getBroadcastItem(i); if (listener != null ) { try { listener.onNewTicketArrived(ticket); } catch (RemoteException e) { e.printStackTrace(); } } } mListenerList.finishBroadcast(); } @Override public void registerListener (IOnNewTicketArrivedListener listener) throws RemoteException { Log.d(TAG, "FMsg:registerListener() called with: listener = [" + listener.getClass() + "]" ); Log.d(TAG, "FMsg:registerListener() called with: mListenerList = [" + mListenerList + "]" ); mListenerList.register(listener); final int N = mListenerList.beginBroadcast(); mListenerList.finishBroadcast(); Log.d(TAG, "registerListener, current size:" + N); } @Override public void unregisterListener (IOnNewTicketArrivedListener listener) throws RemoteException { boolean success = mListenerList.unregister(listener); if (success) { Log.d(TAG, "unregister success." ); } else { Log.d(TAG, "not found, can not unregister." ); } final int N = mListenerList.beginBroadcast(); mListenerList.finishBroadcast(); Log.d(TAG, "unregisterListener, current size:" + N); } @Override public void basicTypes (int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { } public void addTicketList (CopyOnWriteArrayList<RemoteTicket> ticketList) { Log.d(TAG, "FMsg:addTicketList() called with: ticketList = [" + ticketList + "]" ); mTicketList.addAll(ticketList); } @Override public boolean onTransact (int code, Parcel data, Parcel reply, int flags) throws RemoteException { Log.d(TAG, "FMsg:onTransact() called with: code = [" + code + "], data = [" + data + "], reply = [" + reply + "], flags = [" + flags + "]" ); int check = mContext.checkCallingOrSelfPermission( "com.frewen.android.demo.permission.ACCESS_TICKET_SERVICE" ); if (check == PackageManager.PERMISSION_DENIED) { return false ; } String packageName = null ; String[] packages = mContext.getPackageManager().getPackagesForUid(getCallingUid()); if (packages != null && packages.length > 0 ) { packageName = packages[0 ]; } Log.d(TAG, "FMsg:onTransact packageName = [" + packageName + "]" ); if (!packageName.startsWith("com.frewen.android.demo" )) { return false ; } return super .onTransact(code, data, reply, flags); } }
3、创建服务端Service对象 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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 public class RemoteTicketServer extends IRemoteServiceInterface .Stub { private static final String TAG = "T:RemoteTicketServer" ; private final Context mContext; private CopyOnWriteArrayList<RemoteTicket> mTicketList = new CopyOnWriteArrayList <>(); private RemoteCallbackList<IOnNewTicketArrivedListener> mListenerList = new RemoteCallbackList <>(); public RemoteTicketServer (Context mContext) { this .mContext = mContext; } @Override public int getPid () throws RemoteException { SystemClock.sleep(6000 ); return android.os.Process.myPid(); } @Override public List<RemoteTicket> getTicketList () throws RemoteException { Log.d(TAG, "FMsg:getTicketList() begin" ); SystemClock.sleep(30 * 1000 ); Log.d(TAG, "FMsg:getTicketList() end currentThread = " + Thread.currentThread().getName()); return mTicketList; } @Override public void addTicket (RemoteTicket ticket) throws RemoteException { Log.d(TAG, "FMsg:addTicket() called with: ticket = [" + ticket + "]" ); onNewTicketArrived(ticket); } private void onNewTicketArrived (RemoteTicket ticket) throws RemoteException { mTicketList.add(ticket); final int N = mListenerList.beginBroadcast(); for (int i = 0 ; i < N; i++) { IOnNewTicketArrivedListener listener = mListenerList.getBroadcastItem(i); if (listener != null ) { try { listener.onNewTicketArrived(ticket); } catch (RemoteException e) { e.printStackTrace(); } } } mListenerList.finishBroadcast(); } @Override public void registerListener (IOnNewTicketArrivedListener listener) throws RemoteException { Log.d(TAG, "FMsg:registerListener() called with: listener = [" + listener.getClass() + "]" ); Log.d(TAG, "FMsg:registerListener() called with: mListenerList = [" + mListenerList + "]" ); mListenerList.register(listener); final int N = mListenerList.beginBroadcast(); mListenerList.finishBroadcast(); Log.d(TAG, "registerListener, current size:" + N); } @Override public void unregisterListener (IOnNewTicketArrivedListener listener) throws RemoteException { boolean success = mListenerList.unregister(listener); if (success) { Log.d(TAG, "unregister success." ); } else { Log.d(TAG, "not found, can not unregister." ); } final int N = mListenerList.beginBroadcast(); mListenerList.finishBroadcast(); Log.d(TAG, "unregisterListener, current size:" + N); } @Override public void basicTypes (int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { } public void addTicketList (CopyOnWriteArrayList<RemoteTicket> ticketList) { Log.d(TAG, "FMsg:addTicketList() called with: ticketList = [" + ticketList + "]" ); mTicketList.addAll(ticketList); } @Override public boolean onTransact (int code, Parcel data, Parcel reply, int flags) throws RemoteException { Log.d(TAG, "FMsg:onTransact() called with: code = [" + code + "], data = [" + data + "], reply = [" + reply + "], flags = [" + flags + "]" ); int check = mContext.checkCallingOrSelfPermission( "com.frewen.android.demo.permission.ACCESS_TICKET_SERVICE" ); if (check == PackageManager.PERMISSION_DENIED) { return false ; } String packageName = null ; String[] packages = mContext.getPackageManager().getPackagesForUid(getCallingUid()); if (packages != null && packages.length > 0 ) { packageName = packages[0 ]; } Log.d(TAG, "FMsg:onTransact packageName = [" + packageName + "]" ); if (!packageName.startsWith("com.frewen.android.demo" )) { return false ; } return super .onTransact(code, data, reply, flags); } }
我们在使用registerListener的时候报了一个异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 java.lang.NullPointerException: Attempt to invoke interface method 'void android.os.IBinder.linkToDeath(android.os.IBinder$DeathRecipient, int)' on a null object reference at android.os.RemoteCallbackList.register(RemoteCallbackList.java:114) at android.os.RemoteCallbackList.register(RemoteCallbackList.java:78) at com.frewen.android.demo.samples.ipc.remote.aidl.RemoteTicketServer.registerListener(RemoteTicketServer.java:84) at com.frewen.android.demo.samples.ipc.client.AIDLDemoActivity$3.onServiceConnected(AIDLDemoActivity.java:87) at android.app.LoadedApk$ServiceDispatcher.doConnected(LoadedApk.java:1453) at android.app.LoadedApk$ServiceDispatcher$RunConnection.run(LoadedApk.java:1481) at android.os.Handler.handleCallback(Handler.java:751) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6121) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)
后来定位到原因之后:
1 2 3 4 5 6 7 8 9 10 11 12 13 private IOnNewTicketArrivedListener mOnNewBookArrivedListener = new IOnNewTicketArrivedListener.Stub() { @Override public void onNewTicketArrived(RemoteTicket newTicket) throws RemoteException { Log.d(TAG, "FMsg:onNewTicketArrived() called with: thread = [" + Thread.currentThread().getName() + "]"); mHandler.obtainMessage(MESSAGE_NEW_TICKET_ARRIVED, newTicket) .sendToTarget(); } @Override public IBinder asBinder() { return null; } };
如果我们再遇到另外一个问题:
1 2 3 4 5 6 7 8 java.lang.IllegalStateException: beginBroadcast() called while already in a broadcast at android.os.RemoteCallbackList.beginBroadcast(RemoteCallbackList.java:229) at com.frewen.android.demo.samples.ipc.remote.aidl.RemoteTicketServer.onNewTicketArrived(RemoteTicketServer.java:65) at com.frewen.android.demo.samples.ipc.remote.aidl.RemoteTicketServer.addTicket(RemoteTicketServer.java:55) at com.frewen.android.demo.samples.ipc.remote.aidl.RemoteService.addNewTicket(RemoteService.java:99) at com.frewen.android.demo.samples.ipc.remote.aidl.RemoteService.access$200(RemoteService.java:24) at com.frewen.android.demo.samples.ipc.remote.aidl.RemoteService$ServiceWorker.run(RemoteService.java:85) at java.lang.Thread.run(Thread.java:761)
这个问题的主要由原因是我们没有进行广播的结束
1 mListenerList.finishBroadcast();
AIDL的权限验证 如何在AIDL中使用权限验证功能。默认情况下,我们的远程服务任何人都可以连接,但这应该不是我们愿意看到的,所以我们必须给服务加入权限验证功能,权限验证失败则无法调用服务中的方法。在AIDL中进行权限验证,这里介绍两种常用的方法。
第一种方法,我们可以在onBind中进行验证,验证不通过就直接返回null,这样验证失败的客户端直接无法绑定服务,至于验证方式可以有多种,比如使用permission验证。使用这种验证方式,我们要先在AndroidMenifest中声明所需的权限,比如:
1 2 3 4 5 <!-- 使用permission验证。使用这种验证方式,我们要先在AndroidMenifest中声明所需的权限 --> <!-- AIDL的学习:在这个地方声明绑定购票服务的权限 --> <permission android:name="com.frewen.android.demo.permission.ACCESS_TICKET_SERVICE" android:protectionLevel="normal" />
AIDL的基础知识汇总。 1、AIDL会阻塞住客户端主线程吗? 我们知道:客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中, 同时客户端线程会被挂起.
这个时候如果服务端方法执行比较耗时,就会导致客户端线程长时间地阻塞在这里,而如果这个客户端线程是UI线程的话,就会导致客户端ANR,这当然不是我们想要看到的。因此,如果我们明确知道某个远程方法是耗时的,那么就要避免在客户端的UI线程中去访问远程方法。
另外由于客户端的onServiceConnected和onService Disconnected方法都运行在UI线程中,所以也不可以在它们里面直接调用服务端的耗时方法