1 IPC机制基础
1.1 Android IPC简介
(1)IPC是Inter-Process Communication的缩写,含义是进程间通信或者跨进程通信,是指两个进程间进行数据交互的一个过程。
(2)线程是CPU调度的最小单元,同时线程是一种有限的系统资源。而进程一般指一个执行单元,在PC和移动设备上指一个程序或者一个应用。一个进程可以包含多个线程,因此进程和线程是包含与被包含的关系。
1.2 Android 中的多进程模式
Android中的多进程模式,通过给四大组件指定android:process属性,可以轻易的开启多进程模式。
1.2.1 开启多进程模式
(1)正常情况下,在Android中多进程是指一个应用中存在多个进程的情况,因此这里不讨论两个应用之间的情况,首先在Android中使用多进程只有一种方法,那就是给四大组件指定android:process。还有一种非常规的多进程方法,那就是通过JNI在native层去fork一个新的进程。
(2)示例如下:
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize"
android:launchMode="standard">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.seniorlibs.binder.file.SecondActivity"
android:process=":remote" />
<activity
android:name="com.seniorlibs.binder.file.ThirdActivity"
android:process="com.seniorlibs.binderclient.remote" />
(3)“:remote”和包名“.remote”的区别?
①":remote"属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,常用。
②".remote"属于全局进程,其他应用通过ShareUID方式可以和它跑在同一进程中。
1.2.2 多进程模式的运行机制
使用多进程会造成如下几方面的问题:
1、静态成员和单例模式完全失效
2、线程同步机制完全失效。
3、SharedPreferences的可靠性下降。
4、Application会多次创建。
(1)第1问题原因:Android为每一个应用都分配了一个独立的虚拟机,或者说为每个进程都分配一个独立的虚拟机,不同的虚拟机在内存上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。举例,在两个进程中都存在一个UserManager类,并且这两个类是互不干扰的,在一个进程中修改mUseld的值只会影响当前进程,对其他进程不会造成任何影响。所有运行在不同进程中的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败这也是多进程带来的主要影响。
(2)第2问题原因:本质上和第一个问题是类似的,既然都不是一块内存了,那么不管是锁对象还是锁全局类都无法保证线程同步,因为不同进程锁都不是同一个对象。
(3)第3问题原因:SharedPreferences底层是通过读/写XML文件来实现的,并发写显然是可能出问题的,甚至并发读/写都有可能出问题;所以不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失。
(4)第4问题原因:由于系统要在创建新的进程同时分配独立的虚拟机,所以这个过程其实就是启动一个应用的过程。因此,相当于系统又把这个应用重启动了一遍,既然重新启动了,那么自然会创建新的Application。
1.3 IPC基础概念
1.3.1 Serializable
(1)Serializable是java所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化的操作。
(2)想让一个对象实现序列化,只需要这个类实现Serializable接口并且声明一个serialversionUID即可:
private static final long serialVersionUID = 8711368828010083044L;
(3)serialversionUID的工作机制?
序列化的时候会把当前类的serialversionUID写进序列化的文件中(也可能是其他中介),当反序列化的时候系统会去检测文件中的serialversionUID,看它是否和当前类的serialversionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变换,比如成员,类型可能发生了变化,这个时候是无法正常的反序列化的。
(3)为什么建议手动指定serialversionUID的值?
如果不手动指定serialversionUID的值,反序列化时当前类有所改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的hasj值并把它赋值给serialversionUID,这个时候当前类的serialversionUID就和序列化数据中的serialversionUID不一致,于是反序列化失败,程序就会出现crash。
1.3.2 Parcelable
(1)在序列化过程中需要实现的功能有序列化、反序列化和内容描述
public class User implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
public Book book;
public User(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
// 内容描述功能,几乎在所有情况下这个方法都应该返回0,仅当当前对象中存在文件描述符时,此方法返回1
@Override
public int describeContents() {
return 0;
}
// 序列化功能,最终是通过Parcel中的一系列write方法来完成的
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
dest.writeString(userName);
dest.writeByte((byte) (isMale ? 1 : 0));
}
// 反序列化功能,其内部标明了如何创建序列化对象和数组,并通过Parcel的一系列read方法来完成反序列化过程
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
protected User(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readByte() != 0;
}
}
(2)Parcelatle和Serializable之间该如何选取?
①Parcelatle和Serializable都能实现序列化并且都可用于Inient间的数据传递;
②Serializable是Java中的序列化接口,其使用起来简单但是开销很大,序列化和反序列化过程需要大量I/O操作。
③Parcelable是Andrord推荐的序列化方式,更适合用在Android平台上,它的缺点就是使用起来稍微麻烦点,但是它的效率很高,因此我们要首选Parcelatle。
④Parcelable主要用在内存序列化上;通过Parcelable将对象序列化到存储设备中,或者将对象序列化后通过网络传输这个过程会稍显复杂,因此建议使用Serializabie。
1.3.3 Binder
(1)概念
①直观来说,Binder是Android中的一个类,它实现了IBinder接口;
②Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有;
③从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,等等)和相应Managerservice的桥梁:
④从IPC角度来说,Binder是Android中的一种跨进程通信方式;
⑤从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务(包括普通服务和基于AIDL的服务)或者数据。
(2)进程间通信的整体架构
1.4 Binder工作机制分析
1.4.1 通过AIDL来分析Binder工作机制
(1)创建一个工程,在工程中点击右键New->AIDL->AIDL File,默认直接点确定,这时会在工程中出现一个aidl文件。
(2)由于AS是要手动编译才能生成对应AIDL的java文件,既然aidl文件是个接口,那就必须存在着实现这个接口的类,点击编译,系统自动生成一个java类,该java类的代码就是整个Binder机制的原理所在
(3)写服务端的代码,就要开始写服务了,创建一个类,继承Service
/**
* 服务端---服务类
*/
public class MyService extends Service {
@Override
public IBinder onBind(Intent intent) {
return iBinder;
}
private IBinder iBinder = new IMyAidlInterface.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public int add(int num1, int num2) throws RemoteException {
Log.i("TAG", "从客户端发来的AIDL请求:num1->" + num1 + "::num2->" + num2);
return num1 + num2;
}
};
}
(4)必须在manifests文件中配置
<service
android:name=".MyService"
android:process=":remote" />
(5)写客户端的代码,放一个”AIDL“的按钮,先绑定服务,然后点击按钮调用
public class MainActivity extends AppCompatActivity {
private IMyAidlInterface iMyAidlInterface;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 绑定服务
Intent intent = new Intent(this, MyService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
/**
* 点击“AIDL”按钮事件
*/
public void add(View view) {
try {
int res = iMyAidlInterface.add(1, 2);
Log.i("TAG", "从服务端调用成功的结果:" + res);
} catch (Exception e) {
e.printStackTrace();
Log.i("TAG", "服务端未开启");
}
}
/**
* 服务回调方法
*/
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
iMyAidlInterface = null;
}
};
@Override
protected void onDestroy() {
super.onDestroy();
//解绑服务,回收资源
unbindService(mConnection);
}
}
(6)测试结果(先开启服务端,开启服务后,接着开启客户端,绑定远程服务)
02-22 17:21:33.651 19492-19552/demo.binder I/TAG: 从客户端发来的AIDL请求:num1->1::num2->2
02-22 17:21:33.651 22610-22610/demo.binderb I/TAG: 从服务端调用成功的结果:3
1.4.2 Binder的工作机制原理分析-AIDL类源码
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: D:\\project\\project_my\\BinderB\\app\\src\\main\\aidl\\demo\\binder\\IMyAidlInterface.aidl
*/
package demo.binder;
// Declare any non-default types here with import statements
// 接口
public interface IMyAidlInterface extends android.os.IInterface {
/**
* 服务端.
*/
public static abstract class Stub extends android.os.Binder implements demo.binder.IMyAidlInterface {
// 当前Binder类的全名
private static final java.lang.String DESCRIPTOR = "demo.binder.IMyAidlInterface";
// Construct the stub at attach it to the interface.
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* 将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,obj是当前服务端的Binder对象
*/
public static demo.binder.IMyAidlInterface asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
// 如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身,下面方法转换
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof demo.binder.IMyAidlInterface))) {
return ((demo.binder.IMyAidlInterface) iin);
}
// 如果客户端和服务端位不位于同一进程,返回的是系统封装后的Stub.Proxy(obj)
return new demo.binder.IMyAidlInterface.Stub.Proxy(obj);
}
/**
* 当前服务端的Binder对象
*/
@Override
public android.os.IBinder asBinder() {
return this;
}
/**
* 运行在服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理
*/
@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_basicTypes: {
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
long _arg1;
_arg1 = data.readLong();