IPC简介
IPC是Inter-Process Communication的缩写。含义为进程间通信或者跨进程通信。是指两个进程之间进行数据交换的过程。
线程:按照操作系统中的描述,线程是cpu调度的最小单元,同时线程是一种有限的系统资源。
进程:一般指一个执行单元,在pc和移动设备上指一个程序或者一个应用。
IPC不是android独有的,像window上就可以通过剪切板等进行进程间通信。android是基于linux内核的移动操作系统,它的进程间通信方式并不能完全继承制linux。它有自己的进程间通信的方式。android中最有特色的进程通信方式是Binder.另外还支持Scoket。
android中的多进程模式
1.开启多进程模式
一般情况下,android中的多进程指一个应用中存在多个进程的情况,暂不讨论多个应用之间的多进程情况。在android中使用多进程只有一种方法,就是在清单文件中对四大组件指定android:process属性。所以我们无法给一个线程或者一个实体类指定其运行的进程。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.lvqueen.myartexplore"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".day01.FirstActivity" android:process=":process1" /> <!--:process1是指在当前进程名前附加上当前的包名,是简写。属于当前应用的私有进程,其他应用的组件 不能和它跑在同一个进程中。 --> <activity android:name=".day01.SecondActivity" android:process="com.lvqueen.myartexplore.process1" ></activity> <!--写完整路径的这种进程,属于全局进程。其他应用可以通过shareUID方式可以和它泡在同一个进程中--> <!--android系统会为每一个应用分配UID具有相同UID的应用才能共享数据。两个应用通过ShareUID方式跑在同一个 进程中要求这两个应用有相同的ShareUID并且签名相同,这种情况下可以互相访问对方的私有数据data目录,组件信息, 还可以共享内存数据。或者说他们看起来像一个应用的两部分。 --> </application> </manifest>
⚠️注意:多进程造成的问题
(1)静态成员和单例模式完全失效:android为每一个应用(进程)分配了一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致了在不同的虚拟机中访问同一个类对象会产生多份副本。并且这两个副本是互不干扰的,在一个进程中修改一个属性指,只会影响当前进程。而对其他进程不会造成任何影响。
(2)线程同步机制完全失效:既然都不是一块内存了,那么不管是锁对象还是锁全局类都无法保证线程同步。因为不同进程锁的不适同一个对象。
(3)SharePreferences的可靠性下降:它不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失。因为share底层是通过读写XML文件来实现的,并发写显然是可能出现问题的,并发读都有可能出现问题。
(4)Application会多次创建:当一个组件跑在一个新的进程中时,由于系统要创建新的进程同时分配独立的虚拟机,所及这个过程其实就是启动一个应用的过程,因此相当于系统又把这个应用重新启动了一遍,既然重新启动,那么自然就会创建新的Appliction.
IPC基础概念介绍
1.Serializable:实现对象序列化。是java中的序列化接口,使用起来简单,但是花销很大,需要大量的i/o操作。序列化将对象转化为文件,反序列化将文件转换为对象。
使用方式:只要将java类简单的实现Serializable接口就可以了。
注意:在实现Serializable接口的实现类中,有一个SerialVersionUID(public static final long serialVersionUID = 1234567l;)
它用于辅助序列化和反序列化。进行序列化时,系统会把这个值写入文件当中,反序列化时会取出这个值和反序列化的类中的这个值做比对。只有序列化中的这个值和当前类的这个值相同,才能正常被反序列化,否则说明当前类和序列化的类相比发生了某些变化,无法完成正常反序列化。一般来说,我们应该手动指定它的值。如果不手动指定,系统会根据当前类计算一个hash值赋值给serialVersionUID,如果类删除或增加了某个成员变量,就会使这个值发生变法,导致不能正常反序列化了。如果手动指定,当应用升级删除或增加了某个变量后,程序能够最大限度的恢复数据。
/** * 序列化,把对象直接写入文件中 */ private void toSerialize() { Student student = new Student(); try { ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt")); out.writeObject(student); out.close(); } catch (IOException e) { e.printStackTrace(); } } /** * 反序列化,从文件中直接拿到对象 */ private void deSerialize() { try { ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cache.txt")); Student student = (Student) inputStream.readObject(); inputStream.close(); } catch (Exception e) { e.printStackTrace(); } }
2.Parcelable:实现对象序列化》android中的序列化方式,效率高,但是用复杂。
public class Book implements Parcelable{ public int boolId; public String bookName; /** * 进行反序列化,主要下面protected方法 * @param boolId * @param bookName */ public Book(int boolId, String bookName) { this.boolId = boolId; this.bookName = bookName; } /** * 从序列化后的对象中创建原始对象 * @param in * @return */ protected Book(Parcel in) { boolId = in.readInt(); bookName = in.readString(); // in.readParcelable(Thread.currentThread().getContextClassLoader()) } /** * 返回当前对象的内容描述,如果含有文件描述返回1,否则返回0,基本为0; * @return */ @Override public int describeContents() { return 0; } /** * 将当前对象写入序列化结构中,除了一版的数据类型,也可以序列化一个序列化的对象 * parcel.writeParcelable()序列化一个序列化的对象 * @param parcel * @param i:值为1或0,标记为1时表示当前对象需要作为返回值返回,不能立即释放资源 * 基本上这个值都为0. */ @Override public void writeToParcel(Parcel parcel, int i) { parcel.writeInt(boolId); parcel.writeString(bookName); // parcel.writeParcelable(); } public static final Creator<Book> CREATOR = new Creator<Book>() { /** * 从序列化后的对象中创建原始对象 * @param in * @return */ @Override public Book createFromParcel(Parcel in) { return new Book(in); } /** * 创建制定长度的原始对象数组 * @param size * @return */ @Override public Book[] newArray(int size) { return new Book[size]; } }; }
3.Binder的使用和上层原理。
从IPC角度来说Binder是android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,设备驱动是/dev/binder。
从android Framework角度来时,Binder是ServiceManager连接各种Manger和相应的ManagerService的桥梁。
从android应用层来说Binder是客户端和服务器端进行通信的媒介。
android开发中Binder主要用于Service中,包括AIDL(android interface definition language 安卓接口定义语言)和Messenger。普通Service中不涉及进程间通信。Messenger底层为AIDL。
4.aidl文件创建
(在android studio中)例如你要创建的aidl文件在com.exmple.test.myaildtest文件路径下,则选择main-jave-com.exmple.test.myaildtest然后右键-new -AIDL -AIDL File这时候会在与java文件同级创建一个aidl文件夹,下面有个com.exmple.test.myaildtest文件夹,里面有你创建的aidl文件,写上你要添加的一些方法。使用之前rebulid一下,在build-generated-source-aidl-debug-com.exmple.test.myaildtest会生成一个与你创建的aild文件同名的文件,这时候你就可以使用了。
与java文件夹同级下的.aidl文件
⚠️注意:当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的,那不能在ui线程中发起。另外由于服务端的binder方法运行在binder的线程池中,所以binder方法不管是否耗时,都应该采取同步的方式去实现,因为它已经运行在一个线程中了。
interface IBookManager { List<String> getBookList(); void addBook(String book); }
build下自动生成的文件
public interface IBookManager extends android.os.IInterface { /** * Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.lvqueen.myartexplore.aidllearn.IBookManager { // Binder的唯一标示,一般用类名 private static final java.lang.String DESCRIPTOR = "com.lvqueen.myartexplore.aidllearn.IBookManager"; /** * Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.lvqueen.myartexplore.aidllearn.IBookManager interface, * generating a proxy if needed. * 用于将服务端binder对象转换成客户端所需的aidl接口类型对象。如果客户端和服务器端运行在统一进程,返回服务端 * stub对象本身,否则返回stub.proxy */ public static com.lvqueen.myartexplore.aidllearn.IBookManager asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.lvqueen.myartexplore.aidllearn.IBookManager))) { return ((com.lvqueen.myartexplore.aidllearn.IBookManager) iin); } return new com.lvqueen.myartexplore.aidllearn.IBookManager.Stub.Proxy(obj); } /** * 返回当前binder对象 * @return */ @Override public android.os.IBinder asBinder() { return this; } /** * 运行在服务器端的binder线程池中。当客户端发起跨进程请求时,远程请求会通过系统底层封装后由此方法 * 来处理,服务端通过code确定客户端手请求的目标方法。接着从data中取目标方法所需要的参数,执行目标方法。 * 之后向reply中写入返回值。如果onTransact返回false,客户端请求会失败。可以用来做权限验证。这样就不能 * 随便一个进程都能远程调用我们的服务了。 * @param code * @param data * @param reply * @param flags * @return * @throws android.os.RemoteException */ @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<java.lang.String> _result = this.getBookList(); reply.writeNoException(); reply.writeStringList(_result); return true; } case TRANSACTION_addBook: { data.enforceInterface(DESCRIPTOR); java.lang.String _arg0; _arg0 = data.readString(); this.addBook(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.lvqueen.myartexplore.aidllearn.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; } /** * 运行在客户端。创建方法说需要的输入型parcel对象_data输出型parcel对象_reply和返回值对象list; * 然后把该方法的参数信息写入_data中。接着调用transact方法来发起RPC(远程过程调用)请求,同时当前 * 线程挂起,然后服务器端的ontransact方法会被调用。直到RPC过程返回后,当前线程继续执行,并从_reply * 中取出RPC过程的返回结果,最后返回_reply中的数据。 * * @return * @throws android.os.RemoteException */ @Override public java.util.List<java.lang.String> getBookList() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List<java.lang.String> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0); _reply.readException(); _result = _reply.createStringArrayList(); } finally { _reply.recycle(); _data.recycle(); } return _result; } /** * 运行咋看客户端,执行同上,没有返回值,所以不需要从_reply中取出返回值。 * @param book * @throws android.os.RemoteException */ @Override public void addBook(java.lang.String 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); _data.writeString(book); 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<java.lang.String> getBookList() throws android.os.RemoteException; public void addBook(java.lang.String book) throws android.os.RemoteException; }
关于进程间通信的博客