Android IPC多进程介绍

注意:本篇文章是本人阅读相关文章所写下的总结,方便以后查阅,所有内容非原创,侵权删。

本篇文章内容来自于:
Android开发艺术探索

目录

  1. 什么是IPC
  2. Android中的多进程模式
    2.1 如何开启多进程模式
    2.2 多进程模式的坏影响(为什么会有各种跨进程通信方式)
  3. IPC基础概念
    3.1 Serializable接口
    3.2 Parcelable接口
    3.3 Binder接口

1. 什么是IPC

IPC(Inter-Process Communication),进程间通信/跨进程通信,指两个进程之间进行数据交换。

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

Android的进程通信方式
通过Binder可以实现进程间的通信。
通过Socket可以实现两个终端之间的通信。

使用多进程的场景(2种)
场景一:
一个应用因为某些原因自身需要采用多进程模式来实现。
比如有些模块由于特殊原因需要运行在单独的进程中。
比如为了加大一个应用可使用的内存所以需要通过多进程来获取多份内存空间。Android为单个应用使用的最大内存做了限制,
场景二:
当前应用需要向其他应用获取数据,因为是2个应用,则必须采用跨进程的方式来获取所需的数据;
甚至我们通过系统提供的ContentProvider去查询数据的时候,也是一种进程间通信,只不过通信细节被系统内部屏蔽了。

2. Android中的多进程模式

2.1 如何开启多进程模式

多进程:一个应用中存在多个进程。

2.1.1 方法一:
通过四大组件指定android:process属性,则可开启多进程模式。

        
        
  • DemoActivity启动时,系统会为它创建一个新的进程,进程名为"com.xl.demo:remote"。(假设当前包名为"com.xl.demo")
  • HelloActivity启动时,进程名为"com.xl.haha"
  • 没有指定process属性的四大组件,则运行在默认进程,进程名为包名

android:process=":remote"与"com.xl.haha"的区别
区别一:
":remote"的":"含义是指当前的进程名前面加上当前的包名,是简写。
"com.xl.haha"是完整的命名方式。
区别二:
":remote"是当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中。
"com.xl.haha"是全局进程,其他应用通过shareUID方式可以和它跑在同一个进程中。

Android系统会为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据。
两个应用通过shareUID跑在同一个进程中是有要求的,需要2个应用有相同的shareUID并且签名相同才可以。
此时,他们可以互相访问彼此私有数据(data目录、组件信息等),不管他们是否跑在同一个进程中。

2.1.2 方法二:
通过JNI在native层去fork一个进程。

2.2 多进程模式的坏影响

多进程带来的影响:不同进程中的四大组件不可以通过内存来共享数据。
因为android为每个应用/每个进程分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,导致在不同的虚拟机访问同一个类的对象会产生多个副本。

总而言之
使用多进程会造成如下几方面的问题:
1.静态成员和单例模式完全失效
2.线程同步机制完全失效
3.sharepreferences的可靠性下滑。
4.application会多次重建。

为了解决这个问题,虽然不能直接地共享内存,但是系统提供很多跨进程通信方法来实现数据交互。
跨进程通信方式有:
通过Intent来传递数据
共享文件和sharedPreferences
基于Binder的Messenger和AIDL 单方面的客户端向服务端推消息用AIDL
Socket 两者互通用Socket

3. IPC基础概念

Serializable和Parcelable可以完成对象的序列化过程。

  • 当我们需要用Intent或者Binder传输数据时需要使用Parcelable或Serializable
  • 我们需要将对象持久化到设备上或者通过网络传输时,需要用Serializable完成对象持久化

3.1 Serializable接口

使用Serializable来序列化:
只需要这个类实现Serializable接口并声明一个serialVersionUID(不是必须的)即可

//User类
public class User implements Serializable{
    private static final long serialVersionUID = 3983908423L;
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    ...
}

//实现序列化
        User user = new User("a", 1);
        File dir = Environment.getExternalStorageDirectory();
        File file = new File(dir, "haha.txt");
        //序列化  
        try {
            FileOutputStream fos = new FileOutputStream(file);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(user);
            oos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        //反序列化  
        try {
            FileInputStream fis = new FileInputStream(file);
            ObjectInputStream ois = new ObjectInputStream(fis);
            User user1 = (User) ois.readObject();
            Log.d("xl", user1.toString());
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

关于serialVersionUID
serialVersionUID用于辅助序列化和反序列化过程的。
序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能够正常的反序列化。

serialVersionUID的详细工作机制:
序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中,当反序列化的时候系统会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致。
如果一致则说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则则说明当前类和序列化的类相比发生了某些变换(比如成员变量的数量、类型发生变化),则无法正常反序列化。
建议手动赋值,在很大程度上避免反序列化的失败。

3.2 Parcelable接口

只需要实现这个接口即可。

public class User implements Parcelable{
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    protected User(Parcel in) {
        name = in.readString();
        age = in.readInt();
    }

    //反序列化
    public static final Creator CREATOR = new Creator() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };

    //内容描述
    @Override
    public int describeContents() {
        return 0;
    }

    //序列化
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
    }
}

关于Parcel
Parcel内部包装了可序列化的数据,

选择Parcelable还是Serializable?
Serializable是Java中的序列化接口,使用简单但开销大,序列化和反序列化需要大量IO操作。
Parcelable是Android中的序列化方式,更适合用于Android,只是操作麻烦,但效率高,是Android推荐的序列化方式。
因此首选Parcelable。
Parcelable主要用在内存序列化上。
将对象序列化到存储设备或者网络传输用Serializable

3.3 Binder接口

Binder是Android的一个类,实现了IBinder接口。

public class Binder implements IBinder {
3.3.1 什么是Binder
  • 从IPC角度来说,Binder是Android中的一个跨进程通信方式
  • 可以理解为一种虚拟的物理设备,设备驱动是/dev/binder,该通信方式linux中没有。
  • 从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager等)和相应ManagerService的桥梁。
  • 从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService时,服务端返回一个包含服务端业务调用的Binder对象,通过这个Binder对象,客户端可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。

Binder主要用于Service中,包括AIDL和Messenger。
其中普通Service中的Binder不涉及进程间通信。
Messenger的底层其实是AIDL。

3.3.2 Binder的使用以及工作机制

ALDL示例(Binder的使用):
Android:如何创建一个AIDL

完成创建后,SDK会自动生成AIDL所对应的Binder类。
在build/generated/source/aidl/你的 flavor/ 下。


Binder的工作机制

使用时,当调用相应的接口方法getBookList(),
则会先调用Stub的getBookList()方法
该方法首先创建该方法的输入型和输出型Parcel对象_data、_reply 和返回值对象,
将方法参数写入_data,再调用transact方法来发起RPC请求,同时当前线程挂起。
然后服务端的onTransact方法会被调用。
直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程返回结果。
当服务端的onTransact方法被调用
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
服务端通过code来确定所请求的目标方法。从data中取出目标方法需要的参数,然后执行目标方法。
目标方法执行完,则往reply中写入返回值。

3.3.3 Binder的重连机制

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

Binder的2个很重要的方法linkToDeath和unlinkToDeath
通过linkToDeath可以给Binder设置一个死亡代理,当Binder死亡时,我们会收到通知,可以重新发起请求从而恢复连接。

你可能感兴趣的:(Android IPC多进程介绍)