美文网首页
Android进程间的通信

Android进程间的通信

作者: 编程的猫 | 来源:发表于2021-08-03 20:59 被阅读0次

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

相关文章

网友评论

      本文标题:Android进程间的通信

      本文链接:https://www.haomeiwen.com/subject/nrejvltx.html