跨进程通信使用总结(二)_Android中的IPC方式
第一种方式,使用Bundle
Android中的4大组件都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以它可以方便的在不同的进程间传输
第二种方式,使用文件共享:
读写在同一个地方的文件(例如读取外存储卡上的data.txt文件,前提是要有读写文件的权限)
第三种方式.使用Messenger(信使)
通过信使Messenger可以在不同的进程中传递Message(消息)对象,在消息对象中放入我们要传递的数据,就可以轻松实现进程中传递数据了,信使是一种轻量级的IPC方案,它的底层是AIDL,信使是一次处理一个请求,因此服务端不用考虑线程同步的问题,因为服务端不存在线程并发的情况
信使的2种构造函数如下:
//一般用在服务端 public Messenger(Handler target) { mTarget = target.getIMessenger(); }
//一般用在客户端 public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target); }
我们先看一下它的大体逻辑:
服务端进程:
首先,我们要在服务端创建一个Service来处理客户端的请求,同时创建一个Handler并通过它来创建一个Mesenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可
客户端进程:
首先要绑定服务端的Service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息,发送的消息类型是:Message对象.
都是在Handler里面从msg上去获取数据的
.
服务端具体实现代码如下:
public class MessengerService extends Service{ private class MessengerHandler extends Handler{ @Override public void handleMessage(Message msg) { switch (msg.what){ case Constants.MSG_FROM_CLIENT: //从客户端msg里面取到的消息内容 String msgContent= msg.getData().getString("client"); LogUtils.d("服务端msgContent"+msgContent); //从客户端msg里面取到的信使 Messenger msger= msg.replyTo; //创建一个新的消息对象,一会儿给客户端传递消息 Message tempMsg=Message.obtain(null, Constants.MSG_FROM_SERVICE); //给消息里面放数据 Bundle bundle=new Bundle(); bundle.putString("serviceReply","你好,客户端,你发送的消息我已经收到了"); tempMsg.setData(bundle); try { //信使携带消息去客户端 msger.send(tempMsg); } catch (RemoteException e) { e.printStackTrace(); } break; default: super.handleMessage(msg); break; } } } Messenger messenger=new Messenger(new MessengerHandler()); @Nullable @Override public IBinder onBind(Intent intent) { return messenger.getBinder(); } }
客户端的具体实现完整代码如下:
public class MessengerClient extends AppCompatActivity { private class MessengerHandler extends Handler{ @Override public void handleMessage(Message msg) { switch (msg.what){ case Constants.MSG_FROM_SERVICE: //从服务端msg里面取到的消息内容 String msgContent= msg.getData().getString("serviceReply"); LogUtils.d("msgContent"+msgContent); break; default: super.handleMessage(msg); break; } } } Messenger replyMsger = new Messenger(new MessengerHandler()); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_book_manager); Intent a=new Intent(this,MessengerService.class); bindService(a,mServiceConnection, Context.BIND_AUTO_CREATE); } private ServiceConnection mServiceConnection=new ServiceConnection(){ @Override public void onServiceConnected(ComponentName name, IBinder service) { //执行在主线程 //用服务端返回的service,创建Messenger信使,一会儿给服务端发送信息 Messenger msger=new Messenger(service); //创建一个新的消息对象,作为数据的载体 Message clientMsg=Message.obtain(null, Constants.MSG_FROM_CLIENT); //给消息里面放数据 Bundle bundle=new Bundle(); bundle.putString("client","你好,服务端,这是我客户端发送的消息"); clientMsg.setData(bundle); //注意这步:把信使放到msg里面,方便服务端从msg里面取信使 clientMsg.replyTo=replyMsger; try { //信使携带消息去服务端 msger.send(clientMsg); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { //执行在主线程 } @Override public void onBindingDied(ComponentName name) { //执行在主线程 :重新连接 } }; @Override protected void onDestroy() { unbindService(mServiceConnection); super.onDestroy(); } }
第四种方式,使用AIDL
1,AIDL通信的流程:
第一步:服务端:
服务端首先要创建一个service来监听客户端的连接请求,然后创建一个AIDL文件,把暴露给客户端调用的接口在这个AIDL文件中声明,最后在service中实现这个接口
第2步:客户端,
首先绑定服务端的service,绑定成功后,把服务端返回的Binder对象自己转换成AIDL接口所属的类型,接口就可以调用AIDL中的方法了,注意链接断开的时候增加Binder死亡监听
第3步:AIDL接口的创建:创建文件名为IBookManager2 .aidl的文件
// IBookManager2.aidl package com.hlj.demo.aidl; // Declare any non-default types here with import statements //Book1 是自定义的parcelable对象,必须要显示的import进来 import com.hlj.demo.aidl.Book1; import android.os.Parcel; import android.os.Parcelable; import com.hlj.demo.aidl.IOnNewBookArrivedListener; interface IBookManager2 { /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); List<Book1> getBookList(); //in:表示输入类型 //Book1 是自定义的parcelable对象,必须要显示的import进来 void addBook(in Book1 book1); void registerListerner(IOnNewBookArrivedListener lister); void unregisterListerner(IOnNewBookArrivedListener lister); }
说明;
1,AIDL支持的数据类型为:基本数据类型,Arraylist,HashMap,Parcelable,AIDl
2,自定义的parcelable对象和AIDl的对象,必须要显示的import进来,不管它们是否和当前的AIDL文件处于同一个包内
3,除了基本数据类型之外,其他类型的参数必须要标上方向,in,out,或者inout
4,AIDL的包结构在服务端和客户端要一致,因为客户端要反序列化服务端中和AIDl相关的所有类,如果类的完整路径不一样的话,否则会运行报错,所以建议把所有和AIDL相关的类和文件全部放入同一个包中
第4步和第5步就是服务端和客户端的具体实现,后面会写一个Demo具体看demo
代码较多
24000
,就不具体贴出来啦 在这里我提几个重要的注意点:
1.集合用CopyOnWriteArrayList,理由是:它支持并发读写,因为AIDl方法是在Binder线程池中执行的,会存在多个线程同事访问的情况,所以我们要在AIDl方法中处理线程同步,而CopyOnWriteArrayList可自动进行线程同步
CopyOnWriteArrayList<Book1> mBookList=new CopyOnWriteArrayList<Book1>();
2,Binder线程池中执行的程序不能更新UI,是子线程
3,有类似观察者模式或者回调情况的集合要用RemoteCallbackList,RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口,它内部也实现了线程同步
RemoteCallbackList<IOnNewBookArrivedListener> mListenerList= new RemoteCallbackList<IOnNewBookArrivedListener>();
用法:
public void onNewBookArrived(Book1 book1) throws RemoteException { mBookList.add(book1); //注意beginBroadcast和finishBroadcast要配对使用 final int N= mListenerList.beginBroadcast(); for (int i = 0; i < N; i++) { IOnNewBookArrivedListener lister= mListenerList.getBroadcastItem(i); if (lister!=null) { lister.onNewBookArrived(book1); } } mListenerList.finishBroadcast(); };
注意beginBroadcast和finishBroadcast要配对使用
4远程服务不是任何客户端相连就可以连上的,所以我们要做一些验证才可以:这里做的是权限加包名验证
<!-- 表示服务端自定义了一个权限 --> <permission android:name="com.hlj.demo.permiss_ACCESS_BOOK_SERVICE" android:protectionLevel="normal" /> <!-- 表示所有连接这个服务的客户端都要有这个权限,否则不会连接成功 --> <service android:name=".service.BookManagerService" android:permission="com.hlj.demo.permiss_ACCESS_BOOK_SERVICE" android:process=":BookManagerService" />
<!-- 表示客户端可以使用这个权限,已经有了这个权限 --> <uses-permission android:name="com.hlj.demo.permiss_ACCESS_BOOK_SERVICE" />
@Override public IBinder onBind(Intent intent) { //先验证权限在验证包名,只要有一个没有通过就返回null String pkgName=""; int permission= ActivityCompat.checkSelfPermission(this,"com.hlj.demo.permiss_ACCESS_BOOK_SERVICE"); if (permission == PackageManager.PERMISSION_DENIED) return null; String[] pkgNames=getPackageManager().getPackagesForUid(Binder.getCallingUid()); if (pkgNames==null||pkgNames.length<=0) return null; pkgName=pkgNames[0]; if (!pkgName.startsWith("com.hlj."))return null; return mBinder; }
5,Binder是有可能意外死亡的,那当服务断开了的时候,我们要重新连接服务
第一种方式:
给Binder设置DeathRecipient监听,当Binder死亡的时候,我们会受到binderDied方法的回调,可在此方法中重新连接(在非UI线程中执行)
第二种方式:
在onServiceDisconnected()方法中重新连接(在UI线程中执行)
第五种方式,使用ContentProvider
第六种方式,使用Socket(此2中方式在项目中使用较少,略过)
一点优化:
当要提供的AIDl服务过多时,可以使用Binder连接池
结尾:
终于写完了,老衲甚是欣慰啊^- ^
- Android中跨进程通信方式之使用Bundle
- 【Android开发艺术探索】IPC机制(四)-使用AIDL进行跨进程通信
- Android 进阶13:几种进程通信方式的对比总结
- Android IPC进程通信 Messager方式
- Android中跨进程通信方式之使用Messenger
- Android中跨进程通信方式之使用Bundle
- Android IPC进程通信——Messager方式
- Android中跨进程通信方式之使用AIDL
- [置顶] Android跨进程通信方式(IPC)解析
- Android IPC进程通信——Messager方式
- Android IPC进程通信——Messager方式
- Android中跨进程通信方式之使用AIDL一些小细节
- Android IPC进程通信之Messager方式
- [转]Android IPC进程通信——Messager方式
- Android跨进程通信之Binder连接池及选择合适的IPC方式
- Android中跨进程通信方式之使用文件共享
- Android 进阶13:几种进程通信方式的对比总结
- Android IPC进程通信——Messager方式
- Android之IPC进程通信方案适用场景总结
- Android中跨进程通信方式之使用文件共享