IPC就是跨进程通信。什么是进程呢?它一般指的是一个程序或应用。一个进程可以有多个线程,所以进程和线程是包括与被包括的关系。在Android里主线程就是UI线程,如果有很多任务都放在主线程里,就容易造成ANR,解决办法就是多开线程。
ANR: Activity 5s Service 前台20s 后台200s
Android中的多进程模式
开启多线程
在Android中使用多线程只有一种方法
在Mainfest里面给四大组件(Activity、Service、Receiver、ContentProvider)中给它指定android:process
//默认进程名字就是包名,例如com.example.xiaoming,要开多进程,指定一个process就好
android:process=":remote"
android:process="com.example.xiaoming:remote"
这样我们就开启了另一个线程,这两个线程名字一样,都是com.example.xiaoming:remote,但是第一个与第二个是不一样的,直接以:为开头的进程是当前应用的私有进程,而不以:为开头的则是共享进程。其它应用可以通过ShareUID与它跑在同一进程中。
ShareUID可以让两个应用共享对方的私有数据,如data目录、组件信息等。如果它们还跑在同一进程中,那么他们还可以共享内存。其实这样的他们看起来就是一个应用的两部分了。
多进程问题
如果有一个对象
public class User{
public static String name = "XiaoMing";
}
Activity A运行在A1进程里,Activity B运行在B1进程里。A把name改成了”XiaoHong”,然后B再去访问name,这时候B获取的名字是”XiaoMing”。原因就是Android给每一个进程都配备了一个独立的虚拟机,这就导致不同进程访问同一个类会拿到不同副本。所以A即使改变了name,也无法影响到B。 一般来说,Android多线程使用不当会造成以下问题
- 静态成员和单例模式失效
- 线程同步机制失效
- SharedPreference可靠性下降
- Application会多次创建
第一个就不解释了,第二个也跟第一个差不多。第三个则是因为SharedPreference不支持并发写,否则有机率丢失数据。第四个则是因为一个进程会对应一个Application,所以多开进程自然会多创建Application。
IPC基础概念介绍
这一节会介绍Serializable接口、Parcelable接口和Binder。Serializable接口、Parcelable接口内容都是序列化的内容,它们是为Binder的内容做铺垫的。
Serializable接口
Serializable是Java提供的一个接口,使用Serializable来实现序列化很简单,只要在类的声明里加入一个UID标识就可以实现默认的序列化过程。
private static final long serialVersionUID = 一串数字;
一个类要实现Serializable接口,就比如下面的User,在指定一个serialVersionUID即可。
public class User implements Serializable{
private static final long serialVersionUID = 437437473233L;
public int userId;
public String userName;
}
实现对象的序列化过程和非序列化过程只需要采用ObjectOutputStream和ObjectInputStream就可以轻松实现。
//序列化过程
User user = new User(0, "John");
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();
//反序列化过程
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("cache.txt"));
User newUser = (User)in.readObject();
in.close();
其实User类也可以不写serialVersionUID,直接继承Serializable的,但是这样很有可能会出现问题。原则上序列化后的数据中的serialVersionUID和当前类的serialVersionUID相同才会成功反序列化。这个工作机制是这样的
- 序列化的时候系统会把当前类的serialVersionUID写入序列化的文件
- 反序列的时候会去检测文件中的serialVersionUID,看是否与当前类的serialVersionUID一致,如果一致说明当前类和序列化的类相比没有什么不同,这个时候就可以反序列化。否则说明当前类和序列化的类相比有了某些变化,例如增减某些变量,这个时候就无法正常反序列化。
一般来说我们应手动指定serialVersionUID,如果不指定的话,系统会自动根据当前类的hash值去生成serialVersionUID,一旦类有改变,例如增减某些变量,hash值会改变,当前类的serialVersionUID就改变了,序列化数据中的serialVersionUID就与它不一致,反序列化就失败了。如果我们有手动指定serialVersionUID的话,即使类发生改变,还能最大程度恢复数据。当然,如果这个类结构发生了毁灭性改变,如类名改变,成员变量的类型改变,反序列化依旧会失败。
需要注意的有两点:
- 静态成员变量属于类而不属于对象,所以不会参与序列化过程
- 用transient关键字标记的成员变量不参与序列化过程
Parcelable接口
一个类只要实现了Parcelable接口,它就可以通过Intent和Binder传递,下面是一个典型的实现
public class User implements Parcelable{
public int userId;
public String userName;
public Book book;
public User(int userId, String userName, Book book){
this.userId = userId;
this.userName = userName;
this.book = book;
}
public int describeContents(){
return 0;
}
public void writeToParcel(Parcel out, int flags){
out.writeInt(userId);
out.writeString(userName);
}
public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>(){
public User createFromParcel(Parcel in){
return new User(in);
}
public User[] newArray(int size){
return new User[size];
}
};
private User(Parcel in){
userId = in.readInt();
userName = in.readString();
book = in.readParacelable(Thread.currentThread().getContextClassLoader());
}
}
可以看到,序列化功能是由writeToParcel方法来完成的,反序列化则是由CREATOR完成的。需要注意的是,在User(Parcel in)中,由于book是另一个可序列化对象,所以它的反序列化需要传递当前线程的上下文类加载器。
Serializable接口和Parcelable接口的优缺点
- Serializable是Java提供的一个接口,其使用简单但是开销很大,序列化和反序列化需要大量的I/O操作。
- Parcelable是Android的序列化方式,虽然使用起来有点麻烦,但是效率很高,因此我们要首选Parcelable。
- Parcelable主要用在内存序列上,通过Parcelable序列化到存储设备和网络传输都是可以的,但这个过程有点复杂,所以在序列化到存储设备和网络传输上建议使用Serializable。
Binder
直观来说,Binder是Android中的一个类,它实现了IBinder接口。从IPC角度来说,Binder是Android一种跨进程通信方式。从Android应用层来说,Binder是客户端和服务端的他那个新媒介,当bindService的时候,服务端就会返回一个可以调用服务端业务的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或数据。
Android开发中,Binder主要用在特殊的Service中,比如AIDL和Message,普通的Service是不涉及进程调用的,所以无法触及Binder的核心。而Message的底层其实是AIDL,所以这里使用AIDL来阐述Binder的运行机制。 我们可以新建AIDL实例
//Book.java
package com.example.lengary_l.aidltest;
public class Book implements Parcelable{
private int bookId;
private String bookName;
public Book(int bookId, String bookName){
this.bookId = bookId;
this.bookName = bookName;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeInt(bookId);
parcel.writeString(bookName);
}
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];
}
};
protected Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
}
// Book.aidl
package com.example.lengary_l.aidltest;
// Declare any non-default types here with import statements
parcelable Book;
// IBookManager.aidl
package com.example.lengary_l.aidltest;
import com.example.lengary_l.aidltest.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
在上面的三个文件中,Book.java是图书类,Book.aidl是Book类在AIDL类中的声明,IBookManager是我们定义的接口,getBookList是从远程客户端获得图书列表,addBook是向远程客户端添加一本书。我们看下系统生成的Binder类。
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.lengary_l.aidltest.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.example.lengary_l.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.lengary_l.aidltest.IBookManager interface,
* generating a proxy if needed.
*/
public static com.example.lengary_l.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.lengary_l.aidltest.IBookManager))) {
return ((com.example.lengary_l.aidltest.IBookManager) iin);
}
return new com.example.lengary_l.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 {
//通过code判断是要执行哪一个方法
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.example.lengary_l.aidltest.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.example.lengary_l.aidltest.Book _arg0;
//读取执行方法所需要的参数,例如书籍
if ((0 != data.readInt())) {
_arg0 = com.example.lengary_l.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.lengary_l.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.lengary_l.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.lengary_l.aidltest.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.lengary_l.aidltest.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.example.lengary_l.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.lengary_l.aidltest.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.example.lengary_l.aidltest.Book book) throws android.os.RemoteException;
}
我们来介绍一下上一段代码里面的一些变量:
- DESCRIPTOR:Binder的唯一标识,你可以就把它认为是Binder的身份证
- onTransact:这个方法运行在服务端的Binder线程池中。当客户端发起跨进程请求的时候, onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)。服务端通过code判断是哪个方法被执行,接着从data中取出目标方法的参数,然后执行目标方法。执行完毕后就向_reply写入返回值。
- Proxy#addBook:这个方法运行在客户端,当客户端调用这个方法的时候(意味着客户端服务端不在同一进程),它会调用transact方法,与此同时onTransact方法被调用,它会传入_data和_reply给onTransact,onTransact处理_data,然后写入_reply,这时候Proxy就获得_reply了,它会返回_reply中的数据
- 内部类Stub:它就是一个Binder类,当客户端和服务端都位于同一个进程的时候,方法调用不会走跨进程的transact过程,当不同进程的时候,就会走transact,这个逻辑是由代理类Proxy来执行的。
远程服务端实现,这个Service是另外一个进程的
public class BookManagerService extends Service {
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
};
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1, "Android"));
mBookList.add(new Book(2, "IOS"));
}
public BookManagerService() {
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
在代码里,我们创建了一个Binder对象继承自IBookManager.Stub,并实现了它内部的AIDL方法。需要注意到AIDL方法是在服务端的Binder线程池中执行的,因此当有多个客户端同时连接的时候要处理线程同步。
客户端实现,我们需要先绑定好服务端,将服务端返回的Binder对象转化成AIDL接口(IBookManager.aidl),然后就可以通过这个接口访问服务端的方法了。
public class BookManagerActivity extends AppCompatActivity {
private Button register;
private Button sendBook;
private Button getBookList;
private IBookManager bookManager;
private static final String TAG = "BookManagerActivity";
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
bookManager = IBookManager.Stub.asInterface(iBinder);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
register = findViewById(R.id.register);
sendBook = findViewById(R.id.send_book);
getBookList = findViewById(R.id.get_book_list);
register.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(BookManagerActivity.this, BookManagerService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
});
getBookList.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
List<Book> bookList = bookManager.getBookList();
for (int i = 0; i < bookList.size(); i++) {
Log.i(TAG, "book list is: " + bookList.get(i).getBookName());
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
}
需要注意的是,调用bookManager的方法可能会导致ANR,因为服务端的方法可能需要很久要执行完毕,所以在日常开发中要注意这个情况。
从上面我们可以看出大致的流程,Service实现IBookManager的内部虚拟类IBookManager.Stub(),也就是实现addBook(),getBookList(),并把它作为binder返回给客户端,客户端拿到binder后,直接IBookManager.Stub.asInterface(binder)拿到代理Proxy,就通过代理去执行服务端的方法了。(代理模式的作用就是拦截请求,让其在binder线程池中执行)。