Android进程间的通信

Android进程间通信的方式有很多种,例如:文件通信、共享内存、Binder、Socket、管道、信号等等
作为Android开发人员要想深入的了解Android的通信机制,那就不得不了解Binder。在Android中Binder又有多种表现形式,例如:ContentProvider、Messenger、AIDL、Token等,今天的重点是AIDL。AIDL也是Android C\S架构中做常用的进程间通信方式。

内容概要:

  1. 应用间使用AIDL通信
  2. 同一个应用中多进程间的AIDL通信
  3. AIDL原理分析(Android的AMS与客户端使用AIDL通信的应用场景)
  4. 遇到的异常

在看本文前,对于AIDL传递数据类型不熟悉的同学可以先看看这两篇文章(两篇文章都差不多)
https://www.cnblogs.com/0616--ataozhijia/p/4952441.html
https://www.cnblogs.com/android-er/p/5477071.html

应用间使用AIDL通信

在示例中来认识AIDL的工作原理:

  1. 新建一个Android工程
  2. 新建一个本地服务LocalService和一个远程服务RemoteService
  3. 本地服务与远程服务间通过AIDL通信

先写一个使用AIDL在两个不同的应用之间通信的demo:

  1. 先创建两个Android Module,分别为PluginApp(作为服务端)和ClientApp(作为客户端)
  2. 在PluginApp中新建RemoteService,然后再新建aidl文件IAidlInterface.aidl,并且在同一个目录下新建自定义类型User和User.aidl类型申明文件
  3. 在ClientApp中将PluginApp下的aidl目录拷贝到ClientApp下

目录结构如下:

PluginApp.png

RemoteApp.png

示例代码链接: https://pan.baidu.com/s/185Ocv5NgMDlAyYhT7Xu5dg 密码: kn64

同一个应用中多进程间的AIDL通信

同一个应用中的多进程通信实际上也是采用AIDL的方式,跟上边Demo的使用方式差不多

  • 应用开启多进程的方式

很简单,在AndroidManifest.xml中注册Service、Activity、Receiver、ContentProvider时指定android:process属性。

<service
    android:name=".RemoteService"
    android:process=":remote">
</service>

<activity
    android:name=".RemoteActivity"
    android:process="com.chenxf.ipc.remote">
</activity>

有两种声明方式,一个加冒号,一个完整的名字,区别如下:

  • :remote: 以冒号开头是一种简写,系统会在当前进程名前附件当前包名,完整的进程名为:com.chenxf.ipc:remote,同时以冒号开头的进程属于当前应用的私有进程,其它应用的组件不能和它跑在同一进程。

  • com.chenxf.ipc.remote:这是完整的命名方式,不会附加包名,其它应用如果和该进程的ShareUID、签名相同,则可以和它跑在同一个进程,实现数据共享。(一般极少这样用,除非是同一公司开发的app,且2个app关联很大,才会签名也一样)

  • 多进程的优缺点

优点

  • 增加内存。
  • 业务隔离。一些子业务,放子进程,如果崩溃了,不会影响主app退出。

缺点

  • 静态成员和单例模式失效
  • 线程同步机制失效
  • SharedPreferences 可靠性降低
  • Application 被多次创建

1, 2 很容易理解,每个应用或进程分配独立的虚拟机,不同的虚拟机自然占有不同的内存地址空间。可以认为,每个进程,都有独立的静态成员和单例模式的对象,所以进程之间,千万不能通过这些通信,因为它们属于不同的时空喔。
3嘛,如果一个读,一个写,还好,要是同时去写,就可能出问题了,A进程刚写1,B进程又写2,A一脸懵逼,为啥变成2了,你说可靠不可靠。
4很重要,指的是,Application会被重复创建。比如,如果有3个进程,Application会初始化3次。如果希望不同进程做不同的初始化,则可以参考如下的实现:

package com.chenxf.processtest;

import android.app.ActivityManager;
import android.app.Application;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;

public class MyApplication extends Application {
    private static final String TAG = "MyApplication";
    private static final String DOWNLOADER_PROCESS = ":downloader";
    private static final String PLUGIN_PROCESS = ":plugin";

    private BaseApplication mProxy;

    @Override
    public void onCreate() {
        super.onCreate();
        String processName = getMyProcessName();
        Log.i(TAG, "onCreate " + processName);
        initProxyApplication(processName);
    }

    private void initProxyApplication(String processName) {
        String mPackageName = getPackageName();

        if (TextUtils.equals(mPackageName, processName)) {
            //主进程
            Log.i(TAG, "init process " + mPackageName);
            mProxy = new MainApplication(processName);
        } else if (TextUtils.equals(processName, mPackageName + PLUGIN_PROCESS)) {
            //插件安装进程
            Log.i(TAG, "init process " + PLUGIN_PROCESS);
            mProxy = new PluginApplication(processName);
        } else if (TextUtils.equals(processName, mPackageName + DOWNLOADER_PROCESS)) {
            //下载进程
            Log.i(TAG, "init process " + DOWNLOADER_PROCESS);
            mProxy = new DownloaderApplication(processName);
        } else {
            mProxy = new BaseApplication(processName);
        }
    }

    /**
     * 获取进程的名称
     *
     * @return
     */
    public String getMyProcessName() {
        if (mProxy != null) {
            return mProxy.getProcessName();
        } else {
            return initCurrentProcessName(this);
        }
    }

    private String initCurrentProcessName(Context context) {
        int pid = android.os.Process.myPid();
        ActivityManager manager =
                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        for (ActivityManager.RunningAppProcessInfo process : manager.getRunningAppProcesses()) {
            if (process.pid == pid) {
                return process.processName;
            }
        }
        return null;
    }
}

AIDL原理分析

先来看一下.aidl文件,这个文件是Android studio生成的:

// 这是本地IPC存根类,集成Binder类,实现本地IAidlInterface接口
  public static abstract class Stub extends android.os.Binder implements com.pluginapp.IAidlInterface
  {
    private static final java.lang.String DESCRIPTOR = "com.pluginapp.IAidlInterface";
    // 在关联接口时构造存根
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    // 将IBinder对象强制转换为com.pluginapp.IAidlInterface接口,
    // 如果需要,生成代理。
    public static com.pluginapp.IAidlInterface asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.pluginapp.IAidlInterface))) {
        return ((com.pluginapp.IAidlInterface)iin);
      }
      return new com.pluginapp.IAidlInterface.Stub.Proxy(obj);
    }
    @Override public android.os.IBinder asBinder()
    {
      return this;
    }
    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_setUser:
        {
          data.enforceInterface(descriptor);
          com.pluginapp.User _arg0;
          if ((0!=data.readInt())) {
            _arg0 = com.pluginapp.User.CREATOR.createFromParcel(data);
          }
          else {
            _arg0 = null;
          }
          this.setUser(_arg0);
          reply.writeNoException();
          return true;
        }
        case TRANSACTION_getUser:
        {
          data.enforceInterface(descriptor);
          com.pluginapp.User _result = this.getUser();
          reply.writeNoException();
          if ((_result!=null)) {
            reply.writeInt(1);
            _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
          }
          else {
            reply.writeInt(0);
          }
          return true;
        }
        
        case TRANSACTION_basicTypes:
        {
          data.enforceInterface(descriptor);
          int _arg0;
          _arg0 = data.readInt();
          long _arg1;
          _arg1 = data.readLong();
          boolean _arg2;
          _arg2 = (0!=data.readInt());
          float _arg3;
          _arg3 = data.readFloat();
          double _arg4;
          _arg4 = data.readDouble();
          java.lang.String _arg5;
          _arg5 = data.readString();
          this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
          reply.writeNoException();
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.pluginapp.IAidlInterface
    {
      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 void setUser(com.pluginapp.User user) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          if ((user!=null)) {
            _data.writeInt(1);
            user.writeToParcel(_data, 0);
          }
          else {
            _data.writeInt(0);
          }
          boolean _status = mRemote.transact(Stub.TRANSACTION_setUser, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().setUser(user);
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      @Override public com.pluginapp.User getUser() throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        com.pluginapp.User _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          boolean _status = mRemote.transact(Stub.TRANSACTION_getUser, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().getUser();
          }
          _reply.readException();
          if ((0!=_reply.readInt())) {
            _result = com.pluginapp.User.CREATOR.createFromParcel(_reply);
          }
          else {
            _result = null;
          }
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }

      // AIDL支持的参数类型
      @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeInt(anInt);
          _data.writeLong(aLong);
          _data.writeInt(((aBoolean)?(1):(0)));
          _data.writeFloat(aFloat);
          _data.writeDouble(aDouble);
          _data.writeString(aString);
          boolean _status = mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().basicTypes(anInt, aLong, aBoolean, aFloat, aDouble, aString);
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      public static com.pluginapp.IAidlInterface sDefaultImpl;
    }

最重要的是四个部分,Proxy,Stub和asInterface,还有连接远程服务的接口实现ServiceConnection。
首先看看Proxy的实现,首先是将远程的Binder作为参数传入进来,远程Binder传入是在绑定链接远程服务的时候的IBinder对象分两步:

  • 第一步: 链接远程服务,通过回调中获取IBinder对象
private IAidlInterface mIAidlInterface;
    /**
     * 链接服务的回调
     */
    private final ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            LogUtil.logE(true, TAG + "远程服务链接上了");
            mIAidlInterface = IAidlInterface.Stub.asInterface(service);

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            LogUtil.logE(true, TAG + "远程服务断开了");

        }
    };
  • 第二部:在获取本地服务接口对象的时候,将链接服务的回调接口中的IBinder作为参数传递给远程Binder服务在本地的代理对象Proxy,IAidlInterface.Stub.asInterface(service);
 public static com.pluginapp.IAidlInterface asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      // 1. 根据AIDL唯一描述符查询本地的AIDL接口
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
     // 2. 判断查询到的AIDL服务接口是不是本地进程的服务
      if (((iin!=null)&&(iin instanceof com.pluginapp.IAidlInterface))) {
        // 3. 如果是当前进程的本地服务,那么就返回服务本身
        return ((com.pluginapp.IAidlInterface)iin);
      }
     // 4. 如果不是当前进程的本地服务,而是远程服务,那么就返回远程服务的本地代理对象
      return new com.pluginapp.IAidlInterface.Stub.Proxy(obj);
    }

Stub实际上是远程服务在本地进程的存根,是远程服务在本地服务操作远程服务的真正代理者,继承自Binder,而Binder实现IBinder接口,在Binder IPC通信中只要是实现了IBinder接口的对象都具备跨进程通信的能力。Proxy是本地操作远程服务的接口实现,持有外部传入的远程服务的mRemote对象(IBinder对象),通过mRemote对象调用transact方法,写入参数,阻塞等待读取结果。

boolean _status = mRemote.transact(Stub.TRANSACTION_setUser, _data, _reply, 0);
_reply.readException();
if ((0!=_reply.readInt())) {
      _result = com.pluginapp.User.CREATOR.createFromParcel(_reply);
  }

transact方法的实现在远程服务端,在远程服务端实现具体的逻辑,回调Stub中的onTransact方法,读取参数,写入结果返回结果。从这个过程来看,AIDL也是一个同步阻塞的耗时操作。

其实在Android系统中使用AIDL实现进程间通信的场景很多,例如:AMS与客户端进程的通信就是使用的AIDL的方式

遇到的异常

在使用自定义类型的时候可能会遇到类型包找不到的异常,如下:


Error.png

解决方式:在Module Gradle下添加如下代码,申明资源代码的位置,然后Sync Project即可:

 sourceSets{
        main{
            jniLibs.srcDir(['libs'])

            manifest.srcFile 'src/main/AndroidManifest.xml'
            java.srcDirs = ['src/main/java', 'src/main/aidl']
            resources.srcDirs = ['src/main/java', 'src/main/aidl']
            aidl.srcDirs = ['src/main/aidl','src/main/java/com/pluginapp/bean']
            res.srcDirs = ['src/main/res']
            assets.srcDirs = ['src/main/assets']
        }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,247评论 6 543
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,520评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,362评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,805评论 1 317
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,541评论 6 412
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,896评论 1 328
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,887评论 3 447
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,062评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,608评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,356评论 3 358
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,555评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,077评论 5 364
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,769评论 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,175评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,489评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,289评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,516评论 2 379

推荐阅读更多精彩内容