Android跨进程通信-IPC初探(一)

IPC初探(一)


进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。
IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。
Android中特有的IPC机制主要是Binder,当然也支持Socket。


1. Android中的多进程模式

  1. 开启多进程模式

    1. 在native层fork一个新的进程。不是常用的创建多进程方式,暂时不做讨论。
    2. 为四大组件指定android:process属性:

      //packageName:com.domain.test
      <service android:name=".MyService1" />
      <service android:name=".MyService2" android:process=":remote" />
      <service android:name=".MyService3" android:process="com.domain.test.remote" />
      1. 3个Service的进程名分别为:

        //packageName:com.domain.test
        MyService1    com.domain.test
        MyService2    com.domain.test:remote
        MyService3    com.domain.test.remote
        • MyService2":"是简写的命名方式,是在":"前加上完整的包名,形成最终的进程名,它等同于 android:process="com.domain.test:remote"
        • MyService3 则是完整的命名方式。
      2. 两种指定新进程方式的区别:
        • ":"方式指定的进程,属于当前应用私有进程,其它应用的组件不可以与它运行在同一个进程中。
        • 不以":"方式指定的进程,是全局进程,其它应用可以通过ShareUID方式和它运行在同一个进程中。
  2. 多进程模式的问题

    系统会为每个进程都分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,所以会导致下列问题:

    1. Application 多次创建,所以最好不要将数据保存在Application中。
    2. 静态成员、类的对象将拥有多个副本,因为在每个进程中都会独立分配内存。
    3. 线程同步将会失效。因为线程压根就是跑在各自独立的进程中。
    4. SharedPreferences 可靠性下降(SharedPreferences不支持多个进程同时写,会有一定的几率丢失数据)。

    Android提供了一些方法来避免这些问题,比如Intent,Messenger,或者Binder,我们也可以利用文件共享来实现进程通信。


2. 传递序列化的对象

使用 SerializableParcelable 接口。


  1. Serializable是Java 提供的接口,通过ObjectOutputStreamObjectIntputStream 序列化和反序列化对象。
  2. Parcelable 是Android提供的接口,使用方法参考示例代码:

    ```
    public class Student implements Parcelable {
        public String name;
        public int stuId;
    
    
        public Student(int stuId, String name) {
            this.name = name;
            this.stuId = stuId;
        }
    
        protected Student(Parcel in) {
            name = in.readString();
            stuId = in.readInt();
        }
    
        public Student() {
    
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(name);
            dest.writeInt(stuId);
        }
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        public static final Creator<Student> CREATOR = new Creator<Student>() {
            @Override
            public Student createFromParcel(Parcel in) {
                return new Student(in);
            }
    
            @Override
            public Student[] newArray(int size) {
                return new Student[size];
            }
        };
    
        @Override
        public String toString() {
            return String.format("[stuId:%s, name:%s]", stuId, name);
        }
    
    
    }
    ```
    
  3. 在Android平台,推荐使用 Parcelable接口, 它的效率更高。
    - Serializable 的序列化/反序列化过程,需要大量的 I/O 操作,而且使用了反射机制,在序列化时会创建很多的临时变量,时间和空间开销较大。
    - Serializable 是一种标识接口(marker interface),编码更简洁。
    - Parcelable 的原理是将对象的属性分别写入parcel对象,这些属性都必须是intent支持的类型,包括基本类型和实现了Parcelable的对象。
    - List和Map也可以序列化,只要它们里面的每个对象都实现了Parcelable


3. Binder了解一下

  1. 在了解Binder之前,发挥一下想象。所谓IPC,就相当于在两个互相独立、互不接触的空间,搭建一条公路,然后就可以互相传递货物了:
    这里写图片描述

    • 为何需要中间的圆圈:前文提到,多进程属于不同的内存空间,你取到到任何常量、变量对象,通通只是本线程内部的值。
    • 如果你启动一个Intent,从进程1的组件A启动进程2的组件B,那么你传递的货物,就实现了跨进程的传递,而代价是货物必须实现 Parcelable 接口 ,或干脆就是基本类型。
  2. 上面这个朴素的模型实在粗糙简陋,现在来了解一下Binder的工作机制:
    image

    可见在Binder机制中,两个进程是C/S结构,其中发起请求的一方是客户端,而另一端提供服务的是服务端。

  3. 我准备了一个demo, 简单描述下它的需求和实现。代码后面将会贴上来:
    服务端进程拥有数据(List<Student> list),而客户端请求获取list的内容,并可以进行添加操作。

    1. 数据类是Student ,它实现了Parcelable
    2. IStudentManager
      • 声明了两个接口:addStudentgetStudentList , 这正是客户端的需求
      • 内部类IStudentManager.Stub: 这个Stub类且继承了Binder。它提供了两个功能,asBinder来返回Binder对象, asInterface 则将Binder 对象转化为IStudentManager 接口对象。
      • Stub的内部类:IStudentManager.Stub.Proxy , 它是IStudentManager 的真正实现,从名字也可以看出,它是Stub的代理。
      • 你可以手动写一个这样的接口,但系统提供了AIDL的方式,可以自动生成这个接口(//手动斜眼)。
    3. StudentManagerService:它必须提供一个IStudentManager.Stub的实现,也就是一个binder, 然后在onBind方法中返回这个binder

    4. 客户端:

      • 发送请求:

        Intent intent = new Intent(this,StudentManagerService.class);
        bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
      • 参数mConnection :

        private ServiceConnection mServiceConnection = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    IStudentManager studentManager = IStudentManager.Stub.asInterface(service);
                    try {
                        List<Student> students = studentManager.getStudentList();
                        Log.d(TAG, "Client Request students:" + students);
        
                        Student student = new Student(1003,"Jack");
                        studentManager.addStudent(student);
                        Log.d(TAG, "Client Request students:" + students);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
        
                @Override
                public void onServiceDisconnected(ComponentName name) {
        
                }
            };
      • onServiceConnected:与远程服务成功连接的回调。显然,我们在成功连接远程服务之后,获取了数据列表,并添加了新的数据。
  4. 上代码:

    Github-IPCSimple

    一些小细节:

    • AIDL文件在Android Studio和Eclipse 的目录组织上略有不同,生成的java文件目录也不太一样。

      • Android Studio 的AIDL文件默认放在:src/main/aidl, aidl目录与java目录同级别
      • Android Studio 的AIDL生成的java文件在这里:build/generated/source/aidl/debug/
      • 写完AIDL,或clone项目之后,在Android Studio中 “做”(Make)一下,会自动生成相应的java文件。之后你才能在代码中使用这个相应的接口和类。
    • 请注意为远程服务指定的android:process属性

    • 客户端绑定了服务,需要在相应的地方解绑服务。

  5. 下期预告:我们将继续探索Binder的使用,实现更多的功能,发现更多的坑并填上。

猜你喜欢

转载自blog.csdn.net/cangely/article/details/80012476
今日推荐