一、Android IPC简介
IPC : Inter-Process-Communication 进程间通信或者跨进程通信。
要想理解进程间通信,那么就需要知道什么是进程,什么是线程?
线程:线程是CPU调度的最小单元,同时也是一种有限的系统资源
进程:一般指一个执行单元,在PC和移动设备上指一个程序或者一个应用。一个进程可以包含多个线程。
既然是跨进程通信,那么就会涉及到多进程的问题,多进程的情况分为两种:
- 一个应用因为某种原因自身需要采用多进程模式来实现,至于原因有很多种,例如,某些模块因为特殊的原因需要运行在单独的进程中,又或者加大一个应用所需要的内存。
- 另一种原因是当前应用需要向其他应用获取数据。
二、Android中的多进程模式
1、开启多进程模式
在Android中,多进程指的是一个应用中存在多个进程的情况。
在Android中使用多进程的方法有两种:
- 那就是给四大组件在AndroidMenifest中指定process属性
另一种非常规的多进程方法,那就是通过JNI在native层fork一个新的多进程,这种方法属于特殊情况,这里不做讨论。
<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=".SecondActivity" android:process=":remote"> </activity> <activity android:name=".ThirdActivity" android:process="cn.demo.zx_ipc_learn.remote"> </activity>
如果没有指定进程,那么它的进程的名称就是包名。
指定:remote表示进程的名称为 包名:remote,并且这种属性的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以“:”开头的进程属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。
我们知道Android系统会为每一个应用分配一个唯一的UID,具有相同UID的应用才能共享数据。这里需要说明的是,两个应用需要通过ShareUID跑在同一个进程中是有要求的,这两个应用具有相同的ShareUID并且签名相同才可以。在这种情况下,它们可以相互访问对方的私有数据,比如data目录、组件信息等,不管它们是否跑在同一个进程中。如果跑在同一个进程中,还可以共享内存数据。
2、多进程模式的运行机制
所有运行在不同进程中的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败,这是多进程所带来的主要影响。
原因:Android为每一个应用分配了一个独立的虚拟机,或者说为每个进程都分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生很多副本。
一般来说,使用多进程会造成如下几方面的问题:
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
- SharePreferences的可靠性下降
- Application会多次创建
三、IPC基础概念介绍
想要很好的理解PIC,那么就需要知道一些IPC的基本概念。Serializable和Parcelable接口以及Binder。
1、Serializable接口
Serializable是java提供的序列化空接口。
如何进行对象的序列号和反序列化呢?
例如:
/**
* 即使不设置serialVersion也能序列化,因为系统会随机提供一个serialVersionUID,但是却不能反序化。
* 因为SerialVersionUID的工作机制是这样的:序列化的时候系统会把当前类的serialVersionUID
* 写入序列化的文件中(也可能是其他中介),当反序列化的时候,系统会去检测文件中的serialVersionUID,
* 看它是否和当前类的serialVersionUID一致,如果一致就是说明序列化的类的版本和当前类的版本
* 是相同的,这个时候可以成功序列化。因此想要反序列化,那么就得手动设置serialVersionUID的值。
* 当我们手动指定了它之后,我们很大程度上避免反序列化失败,例如删除或者增加了新的成员变量的时候,反序列化
* 也能成功。但是当类的结构发生了根本性变化之后,即使serialVersionUID验证通过,反序列化也会失败,
* 例如,修改了类名,修改了成员变量的类型等等。
*/
public class User implements Serializable {
private static final long serialVersionUID = 1L;
public int userId;
public String userName;
public boolean isMale;
....
}
/**
* 序列号
* @param view
*/
public void serialize(View view){
new Thread(){
@Override
public void run() {
super.run();
User user = new User(0,"zhangx",false);
File file = new File((Utils.TOTAL_PATH));
if(!file.exists()){
file.mkdirs();
}
File cacheFile = new File(Utils.PATH);
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(new FileOutputStream(cacheFile));
out.writeObject(user);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
}
/**
* 反序列化
* @param view
*/
public void unSerialize(View view){
final File file = new File(Utils.PATH);
new Thread(){
@Override
public void run() {
super.run();
User user = null;
if(file.exists()){
ObjectInputStream in = null;
try {
in = new ObjectInputStream(new FileInputStream(file));
user = (User) in.readObject();
System.out.println(user.toString());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}.start();
}
注意:
- 静态成员变量属于类不属于对象,所以不会参与序列号过程
- 用transient关键字标识的成员变量不参与序列号过程。
2、Parcelable接口
public class Student implements Parcelable {
public String name;
public boolean isMale;
/**
* Book类也是一个继承了Parcelable的类
*/
public Book book;
public Student(String name, boolean isMale, Book book) {
this.name = name;
this.isMale = isMale;
this.book = book;
}
/**
* Parcel内部包装了可序列化的数据,可以在Binder中自由传输
* @param in
*/
protected Student(Parcel in) {
name = in.readString();
isMale = in.readByte() != 0;
book = in.readParcelable(Book.class.getClassLoader());
}
/**
* 反序列化
*/
public static final Creator<Student> CREATOR = new Creator<Student>() {
/**
* 创建序列化对象
* @param in
* @return
*/
@Override
public Student createFromParcel(Parcel in) {
return new Student(in);
}
/**
* 创建序列号数据
* @param size
* @return
*/
@Override
public Student[] newArray(int size) {
return new Student[size];
}
};
/**
* 内容描述
* 当当前对象中存在文件描述符时,此方法返回1
* @return
*/
@Override
public int describeContents() {
return 0;
}
/**
* 序列化
* @param dest
* @param flags
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeByte((byte) (isMale ? 1 : 0));
dest.writeParcelable(book, flags);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", isMale=" + isMale +
", book=" + book.toString() +
'}';
}
}
3、Binder
从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowMnager、等等)和相应ManagerService的桥梁;
Binder的工作机制:
从Android应用层来说,Binder是客户端和服务端进行通信的媒介。当bindService的时候,服务端会返回一个Binder对象,通过这个对象就能,客户端就能获取服务端的数据与服务,这里的服务包括普通的服务和基于AIDL的服务。
在Android中,创建一个AIDL文件的话,Android系统会生成Binder的java类。
Binder的工作原理:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: D:\\AndroidStudioDemo\\zx_telephone_monitor\\zx_remote_service\\src\\main\\aidl\\cn\\demo\\zx_remote_service\\IService.aidl
*/
package cn.demo.zx_remote_service;
// Declare any non-default types here with import statements
public interface IService extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements cn.demo.zx_remote_service.IService
{
/**
* Binder唯一标识,一般用类名表示
*/
private static final java.lang.String DESCRIPTOR = "cn.demo.zx_remote_service.IService";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an cn.demo.zx_remote_service.IService interface,
* generating a proxy if needed.
* 将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,
* 如果客户端与服务端在同一进程中,则返回Stub本身,否则返回的是系统封装后的Stub.Proxy对象。
*/
public static cn.demo.zx_remote_service.IService asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof cn.demo.zx_remote_service.IService))) {
return ((cn.demo.zx_remote_service.IService)iin);
}
return new cn.demo.zx_remote_service.IService.Stub.Proxy(obj);
}
/**
* 返回当前Binder对象
*/
@Override
public android.os.IBinder asBinder()
{
return this;
}
/**
* 这个方法运行在服务端的Binder线程池中,
* 当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。
* code:服务端通过code可以确定客户端请求的目标方法是什么
* 接着从data中取出目标文件所需的参数,然后再执行此方法,
* 当目标方法执行完成之后,就向reply中写入返回值。
* 如果返回false表示客户端请求失败。
*/
@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_callMethod:
{
data.enforceInterface(DESCRIPTOR);
this.callMethod();
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements cn.demo.zx_remote_service.IService
{
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和输出对象_reply.
* 将该方法的参数信息写入_data中
* 接着调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起
* 然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果。
*/
@Override
public void callMethod() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_callMethod, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_callMethod = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public void callMethod() throws android.os.RemoteException;
}
这里需要注意:
- 当客户端发起远程请求的时候,由于当前线程会被挂起知道服务端进程返回数据,所以如果是一个远程方法是很耗时的,那么不能在UI线程中发起此远程请求。
- 由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程池中了。
AIDL文件的本质是系统为我们提供了一种快速实现Binder的工具。
还有一点需要注意,在发起的远程调用的时候,服务端因为异常原因终止了,但是客户端却不知道,怎样解决?
需要设置死亡代理:
IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
System.out.println("服务端终止。。。。。。。。。:::"+mIService);
mIService.asBinder().unlinkToDeath(mDeathRecipient,0);
mIService=null;
//TODO 重新绑定服务
}
};
ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mIService = IService.Stub.asInterface(service);
try {
/**
* 设置死亡代理
*/
service.linkToDeath(mDeathRecipient,0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
四、Android中的IPC方式
1、使用Bundle
简单易用,但是只能传输Bundle支持的数据类型,适用于四大组件间的进程间通信。
2、使用文件共享
简单易用,但是不适合高并发场景,并且无法做到进程间的即时通信。
3、使用Messenger
Messenger是一个轻量级的IPC方案,它的底层是AIDL.它的作用主要是传递消息。无法调用服务端方法。
public class MessengerService extends Service {
private class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case GlobalUtils.MSG_FROM_CLIENT:
/**
* 接收客户端发送过来的消息
*/
String msg1 = msg.getData().getString("msg");
System.out.println("获取的数据::::::"+msg1);
/**
* 向服务的发送消息
*/
Messenger client = msg.replyTo;
Message replyMessage = Message.obtain(null,GlobalUtils.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("reply","你的消息我已收到,稍后会回复你。");
replyMessage.setData(data);
try {
client.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:break;
}
}
}
public MessengerService() {
}
/**
* 创建与Handler相关联的Messenger对象。
*/
private Messenger mMessenger = new Messenger(new MessengerHandler());
/**
* 返回mMessenger对象中的Binder
* @param intent
* @return
*/
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
Activity::
public class MessengerActivity extends AppCompatActivity {
/**
* 创建与Handler相关联的Messenger对象,主要用于接收消息。
*/
private Messenger mG = new Messenger(new MessengerHandler());
private class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case GlobalUtils.MSG_FROM_CLIENT:
System.out.println("客户端收到的数据::"+msg.getData().getString("reply"));
break;
default:break;
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
Intent intent = new Intent(this,MessengerService.class);
bindService(intent,conn,BIND_AUTO_CREATE);
}
ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Messenger messenger = new Messenger(service);
Message msg = Message.obtain(null,GlobalUtils.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg","这里是客户端。。。。。。。。。。");
msg.setData(data);
//注意此处,
msg.replyTo = mG;
try {
messenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
}