Android中常见的IPC通信方式

本文详细介绍了Android中常见的四种IPC通信方式:Bundle、共享文件、Messenger和AIDL,重点解析了AIDL的使用步骤、安全问题以及注意事项。通过AIDL服务端和客户端的示例,展示了如何实现跨进程通信。同时,提到了binder连接池的概念,用于优化多个Service的管理。

如何在一个Android项目中使用多个进程

在AndroidManifest中配置使用android:process属性,因为这个属性只能在AndroidManifest中使用,所以跨进程只能作用于四大组件
这个属性的使用,有以下几种特点

  1. 如果没有配置 android:process这个属性,默认为项目的包名
  2. 这个属性可以配置 :remote 或者 包名.remote, 使用冒号这种方式,创建的是这个应用的私有进程,此进程不能与其他 应用 通过ShareId的方式跑在同一个进程中
  3. 因为使用android:process虽然新开了一个进程,但是还是之前的app,所以UID是一样的

可用 adb shell ps | grep 项目名 看PID和UID
USER PID PPID VSIZE RSS WCHAN PC NAME
u0_a145 10608 134 292960 26816 ffffffff 4019ca70 S com.ex.thre
u0_a146 10755 134 302420 32152 ffffffff 4019ca70 S com.ex.two

IPC(跨进程通信方式)

1. Bundle

通过Intent传递一个带有数据的Bundle来来实现跨进程通信,bundle的本质是一个parcelable对象

Intent intent = new Intent()
intent.putExtra()

2. 使用共享文件

比如A进程将数据存放在一个共有的目录下的文件中,然后让B进程去读,这样就实现了跨进程通信。但是两个进程共同去读写文件,会产生多进程安全问题,所以这种方式最好不要再并发量大的场景下使用。

还需要强调的一点是这个共享的文件不能是SharePrefernces文件,因为SP文件在内存中有一份缓存,所以多进程共同读写非常不可靠,容易造成数据丢失。

3.Messenger

Messenger封装了AIDL,可以让我们很容易的进行进程通信,但是使用Messenger一次只能处理一个请求即串行的处理,所以不用考虑多线程同步问题。

  1. 当不要求服务端响应时
    在这里插入图片描述
  2. 如果需要服务端向客户端的回复一些数据
    在这种场景下,需要让客户端也变为一个服务端,即在客户端用Handle新初始化一个Messenger用于处理服务端回复的数据。在发送请求时通过Message的replyTo字段将客户端这边的Messenger传递给服务端。
    在这里插入图片描述

4. AIDL

AIDL服务端
第一步 创建AIDL文件

要想使用AIDL,首先需要创建AIDL文件,AIDL分为两种,一种是声明AIDAIDL服务端提供给AIDL客户端的接口,我们将它成为接口AIDL文件,另一种是声明接口AIDL文件中使用到的Parcelable对象,即Parcelable对象声明文件

具体的AIDL文件规范如下:

  1. 并不是所有的类型AIDL文件都支持,AIDL文件只支持如下几种类型
类型是否需要显示import
八大基本类型,比如int,float,long等不需要
String或者CharSequence不需要
List,只支持ArrayList,要求List中的每一个元素都是AIDL文件支持的类型不需要
Map,只支持HashMap,要求Map中的每一个元素都是AIDL文件支持的类型不需要
实现了Parcelable接口的对象不但需要显示声明,还需要单独的AIDL文件使用parcelable关键字去声明这个对象,要求这个声明文件的全限定名和实际的Java类型保持一致
AIDL接口需要显示import
  1. AIDL中除了基本数据类型和AIDL类型,一律需要在类型前声明 in, out, 或inout,不能一律的使用inout,因为这个在底层是由开销的
类型前声明意义
in表示数据只能由客户端流向服务端,服务端会获取到客户端完整的数据,但客户端不会同步服务端你对该对象的修改,不写的话,默认的 tag 就是 in
out表示数据只能由服务端流向客户端,从服务端端接受该对象不为空,但字段内容为空,服务端修改对象后,binder 远程调用返回后,客户端会收到修改后的对象
inout则表示数据可在服务端与客户端之间双向流通
  1. AIDL接口中不像传统接口,可以定义静态场景,它只能定义方法
第二步 写AIDL服务

写一个服务将AIDL文件生成的binder对象, 返回给绑定这个服务的客户端
上一步写的AIDL文件会在编译阶段,自动帮助我们生成一个含有binder的类
在这里插入图片描述
自己创建一个Service类将该binder对象返回给客户端即可
在这里插入图片描述

AIDL客户端

通过ServiceConnection绑定AIDL服务获得binder对象即可调用AIDL服务端的接口
在这里插入图片描述

AIDL安全问题

如果创建了一个AIDL服务,任何进程都可以bind并并且调用AIDL接口是很不安全的,所以要对连接AIDL接口的客户端做限制
常用的显示方式有两种

  1. 自定义权限
    在这里插入图片描述
    可以在onbind方法进行这种权限判断

  2. 判断binder的身份
    可以在AIDL文件生成的onTransact方法中通过getCallUid方法得知调用者的UID,再通过packageManager.getPackagesForUid()得到调用者的包名再加以判断

onway关键字

我们知道通过binder去跨进程通信,是会阻塞客户端当前线程的,如果在AIDL定义的接口中加了oneway关键字,那么该接口的调用将会变成异步的,不会阻塞当前线程。oneway关键字只适用于无返回值的接口
加了oneway关键字后AIDL文件与之前有什么不同
在这里插入图片描述
只是transact方法的flag从0 变为了 1

客户端给服务端注册回调无法注销

一般我们设置回调都会使用一个list去保存回调,注销时再将回调从list中删除,但是AIDL服务端收到的参数是反序列化过来的,所以注册的对象会想要注销的绝对不是一个对象,所以无法注销, 可用RemoteCallBackList解决这个问题
在这里插入图片描述
虽然不能以回调对象本身作为注销的依据,但是binder对象是同一个
还有就是虽然它看起来像个List但其实本质并不是,所以遍历时一定要注意要用以下的这种方式
在这里插入图片描述

监控AIDL服务端的消失

如果AIDL服务端被杀了,客户端有两种方式可以感应到

  1. 注册死亡通知
    通过IBinder对象注册
    在这里插入图片描述
    当服务端死亡时, binderDied会在binder线程池中被回调
  2. ServerConnetion的回调
    在这里插入图片描述
    当服务端死亡时, serviceConnection会在onServiceDisconnected方法会在主线程中回调
binder连接池

binder连接池是一种思想,并不像线程池一样有封装好的类,需要自己实现
binder连接池可以解决一个AIDL服务端进程写了十几个Service的问题
大致思想如下图所示
在这里插入图片描述

什么是binder

是Android中的一个类,实现了IBinder接口,是Android中的一种跨进程通信的一种方式
下面我们通过AIDL来通知binder是工作过程
首先我们要创建三个文件

  1. Book.java ------- aidl请求接口中用到的parcelable对象
  2. IBookInterface.aidl ------- aidl请求接口
  3. Book.aidl ------- 声明Book.java对象
    在这里插入图片描述

Book.java

package com.example.studyapplication;

import android.os.Parcel;
import android.os.Parcelable;

public class Book implements Parcelable {
    private String name;
    private int price;

    protected Book(Parcel in) {
        name = in.readString();
        price = in.readInt();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(price);
    }
}

IBookInterface.aidl

// IBooklInterface.aidl
package com.example.studyapplication;

// Declare any non-default types here with import statements
import com.example.studyapplication.Book;

interface IBooklInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

    List<Book> getBookList();
    void addBook(in Book book);
}

Book.aidl

// Book.aidl
package com.example.studyapplication;
// Declare any non-default types here with import statements
parcelable Book;

编译完程序后,你会发现在generated目录下生成了一个和aidl接口同名的java文件
在这里插入图片描述
该文件的分析如下

/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.example.studyapplication;
public interface IBooklInterface extends android.os.IInterface {
	  /** Default implementation for IBooklInterface. */
	   public static class Default implements com.example.studyapplication.IBooklInterface  {
	    /**
	         * Demonstrates some basic types that you can use as parameters
	         * and return values in AIDL.
	         */
	    @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
	    {
	    }
	    @Override public java.util.List<com.example.studyapplication.Book> getBookList() throws android.os.RemoteException
	    {
	      return null;
	    }
	    @Override public void addBook(com.example.studyapplication.Book book) throws android.os.RemoteException
	    {
	    }
	    @Override
	    public android.os.IBinder asBinder() {
	      return null;
	    }
  }


  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.example.studyapplication.IBooklInterface { 
		   //DESCRIPTOR(描述符)---- Binder的唯一标识, 一般为次binder接口的全限定名
		   private static final java.lang.String DESCRIPTOR = "com.example.studyapplication.IBooklInterface";
		   /** Construct the stub at attach it to the interface. */
		   public Stub() {  this.attachInterface(this, DESCRIPTOR); }
		   /**
		    * Cast an IBinder object into an com.example.studyapplication.IBooklInterface interface,
		    * generating a proxy if needed.
		    * 将服务端的binder对象强制转换为客户端的AIDL接口对象
		    * 如果客户端和服务端在不同进程,会生成代理,反之不会
		    */
		   public static com.example.studyapplication.IBooklInterface asInterface(android.os.IBinder obj)  {
				     if ((obj==null)) {
				       return null;
				     }
				     android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
				     if (((iin!=null)&&(iin instanceof com.example.studyapplication.IBooklInterface))) {
				       return ((com.example.studyapplication.IBooklInterface)iin);
				     }
				     return new com.example.studyapplication.IBooklInterface.Stub.Proxy(obj);
		   }
		
		   //返回当前的binder对象
		   @Override public android.os.IBinder asBinder()  { return this; }
		
		   /*
		   这个方法运行在服务端的线程池中,code是方法Id,AidL接口定义的每个方法都有一个id,data是要调用的方法的参数,如果要调用的方法有返回值会写入reply中
		   如果客户端请求失败onTransact方法会返回false
		   */
		   @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_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;
			       }
			       case TRANSACTION_getBookList:
			       {
			         data.enforceInterface(descriptor);
			         java.util.List<com.example.studyapplication.Book> _result = this.getBookList();
			         reply.writeNoException();
			         reply.writeTypedList(_result);
			         return true;
			       }
			       case TRANSACTION_addBook:
			       {
			         data.enforceInterface(descriptor);
			         com.example.studyapplication.Book _arg0;
			         if ((0!=data.readInt())) {
			           _arg0 = com.example.studyapplication.Book.CREATOR.createFromParcel(data);
			         }
			         else {
			           _arg0 = null;
			         }
			         this.addBook(_arg0);
			         reply.writeNoException();
			         return true;
			       }
			       default:
			       {
			         return super.onTransact(code, data, reply, flags);
			       }
		     }
           }
    private static class Proxy implements com.example.studyapplication.IBooklInterface {
      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; }
      /**
           * Demonstrates some basic types that you can use as parameters
           * and return values in 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();
        }
      }

      /*
      这个方法运行在客户端中,大致的调用逻辑为:
      创建两个Parcel对象_data,和_reply,_负责写入请求方法的参数,_reply负责写入该方法的返回值
      紧接着RPC调用服务端的transact方法,此时此方法的调用进程挂起,等待服务端的返回结果
      */
      @Override public java.util.List<com.example.studyapplication.Book> getBookList() throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        java.util.List<com.example.studyapplication.Book> _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          boolean _status = mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().getBookList();
          }
          _reply.readException();
          _result = _reply.createTypedArrayList(com.example.studyapplication.Book.CREATOR);
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }

	      /*
	      此方法的调用原理和上面的一致
	      */
	      @Override public void addBook(com.example.studyapplication.Book book) 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 ((book!=null)) {
	            _data.writeInt(1);
	            book.writeToParcel(_data, 0);
	          }
	          else {
	            _data.writeInt(0);
	          }
	          boolean _status = mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
	          if (!_status && getDefaultImpl() != null) {
	            getDefaultImpl().addBook(book);
	            return;
	          }
	          _reply.readException();
	        }
	        finally {
	          _reply.recycle();
	          _data.recycle();
	        }
	      }
      	public static com.example.studyapplication.IBooklInterface sDefaultImpl;
    }


    static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    //getBookList方法的ID
    static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    //addBook方法的ID
    static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
    public static boolean setDefaultImpl(com.example.studyapplication.IBooklInterface impl) {
      // Only one user of this interface can use this function
      // at a time. This is a heuristic to detect if two different
      // users in the same process use this function.
      if (Stub.Proxy.sDefaultImpl != null) {
        throw new IllegalStateException("setDefaultImpl() called twice");
      }
      if (impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.example.studyapplication.IBooklInterface getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  /**
       * Demonstrates some basic types that you can use as parameters
       * and return values in AIDL.
       */
  public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
  public java.util.List<com.example.studyapplication.Book> getBookList() throws android.os.RemoteException;
  public void addBook(com.example.studyapplication.Book book) throws android.os.RemoteException;
}

将上面的流程话为流程图
在这里插入图片描述
由于client调用aidl请求接口会挂起当前线程,所以不建议在主线程中调用

其实我们发现,aidl文件的作用仅仅是帮助我们自动生成binder对象,其实也可以我们自己实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值