Android中实现IPC通信的8种方式

Android中实现IPC通信的8种方式

2023年7月7日发(作者:)

Android中实现IPC通信的8种⽅式[TOC]1.使⽤Bundle ----> ⽤于android四⼤组件间的进程间通信android的四⼤组件都可使⽤Bundle传递数据 所以如果要实现四⼤组件间的进程间通信 完全可以使⽤Bundle来实现 简单⽅便2.使⽤⽂件共享 ---->⽤于单线程读写这种⽅式在单线程读写的时候⽐较好⽤ 如果有多个线程并发读写的话需要限制线程的同步读写另外 SharePreference是个特例 它底层基于xml实现 但是系统对它的读写会基于缓存,也就是说再多进程模式下就变得不那么可靠了,有很⼤⼏率丢失数据3.使⽤Messenger ---->⽤于可存放在message中的数据的传递使⽤这个⽅式可以在不同进程间传递message对象 这是⼀种轻量级的IPC⽅案 当传递的对象可以放⼊message中时 可以考虑⽤这种⽅式 但是最好不要放因为不⼀定可以序列化使⽤它的步骤如下:假设这样⼀个需求 需要在客户端A发送消息给服务端B接受 然后服务端B再回复给客户端A1. ⾸先是客户端A发送消息给服务端B 所以在客户端A中 声明⼀个Handler⽤来接受消息 并创建⼀个Messenger对象 ⽤Handler作为参数构造然后onBinder⽅法返回der() 即可public class MyServiceA extends Service { private class MessageHandler extends Handler{ //创建的接受消息的handler @Override public void handleMessage(Message msg) { switch (){ case 1: Bundle bundle = a(); String str = ing("aaa"); n("----"+str); Messenger replyTo = o; //此处往下是⽤来回复消息给客户端A的

Message replyMsg = (null,2); Bundle bundle1 = new Bundle(); ing("bbb","remote222给主进程回复消息啦"); a(bundle1); try { (replyMsg); } catch (RemoteException e) { tackTrace(); } break; } Message(msg); } } Messenger messenger = new Messenger(new MessageHandler()); public MyServiceA() { }

public IBinder onBind(Intent intent) { return der(); } }2.在客户端A⾃然是需要发送消息给服务端B的 所以需要在服务绑定完成之后 获取到binder对象 之后⽤该对象构造⼀个Messenger对象 然后⽤messenger发送消息给服务端即可 代码如下 :public void onServiceConnected(ComponentName name, IBinder service) { Messenger messenger = new Messenger(service); Message msg = (null,1); Bundle bundle = new Bundle(); ing("aaa", "主进程给remote22进程发消息啦"); a(bundle); o = mmessenger; //这⾏代码⽤于客户端A接收服务端请求 设置的消息接收者

try { (msg); } catch (RemoteException e) { tackTrace(); } }3.由于在服务端接收到了客户端的消息还需要回复 所以在服务端代码中获取 msg中的replyTo对象 ⽤这个对象发送消息给 客户端即可在客户端需要创建⼀个handler和Messenger 将发送的o设置成Messenger对象 就可 android 接⼝定义语⾔ ---->主要⽤于调⽤远程服务的⽅法的情况 还可以注册接⼝使⽤⽅法很简单在服务端定义aidl⽂件 ⾃动⽣成java⽂件 然后在service中实现这个aidl 在onbind中返回这个对象在客户端把服务端的aidl⽂件完全复制过来 包名必须完全⼀致 在onServiceConnected⽅法 中 把 Ibinder对象 ⽤asInterface⽅法转化成 aidl对象然后调⽤⽅法即可需要注意的地⽅:在aidl⽂件中并不是⽀持所有类型仅⽀持如下6种类型:基本数据类型---- int long char boolean doubleString charSequenceList 只⽀持ArrayList CopyOnWriteArrayList也可以。。 ⾥⾯元素也必须被aidl⽀持Map 只⽀持HashMap ConCurrentHashMap也可以 ⾥⾯元素也必须⽀持aidlParcelable 所有实现了此接⼝的对象AIDL 所有的AIDL接⼝ 因此 如果需要使⽤接⼝ 必须使⽤AIDL接⼝其中⾃定义的类型和AIDL对象必须显⽰import进来 不管是不是在⼀个包中如果AIDL⽂件中⽤到了⾃定义的Parcelable对象 必须创建同名的AIDL⽂件 并声明为Parcelable类型AIDL⽂件中除了基本数据类型外 其他类型必须标上⽅向 in out inoutAIDL接⼝中只⽀持⽅法 不⽀持声明静态常量在使⽤aidl时 最好把所有aidl⽂件都放在⼀个包中 这样⽅便复制到客户端其实所有的跨进程对象传递都是对象的序列化与反序列化 所以必须包名⼀致现在加⼊有这样⼀个需求 如果服务端是 图书馆添加和查看书的任务 客户端可以查看和添加书 这时候需要添加⼀个功能 当服务端每添加了⼀本书需要通知客户端注册⽤户 有⼀本新书上架了 这个功能如何实现?想想可知 这是⼀个观察者模式 如果在同⼀进程中很容易实现,只需要在服务端中的代码中维护⼀个集合 ⾥⾯放的是注册监听的⽤户 然后⽤户需要实现⼀个新书到来的回调接⼝当有新书上架时 遍历这个集合 调⽤每个注册者的接⼝⽅法 即可实现现在我们是跨进程通信 所以⾃然不能如此简单了 但也不是很复杂 想⼀想 其实就是把以往的接⼝定义 变成了aidl接⼝定义 然后其他的⼀样即可但是这样还是存在⼀个问题 如果注册了listener 我们⼜想解除注册 是不是在客户端传⼊listener对象 在服务端把它移除就可以呢?其实是不可以的 因为这是跨进程的 所以对象并不是真正的传递 只是在另⼀个进程中重新创建了⼀个⼀样的对象 内存地址不同 所以根本不是同⼀个对象所以是不可以的 如果要解决这个问题 需要使⽤RemoteCallbackList 类 不要使⽤CopyWriteArrayList在RemoteCallBackList中封装了⼀个Map 专门⽤来保存所有的AIDL回调 key为IBinder value是CallBack 使⽤IBinder 来区别不同的对象 ,因为跨进程传输时会产⽣很多个不同的对象 但这些对象的底层的Binder都是同⼀个对象 所以可以在使⽤RemoteCallBackList时 add 变为 register remove 变为 unregister 遍历的时候需要先 beginBroadcast 这个⽅法同时也获取集合⼤⼩获取集合中对象使⽤ getBoardCastItem(i) 最后不要忘记finishBoardCast⽅法还有⼀个情况 由于onServiceConnected⽅法 是在主线程执⾏的 如果在这⾥执⾏服务端的耗时代码 会ANR 所以需要开启⼀个⼦线程执⾏同理在服务端中 也不可以运⾏客户端的耗时程序总结起来就是 在执⾏其他进程的耗时程序时 都需要开启另外的线程防⽌阻塞UI线程 如果要访问UI相关的东西 使⽤handler为了程序的健壮性 有时候Binder可能意外死亡 这时候需要重连服务 有2种⽅法:1.在onServiceDisconnected⽅法中 重连服务2. 给Binder注册DeathRecipient监听 当binder死亡时 我们可以收到回调 这时候我们可以重连远程服务最后有时候我们不想所有的程序都可以访问我们的远程服务 所以可以给服务设置权限和过滤:1.我们在onbind中进⾏校验 ⽤某种⽅式 如果验证不通过那么就直接返回null2.我们可以在服务端的中 设置所需的权限然后在onbind中 检查是否有这个权限了 如果没有那么直接返回null即可 判断⽅法如下 :int check = checkCallingOrSelfPermission("aaa"); if(check== SION_DENIED){ return null; }3.可以在onTransact⽅法中 进⾏权限验证 如果验证失败直接返回false 可以采⽤permission⽅法验证 还可以⽤Uid和Pid验证 很多⽅法其中声明权限与 添加权限的⽅式 是 在Service所在的AndroidMinifest中 声明权限⽐如然后在 需要远程调⽤的 app中添加 这个权限这样 就可以在 onbind中验证权限了⾄此 AIDL ⼤体介绍完了 以后需要在使⽤中提升了tProvider⽅式 实现对另⼀个应⽤进程开放provider数据的查询此⽅法使⽤起来也⽐较简单 底层是对Binder的封装 使之可以实现进程间通信 使⽤⽅法如下1. 在需要共享数据的应⽤进程中建⽴⼀个ContentProvider类 重写它的CRUD 和getType⽅法 在这⼏个⽅法中调⽤对本应⽤进程数据的调⽤然后在⽂件中声明provider

android:authorities="" //这个是⽤来标识provider的唯⼀标识 路径uri也是这个 android:name=".BookProdiver" android:process=":remote_provider"/> //此句为了创建多进程 正常不需要使⽤2. 在需要获取共享数据的应⽤进程中调⽤getContentResolver().crud⽅法 即可实现数据的查询需要注意的问题:1.关于 sqlite crud的各个参数的意义query函数 参数Cursor query(boolean distinct, String table, String[] columns,String selection, String[] selectionArgs, String groupBy,String having, String orderBy, String limit)第⼀个参数 distinct 英语单词意思 独特的 如果true 那么返回的数据都是唯⼀的 意思就是实现查询数据的去重第⼆个参数 table 表名第三个参数 columns 要查询的⾏的名字数组 例如 new String[]{"id","name","sex"}第四个参数 selection 选择语句 sql语句中where后⾯的语句 值⽤?代替 例如 "id=? and sex=?"第五个参数 selectionArgs 对应第四个参数的 ? 例如 new String[]{"1","男"}第六个参数 groupBy ⽤于分组第七个参数 having 筛选分组后的数据第⼋个参数 orderby ⽤于排序 desc/asc 升序和降序 例如 id desc / id asc最后⼀个参数 limit ⽤于限制查询的数据的个数 默认不限制其他⼏个函数 根据query函数的参数猜想即可2.由于每次ipc操作 都是靠uri来区别 想要获取的数据位置 所以provider在调取数据的时候根据uri并不知道要查询的数据是在哪个位置所以我们可以通过 UriMatcher 这个类来给每个uri标上号 根据编号 对应适当的位置 例如:public static final int BOOK_CODE = 0; public static final int USER_CODE = 1; public static UriMatcher matcher = new UriMatcher(_MATCH); static { ("book uri", "book", BOOK_CODE); ("user uri", "user", USER_CODE); } 这样我们可以通过 下⾯这个样⼦来获取位置(此处是表名 其他类型也⼀样) private String getTableName(Uri uri) { switch ((uri)) { case BOOK_CODE: return "bookTable"; case USER_CODE: return "userTable"; } return ""; }3.另外ContentProvider除了crud四个⽅法外,还⽀持⾃定义调⽤ 通过ContentProvider 和ContentResolver的 call⽅法 来实现⽅法实现Ipc 这种⽅式也可以实现 但是不常⽤需要权限这种⽅式需要⼀个服务端socket 和⼀个客户端socket 建⽴连接后 通过流循环获取消息即可1.在服务端开启⼀个serverSocket 不断获取客户端连接 注意要在⼦线程中开启ServerSocket serverSocket = new ServerSocket(8688); while(isActive) { //表⽰服务⽣存着 try { final Socket client = (); //不断获取客户端连接 n("---服务端已获取客户端连接"); new Thread(){ @Override public void run() { try { dealWithMessageFromClient(client); //处理客户端的消息 就是开启⼀个线程循环获取out 和 in 流 进⾏通信 } catch (IOException e) { tackTrace(); } } }.start(); } catch (IOException e) { tackTrace(); } }2.在客户端开启⼀个线程 使⽤ip和端⼝号连接服务端socket 连接成功后 ⼀样 开启⼦线程 循环获取消息 处理Socket socket = null; while(socket==null){ //失败重连 try { socket = new Socket("localhost",8688); out = new PrintWriter(putStream(),true); ptyMessage(1); final Socket finalSocket = socket; new Thread(){ @Override public void run() { try { reader = new BufferedReader(new InputStreamReader(utStream())); } catch (IOException e) { tackTrace(); } while(!shing()){ //循环获取消息 这⾥必须⽤ 循环 否则 只能获取⼀条消息 服务端也⼀样 try { String msg = ne(); n("---"+msg); if (msg!=null){ ssage(Message(2,msg)); } } catch (IOException e) { tackTrace(); } } } }.start(); } catch (IOException e) { (1000); tackTrace(); } } 连接池的使⽤ 很好⽤我们在android中进程间通信 ⼀般都使⽤ AIDL实现 因为它强⼤ 但是普通的使⽤⽅法每次使⽤AIDL 都需要开启⼀个服务 如果有多个AIDL请求那岂不是要开启很多个服务这明显是不可以的 ⽐如你让你⽤户的⼿机 发现你这⼀个应⽤程序绑定了10个服务 那是极差的 所以 我们在多个AIDL 请求的时候可以使⽤Binder连接池技术只开启⼀个服务 根据需要获取的AIDL不同 转化成需要的AIDL 接⼝ 执⾏不同的⽅法实现的基本原理 就是在onbind中返回⼀个BinderPool 接⼝ 这个接⼝有个⽅法 可以根据不同的标志位返回不同的aidl接⼝ 这样我们在asInTerface之后调⽤哪个⽅法传⼊标志位即可返回需要的aidl接⼝说起来简单 让我们来实现⼀个试试吧1.假设原来有2个AIDL接⼝需要实现(可以扩展成多个) 在服务端建⽴好AIDL⽂件 并且建⽴⼀个IBinderPool aidl接⼝ 只有⼀个查询binder的⽅法⽤于查询需要的binderinterface IBinderPool {/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/IBinder queryBinder(int code); //此⽅法返回Ibinder ⽤于转化成需要的AIDL接⼝}2.在服务端 onbind⽅法中返回 IBinderPool的实现类 实现query⽅法 按照传⼊的code 返回需要的ibinder@Overridepublic IBinder onBind(Intent intent) {return iBinderPool;}private Binder iBinderPool = new () {@Overridepublic IBinder queryBinder(int code) throws RemoteException {switch (code) { case 1:return new () {@Overridepublic void getBook() throws RemoteException {n("--->book");}}; case 2:return new () {@Overridepublic void getPerson() throws RemoteException {n("---->person");}};}return null;}};3.客户端实现⼀个BinderPool类 这个类主要是封装了 AIDL的⼀些实现⽅法 ⽅便调⽤罢了 其中 涉及到⼀个可以实现同步机制的类CountDownLatch 这个类 当他的值 不是0的时候 执⾏了await⽅法后会使⽅法⼀直停在await处 不进⾏ 直到他的值变成了0 才可以继续执⾏也就是说 当执⾏了await⽅法 这个线程就会阻塞 等待这个数值变到0后继续执⾏⽽在BinderPool中的应⽤场景是这样的private void connectService(){countDownLatch = new CountDownLatch(1); //实现同步机制

Intent intent = new Intent();ss(ctx,);rvice(intent,connection,_AUTO_CREATE);try {();} catch (InterruptedException e) {tackTrace();}}⾸先为什么在这⾥要使⽤同步机制 我们要搞清楚 让我们看这个⽅法调⽤的时机 :binderPool = tance(); //connectService⽅法 是在这个⽅法中调⽤的

IBinder iBinder = inder(2); iPersonManager = rface(iBinder); try { son(); } catch (RemoteException e) { tackTrace(); }因为我们最终的⽬的是在bind服务 连接到远程服务之后获取到 binderPool对象调⽤它的 inder(2) ⽅法 如果不加同步机制异步执⾏ 就有可能在 connectService⽅法 执⾏完之后 执⾏IBinder iBinder = inder(2);这⾏代码的时候binderPool对象还没有被赋值 这样就会产⽣问题 所以我们让 connectService⽅法 阻塞 当BinderPool中的 binderPool对象赋值之后 让CountDownLatch的值countDown到0这样 connectService⽅法就会继续执⾏ 然后执⾏下⼀⾏代码了最后针对这⼏种IPC通信⽅式分析⼀下优缺点 :简单易⽤ 但是只能传输Bundle⽀持的对象 常⽤于四⼤组件间进程间通信2.⽂件共享:简单易⽤ 但不适合在⾼并发的情况下 并且读取⽂件需要时间 不能即时通信 常⽤于并发程度不⾼ 并且实时性要求不⾼的情况 :功能强⼤ ⽀持⼀对多并发通信 ⽀持即时通信 但是使⽤起来⽐其他的复杂 需要处理好多线程的同步问题 常⽤于⼀对多通信 且有RPC 需求的场合(服务端和客户端通信)ger :功能⼀般 ⽀持⼀对多串⾏通信 ⽀持实时通信 但是不能很好处理⾼并发情况 只能传输Bundle⽀持的类型 常⽤于低并发的⽆RPC需求⼀对多的场合tProvider :在数据源访问⽅⾯功能强⼤ ⽀持⼀对多并发操作 可扩展call⽅法 可以理解为约束版的AIDL 提供CRUD操作和⾃定义函数 常⽤于⼀对多的数据共享场合 :功能强⼤ 可以通过⽹络传输字节流 ⽀持⼀对多并发操作 但是实现起来⽐较⿇烦 不⽀持直接的RPC 常⽤于⽹络数据交换总结当仅仅是跨进程的四⼤组件间的传递数据时 使⽤Bundle就可以 简单⽅便当要共享⼀个应⽤程序的内部数据的时候 使⽤ContentProvider实现⽐较⽅便当并发程度不⾼ 也就是偶尔访问⼀次那种 进程间通信 ⽤Messenger就可以当设计⽹络数据的共享时 使⽤socket当需求⽐较复杂 ⾼并发 并且还要求实时通信 ⽽且有RPC需求时 就得使⽤AIDL了⽂件共享的⽅法⽤于⼀些缓存共享 之类的功能

发布者:admin,转转请注明出处:http://www.yc00.com/news/1688674528a161651.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信