Android IPC通信机制(一)

1. IPC序言

IPC即Inter-Process Communication,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程
线程是CPU调度的最小单元,是一种有限的系统资源。进程一般指一个执行单元,在PC和移动设备上是指一个程序或者应用。进程与线程是包含与被包含的关系。一个进程可以包含多个线程。最简单的情况下一个进程只有一个线程,即主线程(例如Android的UI线程)。

ANR:UI线程才能操作界面元素,把耗时任务放在主线程执行会造成界面无法响应,应用无响应。

Android中IPC使用场景:‘

  1. 有些模块由于特殊原因需要运行在单独的进程中
  2. 通过多进程获取多份内存空间
  3. 当前应用需要向其它应用获取数据

2 Android中多进程模式

在Android中使用多进程只有一种方法:给四大组件在AndroidManifest中指定android:process属性,这个属性值就是进程名

可以用DDMS视图查看进程信息,或shell查看,命令为adb shell ps 或者adb shell ps | 进程名

注意:进程名以开头是只在当前进程名前面附加上当前的包名,并且属于当前应用的私有进程。

Android系统为每个应用分配一个唯一的UID。具有相同的UID的应用才能共享数据(签名页相同)

Android为每个进程都分配了一个独立的虚拟机,不同虚拟机在内存分配上有不同的地址空间,导致不同的虚拟机访问同一个类的对象会产生多份副本。例如不同进程的Activity对静态变量的修改,对其他进程不会造成任何影响。

所有运行在不同进程的四大组件,只要它们之间需要通过内存在共享数据,都会共享失败。四大组件之间不可能不通过中间层来共享数据

多进程可能带来的问题:

  1. 静态成员和单例模式完全失效
  2. 线程同步机制完全失效(不同进程锁的不是同一个对象)
  3. SharedPreferences的可靠性下降(并发读/写XML会出现问题,导致一定几率数据丢失)
  4. Application会多次创建(运行在同一个进程中的组件是属于同一个虚拟机和同一个Application)

对于一个应用间的多进程,可以理解为相当于两个不同的应用采用了SharedUID模式

3. IPC基础概念介绍

3.1 Serializable接口

Serializable是Java提供的一个序列化接口(空接口),为对象提供标准的序列化和反序列化操作。

只需要一个类去实现Serializable接口并声明一个serialVersionUID即可实现序列化。例如:

private static final long serialVersionUID = 8711368828010083044L

实际上这个serialVersionUID也不是必需的,不声明这个一样可以序列化,但会对反序列化产生影响。对对象实现序列化和反序列化采用ObjectOutputStreamObjectInputStream
示例:

//序列化
User user = new User(0,"jake",true);
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();

如果不手动指定serialVersionUID的值,反序列化时当前类有所改变(比如增删了某些成员变量),那么系统就会重新计算当前类的hash值并赋值给serialVersionUID。这个时候当前类的serialVersionUID就和序列化数据中的serialVersionUID不一致,导致反序列化失败,程序就出现crash。

静态成员变量属于类不属于对象,不参与序列化过程,其次transient关键字标记的成员变量不参与序列化过程。

3.2 Parceable接口

Parceable也是一个接口,只要实现这个接口,一个类对象就可以实现序列化并可以通过Intent和Binder传递。

示例:

public class User implements Parcelable{
        public int userId;
        public String userName;
        public boolean isMale;
 		
 		public Book book;
 		
 		public User(int userId, String userName, boolean iaMale) {
 			this.userId = userId;
 			this.userName = userName;
 			this.isMale = isMale;
 		}
       	
 		// 重写describeContents()方法
       @Override
       public int describeContents() {
            return 0;
        }
 		// 重写writeToParcel()方法
        @Override
        public void writeToParcel(Parcel parcel, int i) {
            parcel.writeInt(userId);
            parcel.writeString(userName);
            parcel.writeInt(isMale ? 1 : 0);
            parcel.writeParcelable(book, 0);
        }
 		// 创建一个Parcelable.Creator接口
    public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>(){
            @Override
                public User createFromParcel(Parcel parcel) {
                    return new User(parcel);
            }
 
            @Override
            public Person[] newArray(int i) {
                    return new Person[i];
            }
        };
	private User(Parcel in) {
		userId = in.readInt();
		userName = in.readString();
		isMale = in.readInt() == 1;
		book = in.readPaecelable(Thread.currentThread().getContextClassLoader());
	}
}

Parceable内部包装了可序列化的数据,序列化的功能由writeToParcel方法完成,最终通过一系列write方法来完成。反序列化功能由CREATOR来完成。

内容描述功能由describeContents方法完成,几乎所有情况下都应该返回0,仅当当前对象中存在文件描述符时返回1

Parcelable主要在内存序列化上,Parcelable也可以将对象序列化到存储设备中或者将对象序列化后通过网络传输,但是稍显复杂,推荐使用Serializable

3.3 Binder

Binder是Android中的一个类,实现了IBinder接口。从IPC角度说,Binder是Andoird的一种跨进程通讯方式。从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager·、WindowManager)和相应ManagerService的桥梁。从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService时,服务端返回一个包含服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务器端提供的服务或者数据(包括普通服务和基于AIDL的服务)。

Android中Binder主要用于Service,包括AIDLMessenger。普通Service的Binder不涉及进程间通信,Messenger的底层其实是AIDL,所以下面通过AIDL分析Binder的工作机制。


  • 待补充示例

这个接口的核心实现就是她内部类Stub和Stub的内部代理类Proxy。这两个类的每个方法的含义如下:

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

asInterface(android.os.IBinder obj)
将服务端的Binder对象转换为客户端所需的AIDL接口类型的对象,如果C/S位于同一进程,此方法返回就是服务端的Stub对象本身,否则返回的就是系统封装后的Stub.proxy对象

asBinder
返回当前Binder对象

onTransact
这个方法运行在服务端的Binder线程池中,由客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。该方法的原型是
java public Boolean onTransact(int code,Parcelable data,Parcelable reply,int flags)

  • 服务端通过code确定客户端请求的目标方法是什么
  • 接着从data取出目标方法所需的参数,然后执行目标方法
  • 执行完毕后向reply写入返回值(如果有返回值)
  • 如果这个方法返回值为false,那么服务端的请求会失败,利用这个特性我们可以来做权限验证

Proxy#getBookList 和Proxy#addBook(待更新getBookList等)

  • 这个方法运行在客户端,首先该方法所需要的输入型对象Parcel对象_data,输出型Parcel对象_reply和返回值对象List。
  • 然后把该方法的参数信息写入_data(如果有参数),3
  • 接着调用transact方法发起RPC(远程过程调用),同时当前线程挂起,
  • 然后服务端的onTransact方法会被调用知道RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果,最后返回_reply中的数据。

注意

  • 当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,如果一个远程方法很耗时,那么不能在UI线程中发起此远程请求
  • 由于服务端的BInder方法运行在Binder线程池中,所以Binder方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。

在这里插入图片描述
之所以提供AIDL文件,是为了方便系统为我们生成代码,我们完全可以自己实现Binder。

Binder运行在服务端进程,如果服务端进程由于某种原因异常终止,这个时候到服务端的Binder断裂(也称Binder死亡),导致远程调用失败,。Binder提供了两个配对的方法linkToDeathunlinkToDeath,给Binder设置死亡代理,Binder死亡,就会接收通知,我们可以重新发起连接请求。

  • 声明一个DeathRecipient对象。DeathRecipient只有一个方法binderDied,当Binder死亡的时候,系统就会回调DeathRecipient方法。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){
     @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);

可以通过Binder的isBinderAlive判断Binder是否死亡。

猜你喜欢

转载自blog.csdn.net/weixin_43499030/article/details/89914943