IPC机制
- IPC含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。按照操作系统的说法,在pc或者移动设备上,进程一般指一个执行单元,一般指一个程序或者一个应用。
- 对于Android这个基于Linux内核的移动操作系统,他的进程间通信方式并不完全依赖于Linux,相反,他有自己的进程间通信方式 ,在Android中最有特色的进程间通信方式就是Binder了,通过Binder可以轻松实现进程间通信,当然,同一个设备上的两个进程通过Socket通信也是可行的。
- 要学习进程间通信,那么我们首先需要的就是两个以上的进程了,这里我们可以使用android的多进程模式来进行示范(Android的多进程是由于早期的移动设备由于自身内存原因给每个应用分配的内存不大,这个时候如果想使用大一点的内存,就出现了多进程)
Android应用的多进程模式
开启多进程模式
- 在Android中使用多进程只有一种方法,那就是给四大组件在AndroidMenifest中指定android:process属性,这是常规的使用多进程的方法,还有一种就是使用JNI在native层去fork一个新的进程,不过这种方法属于特殊情况,在这里不再说明
- 一般的多进程如下使用
<activity android:name=".LearnMoreProcess.LearnMoreProcessActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:process="com.example.LearnMoreProcess.progress1"
android:name=".LearnMoreProcess.LearnMoreProcess2Activity" />
<activity
android:process="com.example.LearnMoreProcess.progress2"
android:name=".LearnMoreProcess.LearnMoreProcess3Activity"/>
- 这里我创建了三个activity,给第二个和第三个分别指定属性为com.example.LearnMoreProcess.progress1和com.example.LearnMoreProcess.progress2
- 其中,加入我们不使用process属性的话,在程序启动的时候,会为他启动一个名为包名的进程
- 这里我运行一下我这个程序,并在第一个activity的onCreate方法里面启动第二个activity,在第二个activity的onCreate方法里面启动第三个activity,因为两个activity都是指定在不同的进程中的,而activity如果不启动,那么他所在的进程也不会启动的
- 然后我通过android studio的命令行使用“ adb shell ps ”这个命令,他就会将我的手机的所有进程罗列出来,这里我费了好大劲才找到我们的程序的三个进程,如下
u0_a406 10086 684 1625864 60736 SyS_epoll_ 0000000000 S com.example.learnretrofit
u0_a406 10120 684 1620916 60132 SyS_epoll_ 0000000000 S com.example.LearnMoreProcess.progress1
u0_a406 10145 684 1621700 59824 SyS_epoll_ 0000000000 S com.example.LearnMoreProcess.progress2
- 可以看到,就是我们刚才指定的进程名字,并且没有显示指定线程的直接使用默认包名
- 因为我们的主题是进程间通信,那么进程有了,接下来就是通信了
进程间通信初探
- 我们先不去管进程间通信的具体事宜,这里我们试试直接按照直接通信会出现什么问题
- 我们独立新建一个数据类
public class Data {
public static int sUserID = -1;
}
- 就定义一个数据而已,然后我们在启动第一个activity的时候将值修改为1,启动第二个activity的时候将值修改为2,启动第三个activity的时候将他的值修改为3
- 就像这个样子,其他两个activity也是一样的
- 接下来在按返回键返回到第一个activity,然后在第一个activity的onReStart方法中log这个数据的值,像这样
protected void onRestart() {
super.onRestart();
Log.d(TAG, "onRestart: Data.sUserID = " + Data.sUserID);
}
- 最后我先启动程序,然后按返回键返回到第一个Activity,发现他打印的log并非我们想的3
onRestart: Data.sUserID = 1
- 而这个1是怎么来的呢,是我们在第一个activity里面修改的值,诶?我们不是在另外两个里面也修改了吗,为什么没生效?应该想到了吧,就是我们给两个activity指定不同的进程而导致的
- Android为每个应用都会分配一个独立的虚拟机,或者说为每个进程分配一个独立的虚拟机,不同的虚拟机享用不同的内存空间,所以,这就导致了不同的虚拟机中访问同一个类的对象的时候会产生不同的副本存储到不同的进程中,所以,我们在应用中使用三个进程去访问这个数据类的时候,他访问的并非同一个数据类,而是每个进程在本进程中储存的副本数据,所以这也就发生了我们在上面遇到的现象。
- 所以问题的出现缘由是,只要发生不同进程访问同一数据,就会出现上述问题,这个时候,为了解决不同进程间的数据共享,就出现了IPC机制
我们先看看使用多进程时会发生什么情况:
- 静态成员和单例模式完全失效
- 线程同步机制失效
- sharedPreferences的可靠性下降,原因是sharedPreferences不支持多进程并发读写
- Application会多次重建,原因是,当一个组件跑在新的进程中的时候,系统在创建新的进程的时候同时分配独立的虚拟机,这个过程其实就是启动一个应用的过程,所以自然会创建新的Application,所以,不同进程中的组件是属于不同的虚拟机和application的
- 这里我们可以做个试验,我们发现android studio的log界面是把每个进程独立出来的,就像这个样子
- 而我在每个activity的onCreate方法里面都写了这么一句话
- 输出所在application的toString方法的结果,然后在每个进程里面点进去发现并不一样
- 如下面
- 第二个进程
- 第三个进程
- 可见,三个activity所在的进程不同,导致他们的application都是不同的
- 不过,有没有发现,我们虽然说三个activity是处于不用的进程当中,但是他们之间的相互启动难道不是一种跨进程通信吗?所以说,我们在启动Activity用的Intent应该就是一种进程间通信的方式
IPC基础概念介绍
先来说一下进程间通信涉及到的三个东西,Serializable接口,Parcelable接口,BInder
- Serializable接口,Parcelable接口可以完成对象的序列化过程,当我们需要用Intent和Binder传输数据的时候就需要用到Serializable或者Parcelable。下面先来看看Serializable完成对象序列化的过程
Serializable
- Serializable是java提供的一个序列化接口你,他是一个空接口,为对象提供序列化和反序列化的操作,使用起来非常简单,只需要再类的声明中指定一个序列号,就会自动完成默认的序列化过程
- 比如,我先定义一个需要序列化操作的类,并给他继承自Serializable接口
class SerializableTest implements Serializable{
private static final long serialVersionUID = 1353513568413568431L;
public SerializableTest(String name, int id) {
this.name = name;
this.id = id;
}
public String name ;
public int id ;
}
- 这里的那个serialVersionUID 变量的作用稍后再说,我们只需要知道加上它很有必要就行了
- 一般的序列化我们可以这样做
void ssss(){
//序列化过程
SerializableTest test = new SerializableTest("测试",100);
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Test.txt"));
out.writeObject(test);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
//反序列化过程
try {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("Test.txt"));
SerializableTest t = (SerializableTest) in.readObject();
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
- 这里可以看到,序列化和反序列化只是进行了读写操作
- 那么我们之前说的那个长长的变量对我们的序列化有什么作用呢,在我们去序列化一个对象的时候,假设是将他写到一个文本文件当中,那么在写的时候,系统会将我们这里定义的这个长长的变量也会存储,当我们去反序列化将读取到的对象强转成我们本来的类型的时候就会去拿着我们所在类的这个参数是否与之前存储的参数是否一致,如果不一致就会出现异常
- 那么什么时候会出现异常呢,当我们不手动定义这个变量的时候,系统会根据我们类的结构给我们的类自动分配一个这个参数值,那么当比方说序列化之后改变类的结构,比方说增加了一个变量,这个时候这个变量就会变化,当反序列化的时候就会判断成不是一个对象,所以会出现异常,比如说
- 我在这里写完之后,然后把序列化的部分(也就是写文件的部分)注释掉,然后将我们定义的serialVersionUID这个变量改变一下,这个时候就报了这个异常
ssss: 反序列化的时候失败java.io.InvalidClassException: com.example.learnretrofit.LearnMoreProcess.SerializableTest; local class incompatible: stream classdesc serialVersionUID = 135351368413568431, local class serialVersionUID = 1353513683568431
- 可见他说的是,流对象的serialVersionUID值和本地类的值不一样出现异常
- 然后我再将这个参数注释掉,在写完之后,手动给类加上一个参数,再运行程序
ssss: 反序列化的时候失败java.io.InvalidClassException: com.example.learnretrofit.LearnMoreProcess.SerializableTest; local class incompatible: stream classdesc serialVersionUID = -7754834768189228868, local class serialVersionUID = -7283574124845410787
- 可以看到,依然报的是那个错误,不过此时的这个serialVersionUID 参数变成了系统分配的
- 这里我们再来试验一下,我们指定serialVersionUID 参数之后,写完之后,给类加上一个参数再去试着反序列化(也就是读文件操作)试一下,
class SerializableTest implements Serializable{
private static final long serialVersionUID = 1353513683568431L;
public SerializableTest(String name, int id) {
this.name = name;
this.id = id;
}
public int sss;
public String name ;
public int id ;
}
- 这里的sss参数是我序列化之后加上的,这里再将序列化部分注释掉,再运行程序
ssss: 反序列化得到的对象 name = 测试 sss = 0
- 可以看到,在我们手动指定serialVersionUID参数的时候就可以改变我们的类的构造而不用担心反序列化失败了,这里经过我的实验,不只是给类添加变量,甚至是删除变量也可以反序列化成功,这里就不再演示,有兴趣的话可以自行实验
- 这里做点补充,想要反序列化成功,最重要的是必须保证前后类名相同,静态成员变量不属于对象,不会参加序列化过程,其次,用transient标记的变量也不会参与序列化
- 这里相信大家已经明白序列化的意义和那个serialVersionUID参数的作用了吧,我们再去看看Parcelable接口
Parcelable接口
- Parcelable是一个接口,实现这个接口的类的对象就可以实现序列化并可以通过Intent和Binder传递
- 下面简单的示范一下这个接口的用法
public class ParcelableTest implements Parcelable {
public int id;
public String name;
public boolean isMale;
public Book mBook;
public ParcelableTest(int id, String name) {
this.id = id;
this.name = name;
}
public ParcelableTest(Parcel in) {
id = in.readInt();
name = in.readString();
isMale = in.readInt() == 1;
mBook = in.readParcelable(Thread.currentThread().getContextClassLoader());
}
public static final Creator<ParcelableTest> CREATOR = new Creator<ParcelableTest>() {
@Override
public ParcelableTest createFromParcel(Parcel in) {
return new ParcelableTest(in);
}
@Override
public ParcelableTest[] newArray(int size) {
return new ParcelableTest[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
dest.writeInt( isMale ? 1 : 0);
dest.writeParcelable(mBook,0);
}
}
class Book implements Parcelable{
public int id;
public Book(int id) {
this.id = id;
}
private Book(Parcel in) {
id = in.readInt();
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
}
}
- 这里说一下Parcel,Parcel内部包装了可序列化的数据,可以在Binder中自有传输,在上面的代码中可以看出,实现Parcelable会需要实现三部分的方法,序列化部分由writeToParcel方法实现,反序列化部分由Creator的类对象实现,Creator内部标记了如何创建序列化对象和数组,并通过Parcel的一系列的read方法来完成反序列化过程;内容描述部分由describeContents方法实现,几乎所有情况都返回0,当且仅当当前对象中存在文件描述符时,此方法返回1
- 需要注意的是,在反序列化的ParcelableTest(Parcel in)这个方法中,由于mbook是另一个可序列化对象,所以他的反序列化过程需要传递当前线程的上下文类加载器,否则会报无法找到类的错误
- 这里列举一下Parcelable的方法说明
方法 | 功能 |
---|---|
createFromParcel(Parcel in) | 从序列化的对象中创建原始对象 |
newArray(int size) | 创建指定长度的原始对象数组 |
ParcelableTest(Parcel in) | 从序列化后的对象创建原始对象 |
writeToParcel(Parcel dest, int flags) | 将当前对象写入序列化结构,其中flag有两种值,1代表们当前对象需要作为返回值返回,不过几乎所有情况都为0 |
describeContents() | 如果含有文件描述符,就返回 1,否则返回0(几乎所有情况都返回0) |
- 系统已经为我们提供了很多实现了Parcelable接口的类,他们都是可以直接序列化的,比如Intent,bundle,Bitmap等,同时List和Map也是可以序列化,前提是他们的元素都是可序列化
- 由上可知,Parcelable和Serializable都能实现序列化,并传输数据,那么而这该如何抉择呢,前者是Android的序列化方式,当然更适合应用在Android平台上,不过稍微用起来麻烦一点,但是效率高,而后者是java的序列化接口,使用简单但是开销很大
- Parcelable主要用在内存序列化,而Serializable主要用在网络传输的时候
Binder
- BInder这个东西涉及的东西太多,这里对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和Message,其中普通service中的Binder不涉及进程间通信,而Messenger的底层其实是AIDL,所以这里选择AIDL来分析Binder的工作机制
- 为了分析Binder的工作机制,我们需要新建一个AIDL示例,SDK会自动为我们产生AIDL所对应的BInder类,然后我们就可以分析Binder的工作过程,新建java包,然后新建Book.java,Book.aidl,和IBookManager.aidl,如下
- 这里创建java文件不用多说,创建aidl文件的时候,我们在Java文件所属文件夹的目录下右键,new -> AIDL 然后创建AIDL文件,不过这里会因为已经有一个Book.java 文件,所以这里我们在创建Book.aidl文件的时候会提示不能使用一样的名字,这里随便起个名字,等他创建成功再Rename就可以了
- 然后我们会发现android studio会自动在main文件夹下为我们自动创建一个aidl文件夹,就像这个样子
- 这里的aidl文件夹与java文件夹是同一等级的文件夹
- 这里的book.java文件和aidl文件和IBookManager.aidl文件内容分别如下
- book.java 文件
class Book implements Parcelable {
public int bookId;
public String bookName;
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
private Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
}
- book.aidl文件
package com.example.learnretrofit.AIDLTest;
parcelable Book;
- 这里的意思表示将Book类声明
- 再看IBookManager.aidl文件
package com.example.learnretrofit.AIDLTest;
import com.example.learnretrofit.AIDLTest.Book;
interface IBookManager{
List<Book> getBookList();
void addBook(in Book book);
}
- 这里我们在这个aidl问价下自定义了一个接口,分别声明了两个方法
- 创建好之后,点击build->make project,然后系统会自动为我们生成IBookManager.java文件,他的所在目录如图
- 点开这个为我们自动生成的文件,因为他本来的格式实在太不堪入目,我调整了一下,代码如下
package com.example.learnretrofit.AIDLTest;
public interface IBookManager extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.learnretrofit.AIDLTest.IBookManager
{
private static final java.lang.String DESCRIPTOR = "com.example.learnretrofit.AIDLTest.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.learnretrofit.AIDLTest.IBookManager interface,
* generating a proxy if needed.
*/
public static com.example.learnretrofit.AIDLTest.IBookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.learnretrofit.AIDLTest.IBookManager))) {
return ((com.example.learnretrofit.AIDLTest.IBookManager)iin);
}
return new com.example.learnretrofit.AIDLTest.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.example.learnretrofit.AIDLTest.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(DESCRIPTOR);
com.example.learnretrofit.AIDLTest.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.example.learnretrofit.AIDLTest.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.example.learnretrofit.AIDLTest.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.example.learnretrofit.AIDLTest.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.example.learnretrofit.AIDLTest.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.learnretrofit.AIDLTest.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addBook(com.example.learnretrofit.AIDLTest.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.example.learnretrofit.AIDLTest.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.example.learnretrofit.AIDLTest.Book book) throws android.os.RemoteException;
}
- 大概分析一下这个java文件,它是一个继承自android.os.IInterface接口的接口,这里要说的是所有可以在BInder中传输的接口都必须继承这个接口
- 声明了我们本来在IBookManager接口中声明的getBookList方法和addBook方法,
- 还声明了一个内部抽象类stub,这个stub就是一个BInder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程,而当两者位于不同的进程的时候,方法调用就会走transact过程,这个逻辑由stub内部的代理类Proxy来完成
- 那么可以发现这个接口的核心实现就是它的内部类stub和stub的内部代理类Proxy,下面详细分析这两个类的每个方法的含义
自动生成的java文件内部类stub和stub的内部代理类Proxy详细分析
stub#DESCRIPTOR常量
- BInder的唯一标识,一般用当前BInder的类名来表示,比如本例中“com.example.learnretrofit.AIDLTest.IBookManager”
stub#asInterface方法
- 用于将服务端的BInder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是stub对象(这个对象其实就是BInder)本身,否则返回的就是系统封装后的Stub.Porxy对象
- 那么可以看到,内部类Proxy是一个在不同进程间传递的对象
stub#asBInder方法
- 返回当前本身(也就是Binder对象)
stub#onTransact
- 这个方法运行在服务端中的BInder线程池中,当客户端发起跨进程请求的时候,远程请求会通过系统底层封装后交给此方法来处理,该方法的原型是public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags),可以看到有四个参数,服务端通过code参数客户端请求的目标方法是什么,接着从data中取出目标方法所需的参数(如果目标方法有参数的话),然后执行目标方法,当目标方法执行完成后,就像reply中写入返回值(如果目标方法有返回值的话)。这就是这个方法的执行过程
- 注意,如果这个方法返回false的话,表示客户端的请求失败,因此我们可以利用该方法的返回值来做权限的验证,所以当我们的服务不想响应某个客户端的请求的时候,我们给这个方法返回false就可以了
stub.Proxy#getBookList
- 这个方法运行在客户端,当客户端远程调用这个方法时,他的内部实现是这样的:首先创建该方法所需要的输入性Parcel对象_data,输出型Parcel对象_reply,和返回值对象List;
- 然后把该方法的参数信息写入_data中(如果有参数的话),接着调用transact方法来发起RPC(远程过程调用)请求,同时将当前线程挂起,然后服务端的onTransact方法会被调用,知道RPC过程返回后,当前线程继续执行,并从_reply中读取RPC过程中的返回结果,最后将_reply中的数据返回
- 这个方法也是与之前的onTransact方法的过程相对应,一个服务端,一个客户端
stub.Proxy#addBook
- 这个方法运行在客户端,他的执行过程和getBookList是一样的,只不过这个方法没有返回值,所以他不需要从_reply中获取数据
补充
- 当客户端线程发起远程请求之后,会将当前线程挂起等待远程方法返回数据,所以这个远程请求不能运行在UI线程
- 由于服务端的Binder对象运行在Binder线程池中,所以Binder方法可以直接同步执行(如果是追求效率的话),但是,如果有多个客户端连接着同一个服务端的话,应使用异步执行,因为如果还是同步的话一个是数据会出错,再者还会导致客户端等待时间超长,影响用户体验
- 其实通过以上分析来看,以及我们对系统为我们生成的java文件分析,我们发现,其实他的格式是固定的,我们完全可以不依赖系统而自己写一个BInder出来
- 上面生成的java文件是因为我们提供的aidl文件而生成的,但是系统生成的这个文件实在是看的难受,我们完全可以自己书写一个Binder出来
- 按照上面java文件的格式,我们先来声明一个AIDL性质的接口,只需要继承自IInterface的接口即可,如下:
public interface IBookManager extends IInterface {
static final String DESCRIPTOR = "com.example.learnretrofit.AIDLTest.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;
}
- 可以看到,我们的第一个常量格式固定,另外的变量由我们声明了几个方法决定
- 接下来就是实现stub类和stub内部的代理类Proxy了,这里我们还是参考上面的系统为我们生成的java文件写
public class BookManagerImpl extends Binder implements IBookManager {
//运行在服务端
//构造器是固定格式
public BookManagerImpl(){
this.attachInterface(this,DESCRIPTOR);
}
@Override
protected 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参数,然后相应的会在客户端读取到我们在这里写进去的数据
reply.writeTypedList(result);
return true;
case TRANSACTION_addBook:
//第一句话是固定格式
data.enforceInterface(DESCRIPTOR);
Book args;
if( (0 != data.readInt()) ){
//0和1表示我们是否往data中写入了序列化的对象数据,比如说这里的对象就是Book类的对象
args = Book.CREATOR.createFromParcel(data);
}else {
args = null;
}
//拿到具体对象之后,再根据我们自己实现的方法逻辑对参数进行操作,比如我们这里拿到了args这个Book的实
// 例对象,我们为他执行运行在服务端的addBook方法
this.addBook(args);
reply.writeNoException();
return true;
}
return super.onTransact(code,data,reply,flags);
}
@Override
public List<Book> getBookList() throws RemoteException {
//自己实现
return null;
}
@Override
public void addBook(Book book) throws RemoteException {
//自己实现具体的操作逻辑
}
@Override
public IBinder asBinder() {
return this;
}
//内部代理类的实现
private static class Proxy implements IBookManager{
//运行在客户端
private IBinder mRemote;
public Proxy(IBinder remote) {
mRemote = remote;
}
public 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{
//这里执行的主要是将数据写进参数,并调用mRemote.transact方法来发送远程请求
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();
}
}
@Override
public IBinder asBinder() {
return mRemote;
}
}
}
- 方法确实有点长,不过我已经在注释中基本上解释清楚了每个方法的过程的含义,都是比较容易理解的
- 这里我们完全是自己手写的一个Binder对象,可以说他与系统为我们生成的代码功能基本上是一摸一样的(除了一个方法我没写之外,这个方法后面会讲到)
- 既然与系统生成的代码功能和代码都基本一直,那么我们书写这个的意义何在呢,亲自手写这个过程对我们理解Binder的原理很有帮助,同时也是一种不通过AIDL方式实现Binder的新方式
- 如果是我们手写的Binder,我们只需要在服务端创建一个BookManagerImpl对象并在service的onBinder方法中返回即可,AIDL文件的本质只是系统为我们提供了一种实现Binder的工具,仅此而已。
- 接下来我们说一下Binder的很重要的两个方法linkToDeath和unLinkToDeath
- 我们知道,Binder运行在服务端进程,如果服务端进程由于某种原因异常终止,这个时候我们到服务端的Binder连接断裂(称之为Binder死亡),那么客户端的功能就会受到影响,为了解决这个问题,Binder中提供了两个配对的方法linkToDeath和unLinkToDeath,通过linkToDeath,我们可以给Binder设置一个死亡代理,当Binder死亡时,我们就会收到通知,这个时候我们就可以重新发送连接请求从而恢复连接。那么到底如何给Binder设置死亡代理呢?
给Binder设置死亡代理
- 首先声明一个DeathRecipient对象,DeathRecipient是一个接口,其内部只有一个方法binderDied,我们需要实现这个方法,当Binder死亡的时候,系统就会回调binderDied方法,然后我们就可以移出之前绑定的binder代理,并重新绑定远程服务
- 重新绑定代码如下
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if(mIBookManager == null) return;
mIBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mIBookManager = null;
//这里重新绑定Service
bindService(new Intent(LearnMoreProcessActivity.this,process1Service.class),
mConnection, Context.BIND_AUTO_CREATE);
}
};
- 不过这样还不够,我们还必须将这个死亡回调在绑定成功的时候给我们的Binder设置上,那么在什么时候绑定成功呢 ,当然是ServiceConnection的onServiceConnection方法执行之后,所以
ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected: 准备返回操作的Binder");
mIBookManager = BookManagerImpl.asInterface(service);
try {
//设置死亡回调
service.linkToDeath(mDeathRecipient,0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mIBookManager = null;
}
};
- 那么到这里我们就该说说我们在自己写的Binder中忘记书写的那个方法了,不知道大家看到这里有没有想过,emmmmm,数据在Binder中传输的过程有了,那么数据什么时候传输呢?或者说数据是怎么进到这个Binder中让他为我们传输的呢
- 不要着急,接下来我将用一个实实在在的例子说清楚这里面的调用关系
实例说明进程间通信的原理
- 首先我们想要进程间通信,必须有两个进程对吧,还得有个服务端,所以我新建一个服务,并在AndroidManifest.xml文件中注册的时候将他的进程设置为与包名不同的名字,这样服务就与activity不在一个进程了
- 这里如果对服务不太了解的话可以先到这里看一下服务的 基本用法
- 先看一下注册文件中的服务进程设置
<service
android:process="com.example.LearnMoreProcess.progress1"
android:name="com.example.learnretrofit.LearnMoreProcess.process1Service">
</service>
- 然后Service的代码
public class process1Service extends Service {
private static final String TAG = "process1Service";
List<Book> mBooks = new ArrayList<>();
BookManagerImpl mBookManager = new BookManagerImpl() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBooks;
}
@Override
public void addBook(Book book) throws RemoteException {
mBooks.add(book);
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind: 返回给操作Service的Binder");
return mBookManager;
}
}
- 这里我将我们那会写的BookManagerImpl弄成抽象类,这样我们就可以在服务里面去实现两个未实现的方法,因为我们要操作的数据是存在于服务里面的,合情合理
- 这里的mBookManager变量其实就是我们平时写服务内部的Binder对象,只不过这里用我们自己写的Binder代替
- 接下来看一下activity中的代码
- ServiceConnection
ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected: 准备返回操作的Binder");
mIBookManager = BookManagerImpl.asInterface(service);
try {
service.linkToDeath(mDeathRecipient,0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mIBookManager = null;
}
};
- 绑定
if(bindService(new Intent(this,process1Service.class),mConnection, Context.BIND_AUTO_CREATE))
Log.d(TAG, "onCreate: 绑定成功");
- 不知道大家有没有注意到,我在ServiceConnection 的onServiceConnected里面写了这句代码
mIBookManager = BookManagerImpl.asInterface(service);
- 想了一下,这里还是来张图吧
- 三个方法的调用顺序如图,在我们一般使用Binder时候,他在内部将BInder给我们都包装好了,所以在onBind方法中返回的直接是一个可以跨进程通信的Binder,但是我们在这里因为是手写的,虽然通信该有的过程已经完备,可是我们并没有在需要Binder的时候合理的包装Binder
- 也就是说,看我们这里Service的onBind方法,他只是直截了当的将Binder对象返回,并没有做太多的事情,而我们的跨进程通信是需要BookManagerImpl内部的代理类Proxy来操作的,但是这里并没有去给我们拿到这个内部对象,那怎么办呢
- 所以我在BookManagerImpl内部写了这么一个方法
public static IBookManager asInterface(IBinder obj){
if(obj == null){
return null;
}
IInterface lin = obj.queryLocalInterface(DESCRIPTOR);
if((lin != null) && lin instanceof IBookManager){
Log.d(TAG, "asInterface: 准备处理binder对象,返回给同进程操作句柄");
return (IBookManager) lin;
}
Log.d(TAG, "asInterface: 准备处理binder对象,返回给不同进程操作句柄");
return new BookManagerImpl.Proxy(obj);
}
- 这个方法是在不同的时机(多进程或者单进程)返回给我们不同的Binder(跨进程操作的代理类或者本进程的操作类)
- 那么为什么我会在onServiceConnection方法中调用呢,因为我们在前面说过,Proxy是运行在客户端进程的,而BookManagerImpl本身是运行在服务端进程的,所以我们需要在activity进程中(也就是客户端进程)来获取这个Proxy(代理)类,(如果是跨进程操作的话),这样就会通过Proxy将我们的数据跨进程操作了
- 这里我再来一张图说说这里的数据传输流程吧
- 现在应该对这个过程比较清楚了吧
- 其实,按理说到这里Binder的上层原理部分我们已经分析清楚了,方法调用顺序啊之类的,不过我在看到这里的时候有个疑问?
- binder到底是怎么让进程一的数据传到进程二中去的?
- 在我看过很多博客之后,大概弄懂了一点点,这里就简单说说我的理解
Binder到底是怎么把数据在进程间传输的
- 总结一下,系统除了为我们的每个进程分配空间之外,他还有个自己的内核空间
- 一般我们如果直接用进程中的方法去访问数据的话,他是会在本进程中创建一个副本的,所以基于这个机制,我们一般情况下并不能做到数据共享
- 这个时候,有个操作就出现了,我们先将自己想要共享的数据在内核空间申请一片内存,将我们所需传输的数据复制到该内存,然后再通知另个进程去访问该内存,所以通过两次数据复制就完成了跨进程的数据传输(也可以叫做数据共享)
- 而Binder却是只做了一次数据复制,这里只做的一次数据复制我直接拿张别人的图吧
- 这里好像与我们说的不一样,内核空间不是应该独自存在的吗,这里就是Binder的做法了,他将进程的空间分出了一块当做内核空间,不过这里可不是真正的内核空间,这里只不过是内核空间binder虚拟内存,是真正内核空间的内存映射,这个据查,是Binder的一个map函数做的事情
- 所以这里Binder才能直接将数据复制映射到另外一个进程的内核空间,所以这里实现了一次数据复制的高效性
总结
- 这里我再过一遍这个过程吧
- 这里分成进程A(Service进程)和进程B(Activity进程)
- 当我们在Activity中处理Service进程中传过来的Binder引用的时候,还记得我们调用的那个asInterface方法嘛?我们说过,如果是单进程,就直接返回BookManagerImpl对象(也就是onServiceConnection方法中传过来的IBinder参数不做修改)
- 如果是多进程,就返回BookManagerImpl.Proxy对象(也就是IBinder参数的内部Proxy对象)
- 为什么这么做呢,因为返回的IBinder参数是在Service进程中实例化的,如果是单进程,那么就直接拿他来做就行
- 如果是多进程的话,当我们拿到另外一个进程的对象引用的时候,我们调用这个对象方法的时候传递对象肯定是出错的,因为两个进程之间的存储就不是一个数据,但是如果返回由这个对象 包装的内部Proxy对象的话,因为这个Proxy对象是在Activity进程中实例化的,而这个Proxy内部做的跨进程数据传输就是我们刚才分析的原理,所以才不会出错
- 好了 ,本篇就到这里,接下来会通过具体东西来讲讲进程间通信