第二章:IPC机制

       IPC是进程间通信或者跨进程通信。本章涉及(1)Android中的多进程概念;(2)IPC基础概念介绍(3)Bundle、文件共享、AIDL、Messenger、ContentProvider和Socker等进程间通讯的方法,(4)为了更好的使用AIDL进行进程间通讯,本章引入了Binder连接池的概念。

(一)Android IPC简介

1.IPC是什么?

       进程间通信或者跨进程通信,两个进程间进行数据交互的一个过程。

2.进程与线程之间的关系?

       线程是CPU调度的最小单元,是一种有限的系统资源。而进程一般指一个执行单元,在PC和移动设备上指一个程序或者一个应用。一个进程可以包含多个线程,因此进程和线程是包含与被包含的关系。

3.什么是ANR应用无响应?

       一个进程中可以只有一个线程,即主线程,在Android里面主线程也叫UI线程,在UI线程里才能操作界面元素。很多时候,一个进程中需要执行大量耗时的任务,如果这些任务放在主线程中去执行就会造成界面无法响应,严重影响用户体验。解决方法:把一些耗时的任务放在线程中即可。

4.不同平台的IPC机制。

       Windows上:剪贴板、管道和邮槽等;Linux上:命名管道,共享内容、信号量等;Android:Binder实现进程间通信,Socket也可以实现任意两个终端之间的通信。

5.多进程通信的应用场景?

       1.ContentProvider去查询数据;2.有些模块由于特殊原因需要运行在单独的进程等。

(二)Android中的多进程模式

        使用android:process属性可以轻易开启多进程模式;但多进程未必比单个进程好,下面讨论。

(1)开启多进程模式

        Android中多进程是指一个应用中存在多个进程的情况,只有给四大组件(Activity、BroadcastReceiver、Service、Receiver)指定android:process一种方法。

2.1.1相关代码:


<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=".Main2Activity"
android:process=":remote"/>
<activity android:name=".Main3Activity"
android:process = "com.example.hzk.myapplication1.remote"/>

2.1.2 效果:

        使用DDMS视图或者命令可以查看:adb shell ps | findstr com.example.hzk.myapplication1

        前提是通信:

        《Android开发艺术探索》之IPC机制上(二)_安卓开发艺术探索

2.1.3 注意事项:

       Main2Activity和Main3Activity的“:”和“.”的区别:(1)一个简写,一个全称。(2)”:”开头的属当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以”:”开头的进程属于全局进程,其他应用通过ShareUID方式可以和它跑在同一进程中。

       每个应用有一个唯一的UID,具有相同UID的应用才能共享数据。两个应用通过ShareUID跑在同一个进程由是有要求的,需要这两个应用有相同的ShareUID并且签名相同才可以。分享的信息包括:data目录、组件信息等。

(2)多进程模式的运行机制

2.2.1举例

       新建了一个类,叫做UserManager,这个类中有一个public的静态成员变量,在MainActiviy中将其设置为2,跳转至Main2Acitivy后仍为1。这是因为Android为每一个应用都分配了一个独立的虚拟机,或者说为每个进程都分配一个独立的虚拟机,不同的虚拟机在内存上有不同的地址空间。不共享数据可以采用这种简单方法。

public class UserManager {
public static int mUserId = 1;
}

2.2.2多进程会造成如下几方面的问题

       1.静态成员和单例模式完全失效;2.线程同步机制完全失效(不是一块内存);3.SharedPreferences的可靠性下降(不支持两个线程同时执行写操作)。4.Application会多次创建(两个不同的虚拟机和Application的)。

2.2.3实现跨进程通信的方式

       实现跨进程通信的方式很多,比如1.Intent来传递数据,2.共享文件和SharedPreferences,3.基于Binder的Messenger和4. AIDL以及5.Socket等,但是为了更好地理解各种IPC方式,我们需要先熟悉一些基础概念,比如序列化相关的Serializable和Parcelable接口,以及Binder的概念,

(三)IPC基础概念介绍

        Serializable接口,Parcelable接口以及Binder。Serializable和Parcelable可以完成对象的序列化过程,当我们需要通过Intent和Binder传输数据时就需要使用Parcelable或者Serializable,对象持久化到存储设备上或者通过网络传输给其他客户端。序列化是将对象的状态信息转换为可以存储或传输的字节流形式的过程。

(1)Serializable接口

        让一个类去实现Serializable接口接口即可。

3.1.1代码实现:

       实现Serializable的代码:

public class Person implements Serializable{
private static final long serialVersionUID =1223424231123123L;
private String name;
private int age;
....
}

       MainActiviy中:

//序列化过程
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(person);
out.close();
//反序列化过程
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
Person newperson = (Person) in.readObject();

3.1.2 serialVersionUID作用:

       工作机制:序列化的时候会把当前类的serialversionUID写进序列化的文件中,当反序列化的时候系统会去检测文件中的serialversionUID,看它是否和当前类的serialversionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变换,比如成员,类型可能发生了变化,这个时候是无法正常的反序列化的。eg:当版本升级后,我们可能删除了某个变量或者新增加了一些新的成员变量,这个时候我们的反向序列化过程仍然能够成功,程序仍然能够最大限度的恢复数据。但如修改了类名,修改了成员常量的类型会导致反序列化失败。

        需要注意的是:(1)静态成员变量属于类不属于对象,所以不会参与序列化过程;(2)transient关键字标记的成员变量不参与序列化过程。

(2)Parcelable接口

         Parcelable是一个接口,一个类的对象就可以实现序列化并可以通过Intent和Binder传递。

3.2.1代码实现:

public class User implements Parcelable{
public int userId;
public String userName;
public boolean isMale;
protected User(int userId,String userName,boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
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];
}
};
public User(Parcel in) {
userId = in.readInt();
userName =in.readString();
isMale = in.readInt() ==1;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(userId);
out.writeString(userName);
out.writeInt(isMale?1:0);
// out.writeParcelable((Parcelable) person,0);
}
}

3.2.2每个实现方法的功能:

       序列化功能由writeToParcel方法来完成,最终是通过Parcel中的一系列write方法来完成的,反序列化功能由CREATOR来完成,其内部标明了如何创建序列化对象和数组,并通过Parcel的一系列read方法来完成反序列化过程。User方法从序列化后的对象中创建原始对象。

3.2.3  Parcelatle 和Serializable如何选取?

       Serializable是Java中的序列化接口,其使用起来简单但是开销很大,过程需要大量I/O操作。而Parcelable是Andrord中的序列化方式,效率很高,首选Parcelatle,Parcelable主要用在内存序列化上,通过Parcelable将对象序列化到存储设备中或者将对象序列化后通过网络传输。过程复杂,这两种情况下建议大家使用Serializabie。

(3)Binder

3.3.1 Binder是什么?

        Binder是Android中的一个类,它实现了IBinder接口;IPC角度来说,Binder是Android中的一种跨进程通信方式;Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager等等)和相应Managerservice的桥梁;Android应用层来说,Binder是客户端和服务端进行通信的媒介。服务包括普通服务和基于AIDL(Messenager底层)的服务。Messenger的的底层其实是AIDL,所以这里选择用AIDL来分析Binder的工作机制。为了分析Binder的工作机制,我们需要新建一个AIDL示例。

3.3.2 AIDL分析Binder的工作机制相关代码:

/**1.Book.java
* 图书信息的类。实现了Parcelable的接口。
*/
public class Book implements Parcelable {
public int bookId;
public String bookname;
protected 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 out, int flags) {
out.writeInt(bookId);
out.writeString(bookname);
}
}

//2. Book1.aidl
package com.example.hzk.myapplication3;
// Declare any non-default types here with import statements
//Book类在AIDL中的声明。
parcelable Book;

//3. IBookManager.aidl
//我们定义的接口,里面有两个方法:getBookList和addBook。getBook用于远程鼓舞段获取图书列表;addBook用于往图书列表中添加一本书。仍然需要导入Boook类,虽然在同一个包里。
import com.example.hzk.myapplication3.Book;
interface IBookManager {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
List<Book> getBookLists();
void addBook(in Book book);
}

3.3.3 系统为IBookManager.aidl自动生成的iBookManager.java接口类。

1.简单介绍:

      系统自动生成,声明了两个方法getBookList和addBook,声明了两个整型的id分别用于标识这两个方法,这两个id用于标识在transact过程中客户端所请求的到底是哪个方法。接着,它声明了一个内部类Stub,这个Stub就是一个 Binder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程,而当两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub的内部代理类Proxy来完成。IBookManager这个接口的核心实现就是它的内部类Stub和Stub的内部代理类Proxy。

2.方法及相关标识含义

DESCRIPTOR  Binder的唯一标识,一般用当前Binder的类名表示;

asInterface       将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象;

asBinder           此方法用于返回当前Binder对象;

onTransact       运行在服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。服务端通过code可定客户端所请求的目标方法是什么,接着从data中取出目标方法所需要的参数,然后执行目标方法,目标方法执行完毕后向reply中写入返回值。

Proxy#getBookList    运行于客户端,远程调用此方法时,创建该方法所需要的输入型Parcel对象_data、输出型Parcel对象_reply和返回值对象List然后把该方法的参数信息写入_data中(如果有参数的话):接着调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起。直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果;最后返回_reply中的数据。

3.Binder的工作机制

                            《Android开发艺术探索》之IPC机制上(二)_Binder_02

        注意:远程方法是很耗时不能再UI线程中发起此远程请求;Binder方法需要用同步的方法去实现,因为他已经运行在一个线程中了。

4.手撸一个Binder

         抛开AIDL文件直接写一个Binder出来。AIDL文件,是为了方便系统为我们生成代码。首先它本身是一个Binder的接口(继承了IInterface),其次它的内部有个Stub类,这个类就是个Binder。AIDL文件的本质是系统为我们提供了一种快速实现Binder的工具。

       (1)声明一个AIDL性质的接口,只需要继承IInterface接口即可,IInterface接口中只一个asBinder方法。

       (2)实现Stub类和Stub类中的Proxy代理类

5.Binder连接断裂

         linkToDeath和unlinkToDeath:Binder运行在服务端进程,如果服务端进程由于某种原因异常终止,这个时候我们到服务端的Binder连接断裂(称之为Binder死亡),会导致我们的远程调用失败。

Binder中提供了两个配对的方法linkTopeath和unlinkTopeath,通过 linkToDeath我们可以给Binder设置一个死亡代理,当Binder死亡时,我们就会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接。

         如何给Binder设置死亡代理成?(1)声明一个DeathRecipient对象、DeathRecipient是一个接口,其内部只有一个方法binderDied,我们需要实现这个方法,当Binder死亡的时候,系统就会回调binderDied方法,然后我们就可以移出之前绑定的binder代理并重新绑定远程服务;(2)在客户端绑定远程服务成功之后,给binder设置死亡代理;上述已经给Binder设置死亡代理了,当Binder死亡之后我们就可以收到通知了,另外。通过Binder的方法isBinderAlive也可以判断Binder是否死亡。

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if(mBookManager == null){
return;
}
mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mBookManager = null;
//这个可以重新绑定远程service
}
};

mService= IMessageBoxManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);

(四)Android中的IPC方式

       在Intent中附加extras来传递消息,或者通过共享文件的方式来共享数据,还可以采用Binder方式来跨进程通信,另外,ContentProvider天生就是支持扩进程访问的,所以通过Socket也可以实现IPC。

(1)使用Bundle

       Bundle,用Intent传值的引用对象,我们都知道A,S,B三大组件都是可以通过intent的Bundle传值的,那是因为Bundle实现了Parcelable接口。Bunlde中附加我们需要传输给远程进程的信息,然后用intent发送过去。传输的数据必须能够序列化(基本数据类型,实现了Parcelable接口的对象,实现了Serializable接口的对象以及一些Android支持的特殊对象)。

        场景:A进程正在进行一个计算,计算完成之后他要启动B进程一个组件并把计算结果传递给B进程,可是遗憾的是这个数据不支持放在Bundle里,因此无法使用intent来传递。

        解决方案:通过intent启动进程B的一个Service组件,让Service去完成计算,计算完毕后再启动B进程。核心思想:将A进程的计算任务转移到B进程的后台Service去执行。

(2)使用文件共享:

       两个进程通过读/写同一个文件来交换数据,比如A进程把数据写入文件,B再去读取。Android可以并发进行读和写,尽管可能会出问题。除了可交换一些文本信息外,我们还可以序列化UI和对象到文件里,引用的时候再恢复。

       优缺点:文件格式是没有具体要求的(文本文件/XML文件),只要读/写双方约定数据格式即可。如果并发读/写,那么我们读出的内容就可能不是最新的或者更严重。尽量避免并发写这种情况的发生或者考虑使用线程同步来限制多个线程的写操作。文件共享方式适合在对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读/写的问题。

      SharedPreference:属于文件的一种,但是由于系统对它的读/写有一定的缓存策略,即在内存中会有一份SharedPreferences文件的缓存,因此在多进程模式下,系统对它的读/写就变得不可靠,当面对高并发的读/写访问Sharedpreferences有很大几率会丢失数据,因此,不建议在进程间通信中使SharedPreferences。

      举例:在onResume序列化一个对象到sd卡,第二个Activity去恢复。代码:

//MainAcvtivity中:
private void persistToFile() {
new Thread(new Runnable() {
@Override
public void run() {
User user = new User(1, "hello world", false);
File dir = new File(MyConstants.CHAPTER_2_PATH);
if (!dir.exists()) {
dir.mkdirs();
}
File cachedFile = new File(MyConstants.CACHE_FILE_PATH);
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream(
new FileOutputStream(cachedFile));
objectOutputStream.writeObject(user);
Log.d(TAG, "persist user:" + user);
} catch (IOException e) {
e.printStackTrace();
} finally {
MyUtils.close(objectOutputStream);
}
}
}).start();
}
//SecondActivity中:
private void recoverFromFile() {
new Thread(new Runnable() {
@Override
public void run() {
User user = null;
File cachedFile = new File(MyConstants.CACHE_FILE_PATH);
if (cachedFile.exists()) {
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(
new FileInputStream(cachedFile));
user = (User) objectInputStream.readObject();
Log.d(TAG, "recover user:" + user);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
MyUtils.close(objectInputStream);
}
}
}
}).start();
}
//MyConstants中:
public class MyConstants {
public static final String CHAPTER_2_PATH = Environment
.getExternalStorageDirectory().getPath()
+ "/singwhatiwanna/chapter_2/";

public static final String CACHE_FILE_PATH = CHAPTER_2_PATH + "usercache";

public static final int MSG_FROM_CLIENT = 0;
public static final int MSG_FROM_SERVICE = 1;
}

  《Android开发艺术探索》之IPC机制上(二)_Parcelable_03