【学习】Android的IPC机制(上)——基础知识

本文详细介绍了Android中的IPC(跨进程通信)机制,重点讲解了Binder的工作原理、静态成员和单例模式在多进程中的失效、Serializable与Parcelable接口的使用,以及AIDL(Android Interface Definition Language)的实现过程。通过AIDL,开发者可以定义接口以在不同进程间通信。同时,讨论了Binder的linkToDeath和unlinkToDeath方法,用于处理服务端进程异常时的连接恢复。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

IPC是什么?

IPC的含义为跨进程通信,指两个进程间进行数据交换的过程

进程:系统进行资源分配和调度的一个独立单位,一般指一个执行单元,在PC和移动设备上通常指一个程序或者一个应用
线程:调度和分派的基本单位,是一种有限的系统资源

进程和线程之间的关系:包含关系,一个进程可以包含多个线程,也可以只有一个线程,即主线程。在安卓中主线程也叫UI线程,在该线程里才能操作UI元素。

ANR(应用无响应):一个进程中需要执行大量耗时任务时,如果这些任务放在主线程中去执行就会造成界面无法响应,影响用户体验

IPC的使用场景

1.有些模块由于特殊原因需要运行在单独的进程中
2.加大一个应用可使用的内存
3.需要向其他应用获取数据

多进程模式

开启方法

给四大组件在AndroidMenifest指定android:process属性。我们无法给一个线程或者一个实体类指定其运行时所在的线程。非常规方法:通过JNI在native层去fork一个新的进程。默认进程名是包名。

eg: …process=:remote …process=xxx.remote

在设置进程名时":“和”."的区别,":"的含义是指要在当前的进程名前面附上当前的包名,而且是当前应用的私有进程,其他应用的组件不能和它跑在同一进程中,另一个则是一种完整的命名方式,不会附加包名信息,而且是全局进程,可以通过ShareUID的方式和它跑在同一进程中。 安卓系统会为每个应用分配一个唯一的UID,两个应用通过ShareUID跑在同一进程中是有要求的,需要两个应用有相同ShareUID并且签名相同才可以。在这种情况下,他们可以互相访问对方的私有数据如data目录,组件信息,内存数据等。

运行机制

多进程会带来许多影响,android为每个进程都分配一个独立的虚拟机,不同虚拟机在内存分配上有不同的地址,导致不通虚拟机中访问同一个类的对象会产生多份副本。

一般来说,会造成的问题
1.静态成员和单例模式完全失效
2.线程同步机制完全失效,不同进程锁的不是同一个对象
3.SharedPreferences可靠性下降,不支持两个进程同时执行写操作,否则有可能数据丢失,因为其底层通过读写XML文件实现
4.Application会多次创建,当组件跑在一个新进程中时,系统腰围新进程分配独立的虚拟机,相当于又把应用重新启动了一遍,自然要创建新的Application

IPC基础概念

Serializable和Parcelable接口

Serializable

Serializable接口是Java提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。想让一个对象实现序列化,只需要这个类实现Serializable接口并声明一个serialVersionUID。实际上这个UID不是必须的,不生命这个UID也可以实现序列化,但是会影响反序列化
序列化过程
A a=new A(1);
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream(“data.txt”));//打开输出流输出到txt文件中
out.writeObject(a);
out.close() ;

反序列化过程
ObjectInputStream in =new ObjectInputStream(new FileInputStream"data.txt");//打开输入流从文件中读入数据到新建的对象中
A a=(a)in.readObject();//虽然内容一样,但两个a对象并不是同一个对象
in.close();

serialVersionUID作用

它是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能正常地被反序列化。

serialVersionUID详细工作机制

序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中,当反序列化的时候系统会去检测文件中的serialVersionUID,看它是否和现在的serialVersionUID一致,如果一直说明版本相同,这个时候可以成功反序列化,否则无法正常序列化。我们可以手动指定serialVersionUID的值,比如1L,如果不手动指定serialVersionUID的值,反序列化时当前类有所改变的话,系统会重新计算当前类的hash值并把它赋值给serialVersionUID,此时反序列化失败。当我们手动指定它之后,可以在很大程度上避免反序列化失败。如果类结构发生了非常规性改变,比如修改了类名,成员变量的类型,这样还是会失败,因为无法从老版本的数据中还原出一个新的类结构的对象。静态成员属于类不属于对象,不会参与序列化过程

Parcelable

Parcelable接口可以实现序列化并可以通过Intent和Binder传递。

在这里插入图片描述在这里插入图片描述

Parcel

Parcel内部包装了可序列化的数据,可以在Binder中自由传输。
在序列化构成中需要实现的功能有序列化、反序列化、内容描述。序列化功能由writeToParcel完成,最终是通过Parcel的一系列write方法完成的。反序列化由CREATOR完成,其内部表明了如何创建序列化对象和数组,并通过Parcel的一系列read方法来完成反序列化过程,内容描述功能由describeContents方法来完成,几乎所有情况下这个方法都应该返回0,仅当当前对象存在文件描述符时,此方法返回1。在User(Parcel in)方法中,由于book是另一个可序列化的对象,所以他的反序列化过程需要传递当前线程的上下文类加载器,否则会报无法找到类的错误。

在这里插入图片描述

Intent、Bundle、Bitmap、List、Map也可以序列化,前提是里面的元素都是可序列化的。

两者之间的选取:S是java中的序列化接口,使用起来简单但是开销很大,P是安卓中的序列化方式,使用稍微麻烦但是效率很高,推荐P。

Binder的使用和上层原理

含义:Binder是一个实现了IBinder接口的类,Binder是安卓中的一种跨进程方式,也可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,从安卓框架来说,它是ServiceManager连接各种Manager和相应ManagerService的桥梁,从应用层来说,它是服务端和客户端进行通信的媒介,当bindService时,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个对象,客户端可以获取服务端提供的服务(普通服务和AIDL服务)或者数据。
Android开发中,Binder主要用在Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及进程间通信,Messenger的底层其实是AIDL。

用AIDL分析Binder原理

新建三个文件:Book.java,Book.aidl,IBookManager.aidl 目录名需要一样

在这里插入图片描述

Book.java代码:

package com.lin.aidl;
import android.os.Parcel;
import android.os.Parcelable;

public class Book implements Parcelable {

public int id;
public String name;

public Book(int id,String name){
    this.id=id;
    this.name=name;
}

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

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

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

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

private Book(Parcel in){
    id=in.readInt();
    name=in.readString();
}

}

Book.aidl代码:

  ` //Book.aidl
  package com.lin.aidl;  

   //Declare any non-default types here with import statements  

  parcelable Book; ` 

IBookManager.aidl代码:

`// IBookManager.aidl
package com.lin.aidl;
import com.lin.aidl.Book;

// Declare any non-default types here with import statements

interface IBookManager {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
List<Book> getBookList();
void addBook(in Book book);
}`

Book.java是一个表示图书信息的类,实现了Parcelable接口。
Book.aidl是Book类在AIDL中的声明。
IBookManager.aidl是我们定义的一个接口。

其中有两个方法,getBookList用于从远程服务端获取图书列表,addBook用于往图书列表中添加一本书(都是示例用)。尽管Book类和IBookManager位于相同的包中,但是在IBookManager中仍然要导入Book类。

运行之后会在generated目录下产生一个系统生成的IBookManager类,也就是Binder类,我们用这个类来分析Binder的工作原理。

在这里插入图片描述

系统生成的IBookManager代码:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.lin.aidl;
// Declare any non-default types here with import statements

public interface IBookManager extends android.os.IInterface
{
  /** Default implementation for IBookManager. */
  public static class Default implements com.lin.aidl.IBookManager
  {
    /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
    @Override public java.util.List<com.lin.aidl.Book> getBookList() throws android.os.RemoteException
    {
      return null;
    }
    @Override public void addBook(com.lin.aidl.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.lin.aidl.IBookManager
  {
    private static final java.lang.String DESCRIPTOR = "com.lin.aidl.IBookManager";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.lin.aidl.IBookManager interface,
     * generating a proxy if needed.
     */
    public static com.lin.aidl.IBookManager asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.lin.aidl.IBookManager))) {
        return ((com.lin.aidl.IBookManager)iin);
      }
      return new com.lin.aidl.IBookManager.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_getBookList:
        {
          data.enforceInterface(descriptor);
          java.util.List<com.lin.aidl.Book> _result = this.getBookList();
          reply.writeNoException();
          reply.writeTypedList(_result);
          return true;
        }
        case TRANSACTION_addBook:
        {
          data.enforceInterface(descriptor);
          com.lin.aidl.Book _arg0;
          if ((0!=data.readInt())) {
            _arg0 = com.lin.aidl.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.lin.aidl.IBookManager
    {
      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 java.util.List<com.lin.aidl.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.lin.aidl.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.lin.aidl.Book.CREATOR);
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      @Override public void addBook(com.lin.aidl.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.lin.aidl.IBookManager sDefaultImpl;
    }
    static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    public static boolean setDefaultImpl(com.lin.aidl.IBookManager 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.lin.aidl.IBookManager getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  /**
       * Demonstrates some basic types that you can use as parameters
       * and return values in AIDL.
       */
  public java.util.List<com.lin.aidl.Book> getBookList() throws android.os.RemoteException;
  public void addBook(com.lin.aidl.Book book) throws android.os.RemoteException;
}

分析

1.它继承了IInterface接口,并且它自己也是接口,所有可以在Binder中传输的接口都要继承这个接口。

在这里插入图片描述

2.它声明了两个方法:getBookList和addBook

在这里插入图片描述

3.同时还声明了两个整型id标记这两个方法,这两个id用于标识在transact过程中客户端所请求的是什么方法

在这里插入图片描述

4.声明了一个内部类Stub,这个Stub就是一个Binder类,当客户端和服务端位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub的内部代理类Proxy完成

在这里插入图片描述

这个接口的核心实现就是它的内部类Stub和他的内部代理类Proxy

DESCRIPTOR

Binder的唯一表示,一般用当前Binder的类名表示

在这里插入图片描述

asInterface(android.os.IBinder obj)

在这里插入图片描述

用于将服务端的Binder对象转换成客户端所需的AIDL接口类型对象,这种转换过程是区分进程的,如果客户端和服务端位于统一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回系统封装后的Stub.proxy对象

asBinder

@Override public android.os.IBinder asBinder(){return this;}
用于返回当前Binder对象

onTransact

这个方法运行在服务端中的BInder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。该方法的原型时public Boolean onTransact(int code,android.os.Parcel reply,int flags)。服务端通过code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需的参数(如果目标方法有参数的话),然后执行目标方法。当目标方法执行完毕后,就向reply中写入返回值(如果有返回值的话)。如果此方法返回false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限认证。

Proxy#getBookList

在这里插入图片描述

这个方法运行在客户端,当客户端远程调用此方法时,它的内部实现:首先创建该方法所需要的输入型Parcel对象_data、输出型Parcel对象_reply和返回值对象List。然后把该方法的参数信息写入_data中(如果有参数的话),接着调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起,然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果,最后返回_reply中的数据

Proxy#addBook

它的执行过程和getBookList是一样的,addBook没有返回值,所以他不需要从_reply中取出返回值。

额外说明

1.当客户端发起远程请求时,由于当前线程会被挂起直到服务端进程返回数据,所以那么一个远程方法是很耗时的,那么不能在UI线程发起此远程请求。
2.由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方法去实现,因为它已经运行在一个线程中了

在这里插入图片描述

其实我们完全可以不提供AIDL文件即可实现Binder,之所以提供,是为了方便系统为我们生成代码。系统根据AIDL文件生成Java文件的格式是固定的,我们可以抛开AIDL文件直接写一个Binder。

手动实现Binder的步骤

1.声明一个AIDL性质的接口,继承IInterface接口,在接口中声明一个Binder描述符和另外的方法id
2.实现Stub类和其中的Proxy类

AIDL文件的本质是系统为我们提供的一种快速实现Binder的工具。

Binder的linkToDeath和unlinkToDeath方法

Binder运行在服务端进程,如果服务端进程由于某种原因异常种植,这个时候我们到服务端的Binder连接断裂(Binder死亡),会导致我们的远程调用失败。如果我们不知道Binder连接已经断裂,客户端功能会受到影响,这两个办法就是为了解决这个问题而产生的。通过linkToDeath设置一个死亡代理,当Binder死亡时,我们会收到通知,这个时候我们就可以重新发起连接请求回复链接。

给Binder设置死亡代理

声明一个DeathRecipient对象,它是一个接口,内部只有一个binderDied方法,我们需要实现它。当Binder死亡时,系统回调binderDied方法,然后我们就可以移除之前绑定的binder代理并重新绑定远程服务。
eg:
1.在客户端中实例化一个IBookManager对象
2.实例化一个IBinder.DeathRecipient对象,并在binderDied方法中设置死亡代理

在这里插入图片描述

客户端绑定远程服务成功后,给Binder设置死亡代理。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值