Android的IPC机制(上)

Android的IPC机制(上)

1.1 IPC概念简介

  • IPC(Inter-Process Communication),进程间通信或者跨进程通信。
  • 线程:CPU调度的最小单元,是一种有限的系统资源。
  • 进程:一个执行单元,比如一个程序或者一个应用。
  • 一个进程可以包含一个(主线程)或多个线程,Android中主线程也叫UI线程,只能在UI线程中操作界面元素。耗时任务在主线程可能会产生ANR(Application Not Responding)错误。

1.2 Android中的多进程模式

  • Android中进程间通讯方式:BinderSocket
  • 应用可以通过多进程获取多份内存空间。

1.2.1 开启多进程模式

  • Android使用多进程的方式:在Menifest中为四大组件指定android:process属性。我们无法给一个线程或者一个实体类指定其运行时所在的进程。还存在一种非常规多进程方法:通过JNI在native层fork一个新进程。

  • 举例:

    <activity
              android:name=".MainActivity"
              android:launchMode="standard">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    
    <activity
              android:name=".SecondActivity"
              android:launchMode="standard"
              android:process=":remote"/>
    
    <activity 
              android:name=".ThirdActivity"
              android:launchMode="standard"
              android:process="com.virtual.learn101101.remote"/>
    

    多进程adb查看

    • MainActivity未指定process属性,将运行在默认进程中,进程名是包名。
    • SecondActivity指定process属性:android:process=":remote",其中冒号的含义是在当前的进程名前加上当前的包名。并且以冒号开头的进程属于当前应用的私有进程,其他应用的组件不可以和它运行在一个进程中。
    • ThirdActivity指定process属性:android:process="com.virtual.learn101101.remote",完整的命名方式。
  • Android系统会为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据。两个应用运行在同一个进程中的条件:有相同的ShareUID和相同的签名。拥有相同的ShareUID和相同的签名的两个应用,如果不运行在同一个进程:它们可以互相访问对方的私有数据,比如:data目录、组件信息等;如果运行在同一个进程:除上述私有数据外还可以共享内存数据。

1.2.2 多进程模式的运行机制

  • 多进程模式可能会出现一个问题:设置一个全局的Int类型的变量初始值为1,在MainActivity中修改其为2,但是之后在SecondActivity中读取该变量时读取到的值为1。

  • 产生上述问题的原因是Android为每一个应用分配了一个独立的虚拟机(每个进程都分配一个独立的虚拟机),不同的虚拟机在内存分配上有各自不同的虚拟空间,这会导致在对同一个类的对象会产生多份副本,不同的虚拟机中保留各自的副本。因此,运行在不同进程的四大组件,只要它们之间通过内存共享数据时会失败,因而我们需要通过一些中间层来共享数据。

  • 一般情况下多进程造成的问题

    • 1.静态成员和单例模式完全失效。
    • **2.线程同步机制完全失效。**不是一块内存,不同进程锁的不是同一个对象。
    • 3.SharedPreference可靠性降低。SP底层是通过读/写XML文件实现,并发会出现问题。
    • **4.Application多次创建。**运行在同一进程的组件是属于同一虚拟机和同一Application的。
  • 一个应用间的多进程相当于不同应用采用SharedUID模式。

  • 补充:Android 获取进程名的工具类MyUtil.java

    public class MyUtil {
        public static String getProcessName(Context context, int pid) {
            ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            List<ActivityManager.RunningAppProcessInfo> runningAppProcessInfos = activityManager.getRunningAppProcesses();
            if (runningAppProcessInfos == null) {
                return null;
            }
            for (ActivityManager.RunningAppProcessInfo runningAppProcessInfo : runningAppProcessInfos) {
                if (runningAppProcessInfo.pid == pid) {
                    return runningAppProcessInfo.processName;
                }
            }
            return null;
        }
    }
    
    //使用方法
    MyUtil.getProcessName(getApplicationContext(), Process.myPid());
    

1.3 IPC基础

  • 理解Android的IPC机制,需要理解Serializable接口、Parcelable接口和Binder。Serializable接口和Parcelable接口用以完成对象序列化。我们通过Intent和Binder传输的数据对象需要实现序列化接口;同样对象持久化到存储设备或通过网络传输也需要经过序列化。

1.3.1 Serializable接口

  • Serializable接口是Java提供的序列化接口,是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable接口实现序列化:我们在类中声明serialVersionUID标识后便可自动完成默认的序列化过程(非必须声明):

    static final long serialVersionUID = 123456789L;
    
    • serialVersionUID作用:只有序列化后的数据中的serialVersionUID和当前类中的serialVersionUID一致才能正常反序列化。
    • 正常我们应手动指定serialVersionUID值,比如1L;也可以使用IDE根据当前类结构去生成hash值。
    • 如果我们不指定serialVersionUID值,反序列化时当前类有所改变(添加或删除某些成员变量),系统会重新计算当前类的hash并赋给serialVersionUID,这会导致反序列化失败。
    • 修改类名、修改成员变量的类型也会使反序列化失败。
  • 序列化和反序列化

    //序列化
    Person person = new Person("Tom",20);
    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("example.txt"));
    out.writeObject(person);
    out.close();
    
    //反序列化
    ObjectInputStream in = new ObjectInputStream(new FileInputStream("example.txt"));
    Person person = (Person)in.readObject();
    in.close();
    
    • 静态成员变量属于类,不属于对象,不会参与序列化过程。
    • 可以使用transient关键字标记不参与序列化过程的成员变量。
    • 可以重写系统默认的序列化和反序列化的过程。

1.3.2 Parcelable接口

  • Parcelable接口是Android提供的序列化接口。

  • 使用方法举例:

    public class Person implements Parcelable {
        public String pName;
        public int pAge;
        public boolean pState;
        public Info pInfo;
    
        public Person(String pName, int pAge, boolean pState, Info pInfo) {
            this.pName = pName;
            this.pAge = pAge;
            this.pState = pState;
            this.pInfo = pInfo;
        }
    
    
        private Person(Parcel in) {
            pName = in.readString();
            pAge = in.readInt();
            pState = in.readByte() != 0;
            pInfo = in.readParcelable(Thread.currentThread().getContextClassLoader());
        }
    
        public static final Creator<Person> CREATOR = new Creator<Person>() {
            @Override
            public Person createFromParcel(Parcel in) {
                return new Person(in);
            }
    
            @Override
            public Person[] newArray(int size) {
                return new Person[size];
            }
        };
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(pName);
            dest.writeInt(pAge);
            dest.writeByte((byte) (pState ? 1 : 0));
            dest.writeParcelable(pInfo, 0);
        }
    }
    
  • 序列化功能writeToParcel方法实现;反序列化功能CREATOR实现。

    方法 功能 标记位
    writeToParcel(Parcel dest, int flags) 将当前对象写入序列化结构中,其中flags标识有两种值: 0或者1。为1时标识当前对象需要作为返回值返回,不能立即释放资源。几平所有情况都为0。 PARCELABLE_ WRITE_RETURN_ VALUE
    describeContents 返回当前对象的内容描述。如果含有文件描述符,返回1,否则返回0。几平所有情况都为0。 CONTENTS FILE_ DESCRIPTOR
  • 例子中的pInfo是另一个可序列化对象,反序列化过程需要传递当前线程的上下文类的加载器:Thread.currentThread().getContextClassLoader()

  • Android系统中有许多类实现了Parcelabel接口,它们是可以直接序列化的,比如:Intent、Bundle、Bitmap等。,当Map和List里面的元素都是可序列化时,Map和List也可以序列化。

1.3.3 Serializable接口和Parcelable接口对比

  • Serializable接口是Java中的序列化接口,使用简单但开销大,序列化和反序列化过程需要大量的IO操作。
  • Parcelable接口是Android中序列化方式,更适合在Android平台上使用,虽然使用稍复杂,但是效率高。
  • 在将对象序列化到存储设备或通过网络传输的时候,通过Parcelable接口实现会复杂(但可以),推荐Serializable接口实现。

1.3.4 Binder

  • 多个角度看Binder:

    • 直观上,Binder是Android中的一个类,实现了IBinder接口。
    • IPC角度,Binder是Android中的一种跨进程通信方式,可以理解为一种虚拟物理设备,它的设备驱动是/dev/binder(在linux中没有)。
    • Android Framework角度,Binder是ServiceManager连接各种Manager(ActivityManager、WindowsManager,等等)和相应ManagerService的桥梁。
    • Android应用层角度,Binder是客户端和服务端进行通信的媒介。服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包含普通服务和基于AIDL的服务。
  • Android开发中,Binder主要用在Service中,包括AIDL和Messenger。

  • Binder的工作机制

    Binder工作机制

  • 通过AIDL文件实现Binder的示例

    • 我们继续使用上面实现Parcelable接口的Person实体,我们要在客户端提供两个方法:1.查看人员列表;2.添加新的成员。

    • 1.创建Person.aidl文件

      创建AIDL

      // Person.aidl
      package com.virtual.aidltest;
      
      //Person类在AIDL中的声明
      parcelable Person;
      
    • 2.创建IPersonManager.aidl。

      // IPersonManager.aidl
      package com.virtual.aidltest;
      /**
      * Person.java实体类要自己引入,注意不是Person.aidl
      */
      import com.virtual.aidltest.Person;
      
      //接口形式声明,包含两个方法
      interface IPersonManager {
          List<Person> getPersonList();
          void addPerson(in Person person);
      }
      
    • 3.创建成功后的目录结构:

    AIDL创建成功后目录结构

    • 4.在客户端使用该Binder:

      final List<Person> mPersonList = new ArrayList<>();
      //系统生成的IPersonManager.java中的内部类Stub就是Binder类。
      private final IPersonManager.Stub mBinder = new IPersonManager.Stub() {
          
          @Override
          public List<Person> getPersonList() throws RemoteException {
              synchronized (mPersonList) {
                  return mPersonList;
              }
          }
          
          @Override
          public void addPerson(Person person) throws RemoteException {
              synchronized (mPersonList) {
                  if (!mPersonList.contains(person)) {
                      mPersonList.add(person);
                  }
              }
          }
      };
      

      服务端将mBinder传给客户端使用即可。

  • 不通过AIDL文件实现Binder的示例:(参考AIDL生成的java文件)

    • 使用AIDL文件实现Binder是系统提供的简便方法。可以参考生成的文件不使用AIDL文件直接实现Binder。

    • 1.首先我们声明一个AIDL性质的接口:IPersonManager。

      /**
       * 声明一个AIDL性质的接口:继承IInterface接口即可。
       */
      public interface IPersonManager extends IInterface {
          /**
           * DESCRIPTOR:Binder的唯一标识,一般用当前Binder的类名表示。
           * TRANSACTION_XXXX:使用整形id标识方法,id标识用于在transact过程中区分客户端请求的是哪一个方法。
           */
          String DESCRIPTOR = "com.virtual.learn101101.IPersonManager";
          int TRANSACTION_getPersonList = IBinder.FIRST_CALL_TRANSACTION + 0;
          int TRANSACTION_addPerson = IBinder.FIRST_CALL_TRANSACTION + 1;
      
          /**
           * 抽象两个方法:
           * 1.获取人员列表。
           * 2.一个是往人员列表中添加新成员。
           */
          List<Person> getPersonList() throws RemoteException;
          void addPerson(Person person) throws RemoteException;
      }
      
    • 2.实现Binder类PersonManagerImpl(相当于系统生成的IPersonManager.java中的Stub内部类),和Binder类中的代理类Proxy:

      public class PersonManagerImpl extends Binder implements IPersonManager {
      
          private final List<Person> mPersonList;
      
          public PersonManagerImpl(List<Person> mPersonList, List<Person> personList) {
              /**
               *  attachInterface方法是将IInterface的实现与Binder相连。
               *  这里是将PersonManagerImpl与Binder关联。
               */
              this.attachInterface(this, DESCRIPTOR);
              this.mPersonList = mPersonList;
          }
      
          /**
           * asInterface方法用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象。
           * 本例子中所需的AIDL接口类型的对象为IPersonManager。
           * 转换是区分进程的:
           * 1.客户端和服务端位于同一进程:返回服务端的IPersonManager对象本身。
           * 2.客户端和服务端位于不同进程:返回经Proxy封装后的IPersonManager对象。
           */
          public static IPersonManager asInterface(IBinder obj) {
              //Binder不存在,返回null
              if ((obj == null)) {
                  return null;
              }
              /**
               * 请求本地接口。
               * 之前调用过attachInterface(this, DESCRIPTOR)将IPersonManager与Binder关联;
               * 通过queryLocalInterface(DESCRIPTOR)尝试将IPersonManager取回。
               */
              IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
              /**
               * 如果iin不为null且类型为IPersonManager;说明客户端和服务端位于同一进程,直接返回。
               */
              if (((null != iin) && (iin instanceof IPersonManager))) {
                  return ((IPersonManager) iin);
              }
              /**
               * 客户端和服务端位于不同进程,通过代理返回。
               */
              return new PersonManagerImpl.Proxy(obj);
          }
      
          /**
           * 返回当前Binder对象。
           */
          @Override
          public IBinder asBinder() {
              return this;
          }
      
          /**
           * 服务端onTransact
           */
          @Override
          protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
              switch (code) {
                  case INTERFACE_TRANSACTION: {
                      reply.writeString(DESCRIPTOR);
                      return true;
                  }
                  case TRANSACTION_getPersonList: {
                      data.enforceInterface(DESCRIPTOR);
                      List<Person> result = this.getPersonList();
                      reply.writeNoException();
                      reply.writeTypedList(result);
                      return true;
                  }
                  case TRANSACTION_addPerson: {
                      data.enforceInterface(DESCRIPTOR);
                      Person arg0;
                      if ((0 != data.readInt())) {
                          arg0 = Person.CREATOR.createFromParcel(data);
                      } else {
                          arg0 = null;
                      }
                      this.addPerson(arg0);
                      reply.writeNoException();
                      return true;
                  }
                  default:
              }
              return super.onTransact(code, data, reply, flags);
          }
      
      
          /**
           * 运行在客户端的代理
           */
          private static class Proxy implements IPersonManager {
              private IBinder mRemote;
      
              public Proxy(IBinder remote) {
                  mRemote = remote;
              }
      
              @Override
              public IBinder asBinder() {
                  return mRemote;
              }
      
              /**
               * 注意内部类这里要实现获取Binder的唯一标识的方法。
               * 外部类继承Binder,构造器调用attachInterface方法会自带getInterfaceDescriptor。
               * 内部类未继承Binder。
               */
              String getInterfaceDescriptor() {
                  return DESCRIPTOR;
              }
      
              /**
               * 运行在客户端的两个方法:
               * 创建输入型的Parcel对象data、reply;方法参数写入data(如果有)
               * 接着调用transact方法发起RPC(远程过程调用)请求,挂起当前线程;
               * 然后服务端onTransact方法调用;
               * RPC返回后,当前线程继续,并从reply中取出RPC过程的返回结果,再返回该结果。
               */
              @Override
              public List<Person> getPersonList() throws RemoteException {
                  Parcel data = Parcel.obtain();
                  Parcel reply = Parcel.obtain();
                  List<Person> result;
                  try {
                      data.writeInterfaceToken(DESCRIPTOR);
                      mRemote.transact(TRANSACTION_getPersonList, data, reply, 0);
                      reply.readException();
                      result = reply.createTypedArrayList(Person.CREATOR);
                  } finally {
                      reply.recycle();
                      data.recycle();
                  }
                  return result;
              }
      
              @Override
              public void addPerson(Person person) throws RemoteException {
                  Parcel data = Parcel.obtain();
                  Parcel reply = Parcel.obtain();
                  try {
                      data.writeInterfaceToken(DESCRIPTOR);
                      if (null != person) {
                          data.writeInt(1);
                          person.writeToParcel(data, 0);
                      } else {
                          data.writeInt(0);
                      }
                      mRemote.transact(TRANSACTION_addPerson, data, reply, 0);
                      reply.readException();
                  } finally {
                      reply.recycle();
                      data.recycle();
                  }
              }
          }
      
          /**
           * 服务端两种方法的实现
           */
          @Override
          public List<Person> getPersonList() throws RemoteException {
              synchronized (mPersonList) {
                  return mPersonList;
              }
          }
      
          @Override
          public void addPerson(Person person) throws RemoteException {
              synchronized (mPersonList) {
                  if (!mPersonList.contains(person)) {
                      mPersonList.add(person);
                  }
              }
          }
      
      }
      
  • linkToDeathunlinkToDeath

    • Binder运行在服务端,如果服务端进程异常终止,客户端到服务端的Binder连接断裂,会导致远程调用失败。如果不知道Binder连接断裂,挂起的客户端会受到影响。Binder提供了linkToDeath和unlinkToDeath这两个方法解决这个问题。通过linkToDeath可以设置一个死亡代理,当Binder连接断裂时会收到通知,接着可以重发连接请求恢复连接。

    • 声明DeathRecipient对象:(当binder死亡时,系统会回调binderDied方法)

      private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
          @Override
          public void binderDied() {
              if (null == mPersonManager) {
                  return;
              }
              mPersonManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
              mPersonManager = null;
              // TODO 重新绑定Service
          }
      };
      
    • 在客户端绑定远程服务成功后,给binder设置死亡代理。

      IPersonManager mService=IPersonManager.Stub.asInterface(mBinder);
      mBinder.linkToDeath(mDeathRecipient,0);
      
    • 通过Binder的方法isBinderAlive方法也可以判断Binder是否死亡。

发布了64 篇原创文章 · 获赞 65 · 访问量 8818

猜你喜欢

转载自blog.csdn.net/qq_33334951/article/details/102952004