(《Android开发艺术探索》读书笔记)
Android IPC基本概念:
IPC:Iner-Process Communication。意思为进程间通信,两个进程之间进行数据交换的过程。
IPC的使用场景:
一个应用因为某些原因自身需要采用多进程模式来实现(比如某个特殊模块需单独运行在进程中,亦或大应用需获取多份内存空间)
当前应用需要向其他应用获取数据(比如ContentProvider,只不过系统屏蔽了通信细节)
Android中的多进程模式:
1、如何简单的开启多进程模式
常规的方法是给四大组件在AndroidMenifest中指定android:process属性:
<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" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.android.ipc.Second"
android:configChanges="screenLayout"
android:process=":remote" />
<activity
android:name="com.android.ipc.Third"
android:configChanges="screenLayout"
android:process="com.android.ipc.remote" />
非常规方法通过JNI在native层去fork一个新的进程。
可以看出当前应用新增了两个进程。
以“:”开头的含义是在当前的进程名前面附加上当前的包名,并且是属于当前应用的私有进程。而另一个是完整的命名方式,不会附加包名信息,并且属于全局进程。,其他应用通过ShareUID方式可以和它跑在同一个进程中。
多进程模式的运行机制:
每个进程都分配一个独立的虚拟机、Application和内存空间,不同的虚拟机在内存分配上有不同的地址空间,这会导致在不同的虚拟机中访问同一个类的的对象会产生多份副本。
所有运行在不同进程中的四大组件,只要他们通过内存来共享数据,都会共享失败。
所以使用多进程会造成下面四个问题:
静态成员和单例模式完全失效;
线程同步机制完全失效;
(原因:不同进程锁不是同一个对象)
SharedPreferencce的可靠性下降;
(原因:SharedPreferencce不支持两个进程同时去执行写操作)
Application会多次创建。
(原因:系统创建新的进程会同时分配独立的虚拟机,这个过程为启动一个应用的过程)
IPC基础知识:
1、Serializable接口:
首先声明一个标识:
public class User implements Serializable {
public static final long serialVersionUID = 212345678909876543L;
public int id;
public String name;
public boolean isMan;
public User() {
super();
}
public User(int id, String name, boolean isMan) {
super();
this.id = id;
this.name = name;
this.isMan = isMan;
}
}
然后通过ObjectOutputStream和ObjectInputStream即可完成:
// 序列化过程
User user= new User(0, "Tom", true);
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// 反序列化过程
try {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
User newUser = (User) in.readObject();
in.close();
} catch (StreamCorruptedException e) {
e.printStackTrace();
} catch (OptionalDataException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
serialVersionUID的工作机制:序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中,当反序列化的时候系统会检测文件中serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类版本和当前类的版本是相同的,这时就可以成功反序列化。
特别需要注意的两点:一是静态成员属于类不属于对象,所以不会参与序列化过程;其次用transient关键字标记的成员变量不参与序列化过程。
Parcelable接口:
实现这个接口,一个类的对象就可以实现序列化并通过Intent和Binder传递。
典型用法:
public class User implements Parcelable {
public int id;
public String name;
public boolean isMan;
public Book book;
public User(int id, String name, boolean isMan) {
super();
this.id = id;
this.name = name;
this.isMan = isMan;
}
// 返回当前对象的内容描述。如果含有文件描述符,返回1,几乎所有情况返回0
@Override
public int describeContents() {
return 0;
}
// 将当前对象写入序列化结构中
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
dest.writeInt(isMan ? 1:0);
dest.writeParcelable(book, 0);
}
// 反序列化
public static final Parcelable.Creator<User> CREATOR = new Creator<User>() {
// 创建指定长度的原始对象数组
@Override
public User[] newArray(int size) {
return new User[size];
}
// 从序列化后的对象中创建原始对象
@Override
public User createFromParcel(Parcel source) {
return new User(source);
}
};
// 通过read方法完成反序列化过程
public User(Parcel source) {
id = source.readInt();
name = source.readString();
isMan = source.readInt() == 1;
// book是另一个可序列化对象,所以需要传递当前线程的上下文类加载器。
book = source.readParcelable(Thread.currentThread().getContextClassLoader());
}
}
Serializable和Parcelable使用场景:
Serializable:使用简单但是开销很大,需要大量的I/O操作。适用于将对象序列化到存储设备中或者将对象序列化后通过网络传输。
Parcelable:使用复杂但效率很高。适用于内存序列化上,这是Android推荐的序列化方式。
Binder
原理没看明白,这里只记录下怎么使用:
Binder是Android中一种跨进程通信方式(IPC),是ServiceManager连接各种Manager和相应ManagerService的桥梁(Framework),是客户端和服务端进行通信的媒介(应用层)。
Binder主要用在Service中,包括AIDL和Messenger。这里用AIDL的方式示例:
// Book.java
package com.android.ipc.aidl;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
public int id;
public String name;
public Book(int id, String name) {
super();
this.id = id;
this.name = name;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
}
public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book[] newArray(int size) {
return new Book[size];
}
@Override
public Book createFromParcel(Parcel source) {
return new Book(source);
}
};
private Book(Parcel source) {
id = source.readInt();
name = source.readString();
}
}
// Book.aidl
package com.android.ipc.aidl;
parcelable Book;
// IBookManager .aidl
package com.android.ipc.aidl;
import com.android.ipc.aidl.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
系统便会自动生成IBookManager.java。AIDL文件的本质是系统为我们提供了一种快速实现Binder的工具。
linkToDeath和unlinkToDeath:
Binder运行在服务端进程,如果服务端进程由于某种原因异常终止,导致我们的远程调用失败,通过linkToDeath设置一个死亡代理,当Binder死亡时,我们就会收到通知,这是就可以重新发起连接请求从而恢复连接。
private IBookManager mBookManager;
private IBinder.DeathRecipient mDeathRecipient = new DeathRecipient() {
// Binder死亡的时候调用这个方法
@Override
public void binderDied() {
if (mBookManager == null)
return;
mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mBookManager = null;
// TODO:这里重新绑定远程Service
}
};
然后,在客户端绑定远程服务成功后,给Binder设置死亡代理:
mService = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient, 0);
Android中的IPC方式
1、Bundle
在一个进程中启动另一个进程的Activity、Servie、Receiver,我们就可以在Bundle中附加需要传输给远程进程的消息并通过Intent发送出去。所传输的数据必须能够序列化。
2、使用文件共享
两个进程通过读写同一个文件来交换数据。适用于无并发访问情形,交换简单的数据,实时性不高的场景。
3、使用Messenger
在Message中放入需要传递的对象,便可实现数据的进程间传递。适用于低并发的一对多即时通信。
4、使用AIDL
处理大量的并发请求
5、使用ContentProvider
在数据源访问方面功能强大。适用于一对多的进程间的数据共享
6、使用Socket
网络数据交换