💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
原文出处点击[这里](http://blog.csdn.net/qy274770068/article/details/49228441) [续集](http://blog.csdn.net/qy274770068/article/details/49228663) IPC机制(IPC介绍) IPC机制 ### 1.介绍 IPC是Inter-Progress Communication的缩写,含义为进程间通信或者跨进程通信。是指两个进程之间进行数据交换的过程。 Android里面最有特色的进程间的通信方式就是Binder,通过它可以轻松的实现进程间的通信,Android也支持Socket,通过Socket也可以实现任意两个终端之间的通信,当然一个设备上的两个进程通过Socket通信自然也是可以的。 ### 2.Android中的多进程模式 正常情况下Android中多进程指一个应用中存在多个进程的情况。在Android中使用多进程只有一种方式,那就是给四大组件在Manifest中指定android:progress属性。 就是说不能为一个线程,一个实体类指定其运行时候所在的进程。(其实还有一种方式,就是使用JNI在Native层去fork一个新的进程,这中方法属于特殊情况,也不是常用的创建多进程的方式)。 <activity android:name= ".MainActivity" android:configChanges= "orientation|screenSize" android:label= "@string/app_name" android:launchMode= "standard" > <intent-filter > <action android:name ="android.intent.action.MAIN" /> </intent-filter > </activity > <activity android:name= ".SecondActivity" android:configChanges= "screenLayout" android:label= "@string/app_name" android:process= ":remote" /> <activity android:name= ".ThirdActivity" android:configChanges= "screenLayout" android:label= "@string/app_name" android:process= "com.ryg.chapter_2.remote" /> 上面的例子中假如当前的应用的包名为com.ryg.chapter_2,那么当SecondActivity启动的时候系统会为它创建一个单独的进程,进程名为com.ryg.chapter_2:remote, 当ThirdActivity启动的时候系统也会为它创建一个单独的进程com.ryg.chapter_2.remote,由于MainActivity没有指定Progress属性,运行在默认的进程中,默认的进程是包名。 使用 : 和直接命名的区别: 1. : 的含义是在当前进程名的前面加上当前的包名,这是一种简单的写法,而另外一种则是完整的命名方式,不会附加包名信息。 2. 使用 : 的进程表示它属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,不使用 : 的进程属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。 > **注意:** > 如果两个进程有相同的ShareUID并且签名相同,它们可以互相访问对方的私有数据,比如data目录,组件信息等。 > 如果跑在同一个进程中(前提是有相同的ShareUID并且前面相同),除了能共享data目录,组件信息,还可以共享内存数据或者说它们看起来就像一个应用的两个部分。 Android为每一个应用分配了一个独立的虚拟机,或者说为每一个进程都分配了一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致不同的虚拟机中访问同一个类的对象会产生多份副本。 ~~~ public class UserManager { public static int sUserId = 1; } ~~~ 在MainActivity的onCreate中把这个值赋值为2,启动SecondActivity,发现值任然为1,可以看到多进程绝非仅仅指定一个android:progress属性那么简单。 比如,在进程com.ryg.chapter_2和进程com.ryg.chapter_2:remote中都存在一个UserManager类,并且这两个类是互不干扰的,在同一个进程中修改sUserId的值,只会影响当前的进程。对其他进程不会造成任何影响。 所有运行在不同进程中的四大组件,只要他们之间需要通过内存来共享数据,都会共享失败,这也是多进程带 来的主要影响。正常情况下,四大组件中间不可能不通过一些中间层来共享数据,那么简单的指定进程名来开启多进程都会无法正确的运行。 当然,特殊情况下,某些组件之间不需要共享数据,这个时候可以指定android:progress来开启多进程,但是这种场景是不常见的,几乎所有的情况都需要共享数据。 一般来说,使用多进程会造成如下几方面的问题: (1)静态成员和单例模式完全失效 (2)线程同步机制完全失效 (3)SharePreferences的可靠性下降 (4)Application多次创建 ### 3.IPC基础概念介绍 #### (1)Serializable接口 Serializable是Java提供的一个序列化接口,是一个空接口,为对象提供标准的序列化和反序列化操作。 通过Serializable方式实现对象的序列化,实现起来非常简单,几乎所有的工作都被系统自动完成了。 反序列化也非常简单,需要采用ObjectOutputStream和ObjectInputStream即可。 ~~~ // 序列化 User user = new User(0,"jake",true); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.dat")); out.write(user); out.close(); // 反序列化 ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.dat")); User newUser = (User) in.readObject(); in.close(); ~~~ serialVersionUID用来辅助序列化和反序列化的,在反序列化的时候会去检测文件中serialVersionUID和当前对象中的serialVersionUID是否相同,相同才可以反序列化。 所以我们应该手动的指定serialVersionUID,这样即使删除了一些变量或者增加了一些变量任然可以成功的反序列化。 (2)Parcelable接口 Book类: ~~~ package com.ryg.chapter_2.aidl; import android.os.Parcel; import android.os.Parcelable; public class Book implements Parcelable { public int bookId; public String bookName; public Book() { } public Book(int bookId, String bookName) { this.bookId = bookId; this.bookName = bookName; } public int describeContents() { return 0; } public void writeToParcel(Parcel out, int flags) { out.writeInt(bookId); out.writeString(bookName); } public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() { public Book createFromParcel(Parcel in) { return new Book(in); } public Book[] newArray(int size) { return new Book[size]; } }; private Book(Parcel in) { bookId = in.readInt(); bookName = in.readString(); } @Override public String toString() { return String.format("[bookId:%s, bookName:%s]", bookId, bookName); } } ~~~ User类: ~~~ package com.ryg.chapter_2.model; import java.io.Serializable; import com.ryg.chapter_2.aidl.Book; import android.os.Parcel; import android.os.Parcelable; public class User implements Parcelable, Serializable { private static final long serialVersionUID = 519067123721295773L; public int userId; public String userName; public boolean isMale; public Book book; public User() { } public User(int userId, String userName, boolean isMale) { this.userId = userId; this.userName = userName; this.isMale = isMale; } public int describeContents() { return 0; } public void writeToParcel(Parcel out, int flags) { out.writeInt(userId); out.writeString(userName); out.writeInt(isMale ? 1 : 0); out.writeParcelable(book, 0); } public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() { public User createFromParcel(Parcel in) { return new User(in); } public User[] newArray(int size) { return new User[size]; } }; private User(Parcel in) { userId = in.readInt(); userName = in.readString(); isMale = in.readInt() == 1; book = in .readParcelable(Thread.currentThread().getContextClassLoader()); } @Override public String toString() { return String.format( "User:{userId:%s, userName:%s, isMale:%s}, with child:{%s}", userId, userName, isMale, book); } } ~~~ Parcel内部包装了可序列化的数据,可以在Binder中自由传输。序列化功能由writeToParcel方法来完成,最终是通过Parcel中的一系列write方法 来完成的。反序列化功能由CREATOR来完成。其内部标明了如何创建序列化对象和数组,并通过Parcel的一系列的read方法来完成反序列化过程。 内容描述功能由describeContents方法来完成,几乎在所有情况下这个方法都应该返回0,仅仅当当前的对象中存在文件描述符时,此方法返回1。 Parcelable的方法说明: createFromParcel(Parcel in) 从序列化的对象中创建原始对象 newArray(int size)创建指定长度的原始对象数组 User(Parcel in)从序列化后的对象中创建原始对象 writeToParcel(Parcel out, int flags)将当前对象写入序列化结构中 describeContents返回当前对象的内容描述 **区别:** Serializable是java中的序列化接口,使用起来简单但是开销很大,序列化和反序列化过程中需要大量的I/O操作。 Parcelable是Android中的序列化方式,因此更适合用在Android平台上,缺点就是使用起来稍微麻烦点。但是它的效率很高,这是Android推荐的序列换方式。 因此我们要首选Parcelable。 Parcelable主要用在内存序列化上,通过Parcelable将对象序列化在存储设备中或者将对象序列化以后通过网络传输也都是可以的,但是这个过程会稍显复杂。 因此在这两种情况下建议使用Serializable。 #### (3)Binder 直观来说Binder是Android中的一个类,它继承Ibinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信的方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有。 从Android Framework的角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,等等)和相应ManagerService的桥梁。 从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。 Android开发中,Binder主要用在Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及进程间通信。Messenger的核心其实是AIDL。 IBookManager.aidl类: ~~~ package com.ryg.chapter_2.aidl; import com.ryg.chapter_2.aidl.Book; import com.ryg.chapter_2.aidl.IOnNewBookArrivedListener; interface IBookManager { List<Book> getBookList(); void addBook(in Book book); } ~~~ 在功gen里面自动生成的java类: ~~~ /* * This file is auto-generated. DO NOT MODIFY. * Original file: E:\\Project\\android-art-res-master\\Chapter_2\\src\\com\\ryg\\chapter_2\\aidl\\IBookManager.aidl */ package com.ryg.chapter_2.aidl; public interface IBookManager extends android.os.IInterface { /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.ryg.chapter_2.aidl.IBookManager { private static final java.lang.String DESCRIPTOR = "com.ryg.chapter_2.aidl.IBookManager"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.ryg.chapter_2.aidl.IBookManager * interface, generating a proxy if needed. */ public static com.ryg.chapter_2.aidl.IBookManager asInterface( android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.ryg.chapter_2.aidl.IBookManager))) { return ((com.ryg.chapter_2.aidl.IBookManager) iin); } return new com.ryg.chapter_2.aidl.IBookManager.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_getBookList: { data.enforceInterface(DESCRIPTOR); java.util.List<com.ryg.chapter_2.aidl.Book> _result = this .getBookList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addBook: { data.enforceInterface(DESCRIPTOR); com.ryg.chapter_2.aidl.Book _arg0; if ((0 != data.readInt())) { _arg0 = com.ryg.chapter_2.aidl.Book.CREATOR .createFromParcel(data); } else { _arg0 = null; } this.addBook(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.ryg.chapter_2.aidl.IBookManager { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public java.util.List<com.ryg.chapter_2.aidl.Book> getBookList() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List<com.ryg.chapter_2.aidl.Book> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0); _reply.readException(); _result = _reply .createTypedArrayList(com.ryg.chapter_2.aidl.Book.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public void addBook(com.ryg.chapter_2.aidl.Book book) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); if ((book != null)) { _data.writeInt(1); book.writeToParcel(_data, 0); } else { _data.writeInt(0); } mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } } static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); } public java.util.List<com.ryg.chapter_2.aidl.Book> getBookList() throws android.os.RemoteException; public void addBook(com.ryg.chapter_2.aidl.Book book) throws android.os.RemoteException; } ~~~ 每个方法的含义: **DESCRIPTOR** Binder的唯一标识,一般用当前Binder的类名表示。 **asInterface(android.os.IBinder obj)** 用于将服务端的Binder对象转换成客户端所需要的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象。 **asBinder** 此方法用于返回当前Binder对象 **onTransact** 这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求的时候,远程请求会通过系统底层封装后交给此方法处理。该方法的原型是protected boolean onTransact( int code, Parcel data, Parcel reply,int flags)。 服务端通过code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需要的参数(如果目标方法有参数的话)然后执行目标方法。 当目标方法执行完毕后,就向reply中写入返回值(如果目标方法有返回值的话),onTransact的执行过程就是这样。 如果此方法返回false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟不希望随便一个进程都能远程调用我们的服务。 **Proxy#getBookList** 这个方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样的: 首先创建该方法所需要的输入型Parcel对象_data,输出型Parcel象_reply和返回值对象List; 然后把该方法的参数信息写入_data中(如果有参数的话); 接着调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起; 然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果; 最后返回_reply中的数据。 **Proxy#addBook** 这个方法执行在客户端,执行过程和getBookList是一样的,addBook没有返回值,所以不需要从_reply中取出返回值。 > **注意**: > 1、当客户端发起远程请求时,由于当前线程会被挂起直到服务端进程返回数据,所以如果一个远程方法是很耗时的,那么不能在UI线程中发起远程请求; > 2、由于服务端的Binder运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。 从上面的分析过程来看,完全可以不提供AIDL文件即可实现Binder,之所以提供AIDL文件,是为了方便系统为我们生成代码。 自己实现: (1)声明一个AIDL性质的接口,只需要继承IInterface接口即可,IInterface接口中只有一个asBinder方法。 ~~~ package com.ryg.chapter_2.manualbinder; import java.util.List; import android.os.IBinder; import android.os.IInterface; import android.os.RemoteException; public interface IBookManager extends IInterface { static final String DESCRIPTOR = "com.ryg.chapter_2.manualbinder.IBookManager"; static final int TRANSACTION_getBookList = (IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addBook = (IBinder.FIRST_CALL_TRANSACTION + 1); public List<Book> getBookList() throws RemoteException; public void addBook(Book book) throws RemoteException; } ~~~ 可以看到,在接口中声明了一个Binder描述符和另外两个id,这两个id分别表示getBookList(),和addBook()方法。 (2)实现Stub类和Stub类中的Proxy代理类 ~~~ package com.ryg.chapter_2.manualbinder; import java.util.List; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; public class BookManagerImpl extends Binder implements IBookManager { /** Construct the stub at attach it to the interface. */ public BookManagerImpl() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an IBookManager interface, generating a proxy * if needed. */ public static IBookManager asInterface(IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof IBookManager))) { return ((IBookManager) iin); } return new BookManagerImpl.Proxy(obj); } @Override public IBinder asBinder() { return this; } @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_getBookList: { data.enforceInterface(DESCRIPTOR); List<Book> result = this.getBookList(); reply.writeNoException(); reply.writeTypedList(result); return true; } case TRANSACTION_addBook: { data.enforceInterface(DESCRIPTOR); Book arg0; if ((0 != data.readInt())) { arg0 = Book.CREATOR.createFromParcel(data); } else { arg0 = null; } this.addBook(arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } @Override public List<Book> getBookList() throws RemoteException { // TODO 待实现 return null; } @Override public void addBook(Book book) throws RemoteException { // TODO 待实现 } private static class Proxy implements IBookManager { private IBinder mRemote; Proxy(IBinder remote) { mRemote = remote; } @Override public IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public List<Book> getBookList() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); List<Book> result; try { data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(TRANSACTION_getBookList, data, reply, 0); reply.readException(); result = reply.createTypedArrayList(Book.CREATOR); } finally { reply.recycle(); data.recycle(); } return result; } @Override public void addBook(Book book) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { data.writeInterfaceToken(DESCRIPTOR); if ((book != null)) { data.writeInt(1); book.writeToParcel(data, 0); } else { data.writeInt(0); } mRemote.transact(TRANSACTION_addBook, data, reply, 0); reply.readException(); } finally { reply.recycle(); data.recycle(); } } } } ~~~ 手动去写的意义在于更加理解Binder的工作原理,同时提供了一种不通过AIDL文件来实现Binder的新方式。如果手写的Binder,那么在服务端只需要创建一个BookManagerImpl的对象,并在Service中返回即可。 #### (4)Binder中两个很重要的方法 linkToDeath,unlinkToDeath Binder运行在服务端进程中,如果服务端的进程由于某种原因异常终止,这个时候我们到服务端的Binder连接断裂(称之为Binder死亡),会导致我们的远程调用失败,更为关键的是,由于客户端不知道Binder连接已经断裂,那么客户端的功能就会受到影响。 为了解决这个问题,Binder中提供了两个配对的方法linkToDeath,unlinkToDeath,通过linkToDeath我们可以给Binder设置一个死亡代理,当Binder死亡时,我们就会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接。 方法: 首先声明一个DeathRecipient对象,DeathRecipient是一个接口,内部只有一个方法,binderDied,我们需要实现这个方法,当Binder死亡的时候,系统就会回调binderDied方法,然后我们就可以移除之前绑定的binder代理并重新绑定远程服务。 private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { Log. d(TAG, "binder died. tname:" + Thread.currentThread().getName()); if ( mRemoteBookManager == null) return; mRemoteBookManager.asBinder(). unlinkToDeath(mDeathRecipient, 0); mRemoteBookManager = null; // TODO:这里重新绑定远程Service } }; 其次在客户端绑定远程服务成功之后,给Binder设置死亡代理: `mRemoteBookManager .asBinder().linkToDeath(mDeathRecipient, 0);` 其中linkToDeath的第二个参数,是个标记位,我们直接设置为0即可。 通过上面的步骤就给Binder设置了死亡代理,当Binder死亡的时候就可以收到通知了,另外通过Binder的方法isBinderAlive也可以判断Binder是否死亡。 ## 二、IPC机制续(IPC方式) 具体方式有很多,比如可以在Intent中附加Extra来传递信息,或者通过共享文件的方式来共享数据,还可以采用Binder方式来跨进程通信,另外,Content Provider天生就是支持跨进程访问的,因此,我们也可以使用它来进行IPC,另外通过网络通信也是可以实现数据传递的,所以Socket也可以实现IPC。 ### 1.使用Bundle 由于Bundle实现了Parcelable接口,所以它可以方便地在不同进程间传输。 除了直接传递数据这种典型的使用场景,他还有一种特殊的使用场景,如A进程正在进行计算,计算完成之后需要把结果传递给B进程,但是这个结果不支持放入Bundle中,那么可以这样考虑,A中,通过Intent启动B进程的一个Service组件(如IntentService),让Service进行后台计算,计算完毕之后,再启动B进程中真正想要启动的组件由于Service也在B进程中,所以目标组件就可以直接获取结果。 findViewById(R.id. button).setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(); intent.setClass(MainActivity. this, SecondActivity.class); User user = new User(0, "jake", true); user. book = new Book(); intent.putExtra( "extra_user", (Serializable) user); startActivity( intent); } }); ### 2.使用文件共享 两个进程通过读写同一个文化夹来交换数据,比如A进程把数据写进文件,B进程通过读取这个文件来获取数据。Linux使得并发读写文件可以没有限制,甚至两个线程同时对一个文件进行读写都是运行的。 希望在ManActivity中的onResume中序列化一个User对象到SDk卡上面的一个文件里面,在SecondActivity的onResume中去反序列化。 MainActivity:onResume执行下面的方法 private void persistToFile() { new Thread( new Runnable() { @Override public void run() { User user = new User(1, "hello world", false); File dir = new File(MyConstants. CHAPTER_2_PATH); if (! dir.exists()) { dir.mkdirs(); } File cachedFile = new File(MyConstants. CACHE_FILE_PATH ); ObjectOutputStream objectOutputStream = null; try { objectOutputStream = new ObjectOutputStream( new FileOutputStream(cachedFile)); objectOutputStream.writeObject( user); Log. d(TAG, "persist user:" + user); } catch (IOException e) { e.printStackTrace(); } finally { MyUtils. close(objectOutputStream); } } }).start(); } SecondActivity中取: private void recoverFromFile() { new Thread(new Runnable() { @Override public void run() { User user = null; File cachedFile = new File(MyConstants. CACHE_FILE_PATH); if ( cachedFile.exists()) { ObjectInputStream objectInputStream = null; try { objectInputStream = new ObjectInputStream( new FileInputStream( cachedFile)); user = (User) objectInputStream.readObject(); Log. d(TAG, "recover user:" + user); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { MyUtils. close(objectInputStream); } } } }).start(); } 当然这种不支持并发,如果想要并发,需要使用线程同步机制来解决。SharePreferences是个特例,通过键值对来存储数据,底层采用xml来存储键值对,位置在/data/data/packagename/shared_prefs目录下面,从本质来说SharePreferences也属于文件的一种,但是由于系统对它的读写有一定的缓存策略,即在内存里面有一份SharePreferences文件的缓存,因此在多进程模式下,系统对他的读写变得不可靠,当面对高并发的读写访问就有很大几率丢失数据,因此不建议进程间通信使用SP。 ### 3.使用Messenger Messenger是一种轻量级的IPC方案,它的底层实现是AIDL。从构造方法可以很明显的看出AIDL的痕迹。 public Messenger(Handler target) { mTarget = target.getIMessenger(); } public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target); } Messenger的使用方法很简单,它对AIDL做了封装,使得我们可以更简单地进行线程间通信,同时由于它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题,这个是因为服务端不存在并发执行的情况。 步骤: 1.服务端进程,首先我们要创建一个Service来处理客户端的请求,同时创建一个Handler并通过它来创建一个Messenger对象,在Service的onBind里面返回这个Messenger对象底层的Binder即可。 2.客户端进程,首先要绑定服务端的Service,绑定成功之后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了发送消息类型为Message对象。 如果要服务端能够回应客户端,就和服务端一样,需要创建一个Handler并创建一个新的Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端。 服务端: ~~~ public class MessengerService extends Service { private static final String TAG = "MessengerService"; private static class MessengerHandler extends Handler { @Override public void handleMessage(Message msg) { switch ( msg. what) { case MyConstants. MSG_FROM_CLIENT: Log. i(TAG, "receive msg from Client:" + msg.getData().getString( "msg")); Messenger client = msg. replyTo; Message relpyMessage = Message. obtain(null, MyConstants.MSG_FROM_SERVICE ); Bundle bundle = new Bundle(); bundle.putString( "reply", "嗯,你的消息我已经收到,稍后会回复你。" ); relpyMessage.setData( bundle); try { client. send(relpyMessage); } catch (RemoteException e) { e.printStackTrace(); } break; default: super.handleMessage( msg); } } } private final Messenger mMessenger = new Messenger( new MessengerHandler()); @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); } @Override public void onCreate() { super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand( intent, flags, startId); } } ~~~ 客户端: ~~~ public class MessengerActivity extends Activity { private static final String TAG = "MessengerActivity"; private Messenger mService; private Messenger mGetReplyMessenger = new Messenger( new MessengerHandler()); private static class MessengerHandler extends Handler { @Override public void handleMessage(Message msg) { switch ( msg. what) { case MyConstants. MSG_FROM_SERVICE: Log. i(TAG, "receive msg from Service:" + msg.getData().getString( "reply")); break; default: super.handleMessage( msg); } } } private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { mService = new Messenger( service); Log. d(TAG, "bind service"); Message msg = Message. obtain(null, MyConstants.MSG_FROM_CLIENT ); Bundle data = new Bundle(); data.putString( "msg", "hello, this is client."); msg.setData( data); msg. replyTo = mGetReplyMessenger; try { mService.send( msg); } catch (RemoteException e) { e.printStackTrace(); } } public void onServiceDisconnected(ComponentName className) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate( savedInstanceState); setContentView(R.layout. activity_messenger); Intent intent = new Intent( "com.ryg.MessengerService.launch"); bindService( intent, mConnection, Context. BIND_AUTO_CREATE); } @Override protected void onDestroy() { unbindService( mConnection); super.onDestroy(); } } ~~~ Mainfest里面: <activity android:name= ".messenger.MessengerActivity" android:label= "@string/title_activity_messenger" > <intent-filter > <action android:name ="android.intent.action.MAIN" /> </intent-filter > </activity > <service android:name= ".messenger.MessengerService" android:process= ":remote" > <intent-filter > <action android:name ="com.ryg.MessengerService.launch" /> </intent-filter > </service > > **注意**: > 通过Messenger来传递Message,Message中能用的载体只有what,arg1,arg2,Bundle以及replyTo。Message中的另外一个字段object在同一个进程中是很实用的,但是在跨进程间通信的时候,在Android2.2以前object字段不支持跨进程传输, > 即使2.2以后,也仅仅是系统提供的实现了Parcelable接口的对象才能通过它来传输。这就意味着我们自定义的Parcelable对象是无法通过object字段来传输的。 Messenger的工作原理图: Messenger的工作原理 ### 4.使用AIDL 由于Messenger的主要作用还是传递消息,有时候可能需要跨进程调用服务端的方法,那么Messenger就不行了。 使用AIDL进行跨进程通信也分为客户端和服务端两个方面: #### (1)服务端 服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口咋这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。 #### (2)客户端 首先绑定服务端的Service,绑定成功之后,将服务端返回的Binder对象转换成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。 #### (3)AIDL接口的创建 ~~~ package com.ryg.chapter_2.aidl; import com.ryg.chapter_2.aidl.Book; interface IBookManager { List<Book> getBookList(); void addBook(in Book book); } ~~~ AIDL文件支持的数据类型: * 基本数据类型(int,long,char,boolean,double等) * String和CharSquence * List,只支持ArrayList,里面的每个元素都要必须能被AIDL支持 * Map,只支持HashMap,里面的每个元素都必须被AIDL支持, 包括Key和Value * Parcelable,所有实现了Parcelable接口的对象 * AIDL,所有的AIDL接口本身也可以在AIDL文件中使用 以上的6种数据类型就是AIDL所支持的所有类型,其中自定义的Parcelable对象和AIDL对象必须要显式import进来,不管是否和当前的AIDL文件位于同一个包里面。 * * * * * **另外,如果AIDL文件里面用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在里面声明它为Parcelable类型。** * * * * * 在IBookManager.aidl文件中使用到了自定义的Book对象,所以必须创建Book.aidl在里面添加: ~~~ package com.ryg.chapter_2.aidl; parcelable Book; ~~~ AIDL中每个实现了Parcelable接口的类型的类,都需要像上面那样去声明,创建对应的AIDL文件,并声明那个类为parcelable。除此之外,AIDL中除了基本数据类型,其他类型的参数必须标上方向,in,out或者inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。AIDL接口中只支持方法,不支持静态常量。 in,out,inout区别: http://hold-on.iteye.com/blog/2026138 inout区别 inout区别 为了方便开发,建议把所有的和AIDL相关的类和文件全部放入同一个包中,这样的好处是,当客户端是另外的应用时,我们可以直接把整个包复制放入到客户端工程中。 AIDL包结构,在客户端和服务端要一致,否则会运行出错,这是因为客户端需要反序列化服务端中和AIDL接口相关的所有类。 #### (4)远程服务端的Service实现 ~~~ public class BookManagerService extends Service { private static final String TAG = "BMS"; private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>(); private Binder mBinder = new IBookManager.Stub() { @Override public List<Book> getBookList() throws RemoteException { return mBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add( book); } }; @Override public void onCreate() { super.onCreate(); mBookList.add(new Book(1, "Android")); mBookList.add(new Book(2, "Ios")); } @Override public IBinder onBind(Intent intent) { return mBinder; } } <service android:name= ".aidl.BookManagerService" android:process= ":remote" > </service > ~~~ 采用CopyOnWriteArrayList,它支持并发读写,AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接的时候,会存在多个线程同时访问的问题,所以要在AIDL方法中处理线程同步,这里使用它来进行自动的线程同步。 服务端可以使用CopyOnWriteArrayList和ConcurrentHashMap来进行自动线程同步,客户端拿到的依然是ArrayList和HashMap。 #### (5)客户端的实现 客户端首先绑定远程服务,绑定成功之后将服务端返回的Binder对象转换成为AIDL接口,然后就可以通过这个接口去调用服务端的远程方法了。 ~~~ public class BookManagerActivity extends Activity { private static final String TAG = "BookManagerActivity"; private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { IBookManager bookManager = IBookManager.Stub.asInterface(service); try { List<Book> list = bookManager.getBookList(); Log. i(TAG, "query book list, list type:" + list.getClass().getCanonicalName()); Log. i(TAG, "query book list:" + list.toString()); } catch (RemoteException e) { e.printStackTrace(); } } public void onServiceDisconnected(ComponentName className) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate( savedInstanceState); setContentView(R.layout. activity_book_manager); Intent intent = new Intent( this, BookManagerService. class); bindService( intent, mConnection, Context. BIND_AUTO_CREATE); } @Override protected void onDestroy() { unbindService( mConnection); super.onDestroy(); } } ~~~ #### (6)监听 比如现在想服务端有新书的时候通知客户端,那么必须要监听了需要使用RemoteCallbackList,存储我们自定义的监听器,它是一个泛型,支持管理任意的AIDL接口。它的内部是Map结构,key是IBinder类型,value是Callback类型。 * * * * * **注意:** 服务端和客户端之间做监听器,服务端需要使用RemoteCallbackList,否则客户端的监听器无法收到通知(因为服务端实质还是一份新的序列化后的监听器实例,并不是客户端那份) * * * * * RemoteCallbackList的beginBroadcast和finishBroadcast必须配对使用,哪怕我们仅仅需要获取RemoteCallbackList中的元素个数。 #### (7)不要在客户端的ui线程里面调用服务端的耗时方法 客户端调用远程服务方法时,因为远程方法运行在服务端的binder线程池中(服务端方法可以执行大量耗时操作,不需要开线程执行异步任务的); 同时客户端线程会被挂起,所以如果该方法过于耗时,而客户端又是UI线程,会导致ANR,所以当确认该远程方法是耗时操作时,应避免客户端在UI线程中调用该方法。 同理,当服务器调用客户端的listener方法时,该方法也运行在客户端的binder线程池中,所以如果该方法也是耗时操作,请确认运行在服务端的非UI线程中。 另外,因为客户端的回调listener运行在binder线程池中,所以更新UI需要用到handler。 #### (8)服务端进程意外终止 客户端通过IBinder.DeathRecipient来监听Binder死亡,也可以在onServiceDisconnected中监听并重连服务端。区别在于前者是在binder线程池中,访问UI需要用Handler,后者则是UI线程。 #### (9)权限验证 可通过自定义权限在onBind或者onTransact中进行权限验证。 onBind中验证,验证不通过返回null,验证方式可以使用permission验证,首先在manifest里面注册。 ~~~ <permission android:name= "com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" android:protectionLevel= "normal" /> ~~~ 就可以在onBind里面验证了。 @Override public IBinder onBind(Intent intent) { int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" ); Log. d(TAG, "onbind check=" + check); if ( check == PackageManager. PERMISSION_DENIED) { return null; } return mBinder; } 一个应用来绑定我们的服务的时候,会验证这个应用的权限,没有权限就返回null。这个方法同样适用于Messenger中。 我们自己内部的应用想要绑定我们的服务,只需要在Manifest采用如下方式使用permission即可。 `<uses-permission android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" />` 服务端的onTransact方法中进行权限验证,验证失败就会返回false,服务端的方法就不会执行,验证方式可以采用permission验证,也可以使用Uid和Pid来验证。通过getCallingUid和getCallingPid可以拿到客户端所属的应用的Uid和Pid,通过这两个参数可以做一些验证工作,比如验证包名。 下面即验证了权限又需要包名以com.rgy开始。 public boolean onTransact( int code, Parcel data, Parcel reply, int flags) throws RemoteException { int check = checkCallingOrSelfPermission( "com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" ); Log. d(TAG, "check=" + check); if ( check == PackageManager. PERMISSION_DENIED) { return false; } String packageName = null; String[] packages = getPackageManager().getPackagesForUid( getCallingUid()); if ( packages != null && packages. length > 0) { packageName = packages[0]; } Log. d(TAG, "onTransact: " + packageName); if (! packageName.startsWith( "com.ryg")) { return false; } return super.onTransact( code, data, reply, flags); } ### 5.使用ContentProvider 底层实现是AIDL,使用比AIDL简单许多。自定义ContentProvider需要继承ContentProvider并实现里面的方法即可,onCreate,query,update,insert,delete,getType,getType用来返回Uri请求所对应的MimeType。如果应用不关系这个,只需要返回null或者”/“,根据Binder,我们知道这6个方法均运行在ContentProvider的进程里面,除了onCreate方法由系统回调并运行在主线程里面,其余的5个均由外界调用并运行在Binder线程池中。 虽然ContentProvider的底层数据看起来很像一个SQLite数据库,但是它对底层的数据的存储方式没有任何要求,我们即可以使用SQLite,也可以使用普通文件,甚至可以采用内存中的一个对象来进行数据的存储。 注册Android:authorities是它的唯一标识,建议命名的时候加上包名前缀,如果声明了权限,那么外界应用也需要相应的权限。 ContentProvider(有的手机上会出现不加uses-permission依然可以访问BookProvider的问题) ### 6.使用Socket Socket 一般用于网络通信,AIDL用这种方式会过于繁琐,不建议。 ### 7.Binder连接池 比如100个地方需要用到AIDL那么不可能创建100个Service,需要减少Service的数量,将AIDL放在同一个Service里面去管理。 Binder连接池的作用就是将每个业务模块的Binder请求统一转发到远程的Service中去。 Binder连接池 Binder连接池,通过BinderPool的方式将Binder的控制与Service本身解耦,同时只需要维护一份Service即可。这里用到了CountDownLatch,大概解释下用意:线程在await后等待,直到CountDownLatch的计数为0,BinderPool里使用它的目的是为了保证Activity获取BinderPool的时候Service已确定bind完成~ 例子: 两个AIDL: ISecurityCenter.aidl ~~~ package com.ryg.chapter_2.binderpool; interface ISecurityCenter { String encrypt(String content); String decrypt(String password); } ICompute.aidl package com.ryg.chapter_2.binderpool; interface ICompute { int add( int a, int b); } ~~~ 实现: ~~~ public class SecurityCenterImpl extends ISecurityCenter.Stub { private static final char SECRET_CODE = '^'; @Override public String encrypt(String content) throws RemoteException { char[] chars = content.toCharArray(); for (int i = 0; i < chars.length; i++) { chars[ i] ^= SECRET_CODE; } return new String(chars); } @Override public String decrypt (String password ) throws RemoteException { return encrypt( password); } } ~~~ 以及: ~~~ public class ComputeImpl extends ICompute.Stub { @Override public int add(int a, int b) throws RemoteException { return a + b; } } ~~~ 为Binder连接池创建AIDL接口IBinderPool.aidl ~~~ interface IBinderPool { /** * @param binderCode, the unique token of specific Binder<br/> * @return specific Binder who's token is binderCode. */ IBinder queryBinder( int binderCode); } ~~~ 为Binder连接池创建远程Service并实现IBinderPool, 下面是queryBinder的实现: @Override public IBinder queryBinder( int binderCode) throws RemoteException { IBinder binder = null; switch ( binderCode) { case BINDER_SECURITY_CENTER: { binder = new SecurityCenterImpl(); break; } case BINDER_COMPUTE: { binder = new ComputeImpl(); break; } default: break; } return binder; } 远程Service的实现比较简单了: ~~~ public class BinderPoolService extends Service { private static final String TAG = "BinderPoolService"; private Binder mBinderPool = new BinderPool.BinderPoolImpl(); @Override public void onCreate() { super.onCreate(); } @Override public IBinder onBind(Intent intent ) { Log.d(TAG, "onBind"); return mBinderPool; } @Override public void onDestroy() { super.onDestroy(); } } ~~~ 下面还有Binder连接池的具体实现,首先绑定远程服务,成功之后,客户端就可以通过它的queryBinder方法获取各自对应的Binder,拿到所需要的Binder之后,不同的业务模块之间就可以进行各自的操作了。 ~~~ public class BinderPool { private static final String TAG = "BinderPool"; public static final int BINDER_NONE = -1; public static final int BINDER_COMPUTE = 0; public static final int BINDER_SECURITY_CENTER = 1; private Context mContext; private IBinderPool mBinderPool; private static volatile BinderPool sInstance; private CountDownLatch mConnectBinderPoolCountDownLatch; private BinderPool(Context context) { mContext = context.getApplicationContext(); connectBinderPoolService(); } public static BinderPool getInsance(Context context) { if (sInstance == null) { synchronized (BinderPool.class) { if (sInstance == null) { sInstance = new BinderPool(context); } } } return sInstance; } private synchronized void connectBinderPoolService() { mConnectBinderPoolCountDownLatch = new CountDownLatch(1); Intent service = new Intent(mContext, BinderPoolService.class); mContext.bindService(service, mBinderPoolConnection, Context.BIND_AUTO_CREATE); try { mConnectBinderPoolCountDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } /** * query binder by binderCode from binder pool * * @param binderCode * the unique token of binder * @return binder who's token is binderCode<br> * return null when not found or BinderPoolService died. */ public IBinder queryBinder(int binderCode) { IBinder binder = null; try { if (mBinderPool != null) { binder = mBinderPool.queryBinder(binderCode); } } catch (RemoteException e) { e.printStackTrace(); } return binder; } private ServiceConnection mBinderPoolConnection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { // ignored. } @Override public void onServiceConnected(ComponentName name, IBinder service) { mBinderPool = IBinderPool.Stub.asInterface(service); try { mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0); } catch (RemoteException e) { e.printStackTrace(); } mConnectBinderPoolCountDownLatch.countDown(); } }; private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { Log.w(TAG, "binder died."); mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0); mBinderPool = null; connectBinderPoolService(); } }; public static class BinderPoolImpl extends IBinderPool.Stub { public BinderPoolImpl() { super(); } @Override public IBinder queryBinder(int binderCode) throws RemoteException { IBinder binder = null; switch (binderCode) { case BINDER_SECURITY_CENTER: { binder = new SecurityCenterImpl(); break; } case BINDER_COMPUTE: { binder = new ComputeImpl(); break; } default: break; } return binder; } } } ~~~ 使用: ~~~ public class BinderPoolActivity extends Activity { private static final String TAG = "BinderPoolActivity"; private ISecurityCenter mSecurityCenter; private ICompute mCompute; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate( savedInstanceState); setContentView(R.layout. activity_binder_pool); new Thread(new Runnable() { @Override public void run() { doWork(); } }).start(); } private void doWork() { BinderPool binderPool = BinderPool.getInsance(BinderPoolActivity. this); IBinder securityBinder = binderPool .queryBinder(BinderPool. BINDER_SECURITY_CENTER); mSecurityCenter = (ISecurityCenter) SecurityCenterImpl . asInterface(securityBinder); Log.d(TAG, "visit ISecurityCenter"); String msg = "helloworld-安卓"; System. out.println( "content:" + msg); try { String password = mSecurityCenter.encrypt( msg); System. out.println( "encrypt:" + password); System. out.println( "decrypt:" + mSecurityCenter.decrypt(password )); } catch (RemoteException e) { e.printStackTrace(); } Log.d(TAG, "visit ICompute"); IBinder computeBinder = binderPool .queryBinder(BinderPool. BINDER_COMPUTE); mCompute = ComputeImpl.asInterface(computeBinder ); try { System. out.println( "3+5=" + mCompute.add(3, 5)); } catch (RemoteException e) { e.printStackTrace(); } } } ~~~ ### 8.选择适合的IPC方式 选择方式 ![](https://box.kancloud.cn/2016-05-29_574aa68a9fbed.png)