开发艺术探索—IPC

2019-04-13  本文已影响0人  leap_

inter process communication,进程间通信,两个进程间进行数据交流,进程和线程:线程是cpu调度的最小单元,进程通常是执行单元,一个进程可以包含多个线程,进程在pc和移动设备上通常是一个程序或应用。
IPC的使用场景必须是多进程的,可以是一个程序中多个进程间通信,也可以是多个程序的进程进行通信,安卓中多进程的的通信方法是Binder,此外还有socket,本文详细帮助大家详细理解一下Binder方式的IPC机制。

安卓中多进程的作用

每个进程所能使用的资源是有限,特别是内存,安卓系统对用户进程有严格的内存要求,超过此内存限制时,应用将会发生OOM,此时我们采用多进程,在一个程序中开启多个进程,分担主进程的压力,避免OOM。

安卓中多进程的实现

要在安卓程序中实现多进程,只有在manifest四大组件的注册中,添加process属性,只有这一种方法!其实还有一种方法,那就是通过JNI在native层去fork一个新的进程,怎么样是不是看不懂,反正我也没看懂,书上就是这么写的;

 <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:process=":remote"
            android:name=".SecondActivity" />
        <activity
            android:process="com.example.demo_ipc.remote"
            android:name=".ThirdActivity"/>

我们没有给mainActivity指定process属性,分别给secondActivity指定了":remote",thirdActivity指定了"com.example.demo_ipc.remote"。此时launch三个activity,可以看到三个进程都运行了,mainActivity默认为主进程,secondActivity的 " : "的含义是前面省略了包名,运用此方法命名的进程是当前应用的私有进程,其他应用的组件不可以和他跑在同一进程中,而thirdActivity的进程则为全局进程,其他应用使用ShareUID方式可以和他跑在同意进程。

多进程产生的问题

我们知道Android为每个应用分配一个虚拟机,实际上是为每个进程分配一个独立的虚拟机,当一个应用是多进程的时候,这个应用就会有多个虚拟机,导致同一个类也会有多个版本,比如我们在上文的project中新建一个静态类里面放一个静态int i =0,在mainActivity中改变这个int=1,在SecondActivity打印出来,发现这个int还是0,我们有三个进程,就有三个int的副本,在线程一改变线程一里的int副本是不会改变线程二中int的值。
总结一下多进程产生的问题:

序列化

主要包括三方面的内容:Serializable接口,Parcelable接口,和Binder。
Serializable和Parcelable用于对象的序列化。

public class User implements Serializable {
    
    private static final long serialVersionUID = 1L;

    private int id;
    private String name;
    private int age;

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

对象序列化

        User user = new User(1,"jack",21);
        File file = new File("user.tet");
        try {
            OutputStream outputStream = new FileOutputStream(file);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
            objectOutputStream.writeObject(user);
            outputStream.close();
            objectOutputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

首先创建OutputStream 对象,在将他封装到ObjectOutputStream对象中,然后writeObject,close,就完成了对user对象的序列化。
反序列化

        try {
            InputStream inputStream = new FileInputStream(file);
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            User newUser = (User) objectInputStream.readObject();
            inputStream.close();
            objectInputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

步骤和序列化差不多。
注意一下我在user类中指定了一个serialVersionUID ,serialVersionUID的作用是用来辅助序列化和反序列化的,序列化的时候会把UID也序列化到文件中,在反序列化的时候会判断文件和类的UID是否相同,如果相同则可以反序列化,如果我们不指定UID,java会根据类的hash去算这个类的UID,如果这个类的成员发生变化,UID就会发生变化,反序列化就会失败。

public class User implements Parcelable {

    private static final long serialVersionUID = 1L;

    private int id;
    private String name;
    private int age;

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


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

    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];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeInt(id);
        parcel.writeString(name);
        parcel.writeInt(age);
    }
}

必须实现几个必要的方法:

写完以后就可以直接使用intent或binder传递User类的对象了
二者的比较:Serializable会产生大量的IO,非常的占用内存,Parcelable 的操作则比较繁琐。

进程间通信

安卓中进程间通信的方式对比
通信方式 Bundle 文件共享 Messager AIDL ContentProvider Socket
特点 只能传递Bundle支持的数据类型 无法做到进程间即时通信 不支持RPC,只能传递Bundle支持的数据类型 功能强大,使用复杂,需要处理好线程同步 可以理解为受约束的AIDL 不支持直接的RPC,实现稍微复杂
适用场景 四大组件之间的进程通信 无并发需求,数据实时性不高的场景 无RPC需求 有RPC需求 应用之间数据交换 网络进程间数据交换

使用AIDL进行进程间通信

1 创建一个子进程的服务
2 创建AIDL文件,在文件中写好业务接口
// IConnectionService.aidl
package com.example.myapplication;

// Declare any non-default types here with import statements

interface IConnectionService {

    // 业务接口
    void connect();
    void disconnect();
    boolean isConnect();

}

3 编译后得到自动生成的JAVA文件
4 在子进程服务中创建这个JAVA文件的实例,并通过onBind()返回给主进程
public class RemoteService extends Service {

    boolean isConnect = false;

    IConnectionService connectionService = new IConnectionService.Stub() {
        @Override
        public void connect() throws RemoteException {
            isConnect = true;
            Log.d("RemoteService","connect");
        }

        @Override
        public void disconnect() throws RemoteException {
            isConnect = false;
            Log.d("RemoteService","disConnect");
        }

        @Override
        public boolean isConnect() throws RemoteException {
            Log.d("RemoteService",isConnect+"");
            return isConnect;
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return connectionService.asBinder();
    }

}
5 在主进程中开启服务,通过这个实例进行通信
     IConnectionService connectionServiceMainProcess;

        Intent intent = new Intent(this,RemoteService.class);
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                    connectionServiceMainProcess = IConnectionService.Stub.asInterface(service);
                try {
                    connectionServiceMainProcess.connect();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
        }, Context.BIND_AUTO_CREATE);

浅谈Binder

什么是Binder?
传统的进程通信方式将发送方用户空间的数据先拷贝到内核空间内核缓存区,再从内核缓存区拷贝到接受方的用户空间,因此需要两次拷贝;
传统IPC
Binder将发送方的数据拷贝到内核缓存区,而接受方和内核缓存区都映射在同一块物理地址上,所以省去了一次拷贝的操作;
Binder

AIDL实现原理

首先编写一个AIDL接口文件,写上我们的业务抽象方法,通过编译我们会得到一个自动生成的java接口,这个业务接口继承了iInterface,还有一个内部类Stub,Stub内部类继承了Binder类,实现了业务接口,有三个比较重要的方法:

客户端调用bindService()绑定服务端,服务端会通过onBind()将业务接口的内部类Stub对象以Ibinder形式传递给客户端,在客户端的回调中,会调用asInterface()将Binder处理成Stub的代理,通过这个代理进行进程间通信;

上一篇 下一篇

猜你喜欢

热点阅读