android IPC 机制及进程通信

本文为读书笔记: 《Android 开发艺术探索 》——第二章 IPC 机制

android 开发中有时候需要用到多进程,那么了解进程间通信对我们开发就尤为重要。往往多进程分为两种情况: 一是一个应用因某些原因需要多进程(如某些模块需要在单独的进程中,或者是为了加大本应用所能使用的内存空间等);二是当前应用需要想起他应用获取数据。

一、IPC简介

IPCInter-Process Communication 的缩写,含义为进程间通信或者跨进程通信,指两个进程间进行数据交互的过程。
进程:是cpu调度的最小单位,是一种有限的系统资源,一般只一个执行单元。在PC或者移动设备上指一个程序或者一个应用。而一个进程可以包含多个进程。

IPC 不是android 中所独有的,任何操作系统都有。Windows上可以通过剪切板、管道和邮槽等来进行进程间通信;Linux 上可以通过命名管道、共享内存、信号量等进行通信;而android 他是一种基于linux 内核的移动操作系统,他的进程间通信方式并不完全继承自linux, 却有着自己独特的方式——Binder,此外,还可以使用Socket进行进程间通信。

二、android 中的多进程模式

在android 中如果使用多进程,通过给四大组件指定 android:process 属性即可开启多进程,此外还可以通过 JNI 在native 层 fork 一个新的进程。
如下activity 配置示例代码:

 <activity android:name=".activity.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
 </activity>

<activity
    android:name=".activity.SecondActivity"
    android:configChanges="screenLayout"
    android:label="@string/app_name"
    android:process=":remote"/>
<activity android:name=".activity.ThirdActivity"
    android:configChanges="screenLayout"
    android:label="@string/app_name"
    android:process="com.imtianx.ipcdemo.remote"/>

通过 MainActivity 启动 SecondActivity ,SecondActivity 启动 ThirdActivity。
这三个activity 分别运行在三个不同的进程中:

//在as中打开terminal 输入下面命令
adb shell
//查看进程:ps|grep 包名
ps|grep cn.imtianx.ipcdemo
//三个进程如下
USER      PID   PPID  VSIZE  RSS   WCHAN            PC  NAME
u0_a106   3682  1680  1136428 57512 SyS_epoll_ 00000000 S cn.imtianx.ipcdemo
u0_a106   3808  1680  1134324 57360 SyS_epoll_ 00000000 S cn.imtianx.ipcdemo:remote
u0_a106   3918  1680  1166916 57920 SyS_epoll_ 00000000 S cn.imtianx.ipcdemo.remote

或者在monitor界面也可以查看。MainActivity没有指定process属性,则他在应用的包名 cn.imtianx.ipcdemo 对应的进程中,其他两个分别 SecondActivity、ThirdActivity 所在的进程。对于SecondActivity ,他的进程是 以 申明的,是指在当前的进程名前加上包名,为 私有进程,其他应用不可与其在同一进程中;而不以 “ :”开头的进程,属于 全局进程 ,则可以。

对于每个进程,都有一个独立的虚拟机,在内存中都有着不同的地址,这会导致不同虚拟机访问同一对象会产生多个副本,互不影响,若在一个进程中修改了数据,在另一个进程中不会变。即多进程下不能通过内存共享数据。

一般多进程会造成如下几个问题:

  • 静态成员和单利失效
  • 多线程同步进制失效
  • SharedPreference 的可靠性下降
    底层是通过读写XML实现的,并发读写是会出问题的
  • Application 会被多次重建
    运行在同一进程中的组件属于同一个虚拟机和同一个Application的

三、IPC 基础概念

只有明白了 IPC 中的 Serializable接口、 Parcelable接口 和 Binder相关的基础概念 ,才能更好的理解跨进程通信。 Serializable接口和 Parcelable接口 是实现序列化的两种方式。对于Intent 和 Binder 传输数据、对象持久化到本地或者网络传输,都需要使用。

3.1 Serializable接口

Serializable接口 是java 中的,是一个空的接口,使用时直接实现,添加如下标识,即可自动实现序列化和反序列化操作。在使用过程中开销较大,需要大量操作io。

private static final long serialVersionUID = 1L;

对于 transient 标识的属性和静态成员变量 , 不参与序列化。

3.2 Parcelable接口

Parcelable接口 是android 特有的序列化方式,使用起来稍微麻烦,但是效率较高,主要用于内存序列化。如下使用示例:

public class User implements Parcelable{

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

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

    //用于反序列化
    public static final Creator<User> CREATOR = new Creator<User>() {
        /**
         * 从序列化对象中创建原始对象
         * @param in
         * @return
         */
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        /**
         * 创建指定长度的原始对象数组
         * @param size
         * @return
         */
        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };

    /**
     * 内容功能描述
     * 大多数返回0,仅当当前对象中存在文件描述符时返回1
     * @return
     */
    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * 当前对象写入序列化结构
     * @param dest
     * @param flags
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(id);
        dest.writeString(name);
        dest.writeString(sex);
        dest.writeInt(age);
    }
}

这里虽然看着很复杂,但这些方法全部可以自动生成,不用手动编写。android 中的 Intent 、Bundle、Bitmap等都实现了Parcelable接口,都是可以进行序列化的。

3.3 Binder

Binder 实现了 IBinder接口。从ipc角度,binder是一种跨进程通信方式,还可以理解为一种虚拟的物理设备,其驱动是 /dev/binder ;从 android Framework 角度说,Binder 是 ServiceManager 连接各种 Manager(ActivityManager、WindowManger,…)和相应 ManagerServices的桥梁;从 android 应用层来说,Binder是客户端与服务器端进行通信的媒介。

android中 Binder 主要用在Services中,包括 AIDL 和 Messenger , Messenger 底层是 AIDL 实现的。

如下是Binder的工作流程图:

四、android 中的IPC 方式

如下各种 IPC 方式的优缺点对比:

名称 优点 缺点 使用场景
Bundle 简单易用 只能传输Bundle支持的数据类型 四大组件间的进程通信
文件共享 简单易用 不适合高并发场景,并且无法做到进程间的即时通讯 无并发访问情形,交换简单的数据,实时性不高
AIDL 功能强大,支持一对多并发通信,实时通信 使用复杂,需要处理好线程同步 一对多通信且有RPC需求
Messenger 功能一般,支持一对多串行通信,实时通信 不能很好处理高并发情形,不支持RPC,数据通过Message进行传输(只能传输Bundle支持的数据类型) 低并发的一对多即时通讯,无RPC需求,或者无需返回结果
ContentProvider 在数据源访问方面功能强大,支持一对多并发数据共享,可以通过Call方法扩展其他操作 可以理解为受约束的AIDL,主要提供数据源的CRUD操作 一对多的进程数据共享
Socket 功能强大,可以通过网络传输字节流,支持一对多实时通信 实现复杂,不支持直接的RPC 网络数据交换

五、AIDL 的简单使用

只有允许不同应用的客户端用 IPC 方式访问服务,并且想要在服务中处理多线程时,才有必要使用 AIDL。 如果您不需要执行跨越不同应用的并发 IPC,就应该通过实现一个 Binder 创建接口;或者,如果您想执行 IPC,但根本不需要处理多线程,则使用 Messenger 类来实现接口。

使用 AIDL 创建绑定服务的基本步骤如下:

  1. 创建 .aidl 文件
    在android studio 中 ,可以直接创建 AIDL ,自动创建相关的 aidl 包,这里创建 IAddAidlInterface.aidl 文件,具体内容如下:
 // IAddAidlInterface.aidl
 package cn.imtianx.ipcdemo;
 interface IAddAidlInterface {
 //计算两个数的和
 int add(int num1 ,int num2);
 }

编译后会自动生成相应的 java 类,这里是在 build/generated/source/aidl/debug/包名/IAddAidlInterface.java ,它包含一个内部类:Stub,继承自 Binder,实现了我们定义的 AIDL 接口,IAddAidlInterface,是用于定义服务的 RPC 接口。

  1. 实现接口

    private IBinder mIBinder = new IAddAidlInterface.Stub(){
        @Override
        public int add(int num1, int num2) throws RemoteException {
         return num1+num2;
        }
    };
    
  2. 向客户端公开接口
    自定义服务,便于客户端调用。

    public class IAddService extends Service {
         public IAddService() {
         }
    
         @Override
         public IBinder onBind(Intent intent) {
             // TODO: Return the communication channel to the service.
             return mIBinder;
         }
         private IBinder mIBinder = new IAddAidlInterface.Stub(){
             @Override
             public int add(int num1, int num2) throws RemoteException {
                 return num1+num2;
             }
         };
    }
    

    具体的调用(部分代码):

    private IAddAidlInterface mIAddAidlInterface;
     private ServiceConnection mConnection = new ServiceConnection() {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             //获取远程服务对象
             mIAddAidlInterface = IAddAidlInterface.Stub.asInterface(service);
         }
    
         @Override
         public void onServiceDisconnected(ComponentName name) {
             mIAddAidlInterface = null;
         }
     };
    
     //绑定服务
     Intent intent = new Intent(AIDLActivity.this,IAddService.class);
     intent.setAction(IAddService.class.getName());
     bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
    
     //调用远程服务方法
     int result  = mIAddAidlInterface.add(num1,num2);
    
     //解绑服务
     @Override
     protected void onDestroy() {
         super.onDestroy();
         unbindService(mConnection);
     }
    

    上述是一个简单的 AIDL 的使用,通过调用远程服务获取 计算结果。

接下来,分析下as 建立aidl文件编译后自动生成的java 类:

package cn.imtianx.ipcdemo;
public interface IAddAidlInterface extends android.os.IInterface
{
    public static abstract class Stub extends android.os.Binder implements cn.imtianx.ipcdemo.IAddAidlInterface
    {
        //binder 的唯一标示,一般是binder的类名
        private static final java.lang.String DESCRIPTOR = "cn.imtianx.ipcdemo.IAddAidlInterface";
        /** Construct the stub at attach it to the interface. */
        public Stub()
        {
            this.attachInterface (this, DESCRIPTOR);
        }

        /**
         * 用于将服务器端的binder 转换成客户端所需要的AIDL 的接口类型
         * @param obj
         * @return 如果客户端和服务器端在同一进程,则返回服务端的stub对象,否则返回 Stub.proxy对象
        */
        public static cn.imtianx.ipcdemo.IAddAidlInterface asInterface (android.os.IBinder obj)
        {
            if ( (obj == null) )
            {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface (DESCRIPTOR);
            if ( ( (iin != null) && (iin instanceof cn.imtianx.ipcdemo.IAddAidlInterface) ) )
            {
                return ( (cn.imtianx.ipcdemo.IAddAidlInterface) iin);
            }
            return new cn.imtianx.ipcdemo.IAddAidlInterface.Stub.Proxy (obj);
        }
        /**
         * 返回当前binder对象
         * @return
         */
        @Override public android.os.IBinder asBinder()
        {
            return this;
        }
        /**
        * 运行在服务端的Binder线程池中
        * @param code 确定请求的方法
        * @param data 获取目标方法所需参数
        * @param reply 写入返回值
        * @param flags
        * @return 若返回false,则为失败,可以此做权限验证
        * @throws android.os.RemoteException
        */
        @Override public boolean onTransact (int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
        {
            switch (code)
            {
            case INTERFACE_TRANSACTION:
            {
                reply.writeString (DESCRIPTOR);
                return true;
            }

            case TRANSACTION_add:
            {
                data.enforceInterface (DESCRIPTOR);
                int _arg0;
                _arg0 = data.readInt();
                int _arg1;
                _arg1 = data.readInt();
                int _result = this.add (_arg0, _arg1);
                reply.writeNoException();
                reply.writeInt (_result);
                return true;
            }
            }
            return super.onTransact (code, data, reply, flags);
        }
        //代理对象
        private static class Proxy implements cn.imtianx.ipcdemo.IAddAidlInterface
        {
            private android.os.IBinder mRemote;
            Proxy (android.os.IBinder remote)
            {
                mRemote = remote;
            }
            @Override public android.os.IBinder asBinder()
            {
                return mRemote;
            }
            public java.lang.String getInterfaceDescriptor()
            {
                return DESCRIPTOR;
            }
            @Override public int add (int num1, int num2) throws android.os.RemoteException
            {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken (DESCRIPTOR);
                    _data.writeInt (num1);
                    _data.writeInt (num2);
                    mRemote.transact (Stub.TRANSACTION_add, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                }
                finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        /**
        * 方法标示符
        * 格式:TRANSACTION_方法名 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 
        *       i);其中i按方法数自增
        */
        static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);

    }
    public int add (int num1, int num2) throws android.os.RemoteException;
}

更多AIDL 的资料,可参见官方文档,对于Socket、ContentProvider等方式,之前接触过,这里不做介绍。



   转载规则(或关注公众号:imtianx)


《android IPC 机制及进程通信》imtianx 采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。
 上一篇
Android View 的事件体系 Android View 的事件体系
本文为读书笔记: 《Android 开发艺术探索 》——第三章 View 的事件体系 android 系统虽然提供了很多基本的控件,如Button、TextView等,但是很多时候系统提供的view不能满足我们的需求,此时就需
下一篇 
android  多渠道打包 android 多渠道打包
这里介绍使用友盟进行多渠道打包,参考慕课视屏 一、 配置环境使用 gradle 添加依赖: //友盟统计 compile 'com.umeng.analytics:analytics:latest.integration'
  目录