Android IPC 之序列化

01.什么是IPC

  • IPC(Inter-Process Communication)的含义即为进程间通信或者翻译为跨进程通信,是指两个进程之间进行数据交换的过程。
  • 一般情况下,在 Android系统中一个应用就只享有一个进程,在最简单的情况下一个进程可以只包含有一个线程(当然,一般情况下是不可能的),即主线程,也称为 UI 线程
  • 有时候应用因为某些原因需要采用多进程模式,此时如果要在应用内的不同进程间进行通信,就需要使用到 IPC 机制。或者是两个不同的应用需要进行数据交换,此时也一样需要依靠 Android 系统提供的 IPC 方案

02.开启多进程

  • 为一个 Android 应用开启多进程模式的方法有两种。

    • 第一种方法是在 AndroidMenifest 中为四大组件指定 android:process 属性,为其声明要在哪个进程名下运行,即可开启多进程。
    • 第二种方法是通过 JNI 在 native 层中 fork 一个新的进程。这里只讨论第一种方法。
  • Android 应用默认在命名为包名的进程下运行,除非你为其指定了 android:process 属性
    例如,这里创建一个应用,包名为 com.yc.ipc ,再指定四大组件之一的 Service 运行在其它进程下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <activity android:name=".MainActivity">
    <intent-filter>
    <action android:name="android.intent.action.MAIN" />

    <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    </activity>

    <service
    android:name=".MyService"
    android:process="com.yc.process.test" />
  • 并在启动 MainActivity 的同时启动MyService,这样,在系统进程列表中就可以看到这两个相关的进程

03.多进程影响

  • 虽然开启多进程的方法并不算麻烦,但当应用开启了多进程后,其实会对来很多的负面影响,主要有以下几个:
    • 静态成员与单例模式失效
    • 线程同步机制失效
    • SharedPreferences 可靠性下降
    • Application 被创建多次
  • 为了解决多进程带来的问题,系统也为开发者提供了很多的跨进程通信方式,比如文件共享、ContentProvider、Messenger、AIDL、Socket 等

04.Serializable

  • 跨进程通信的目的就是为了进行数据交换,但并不是所有的数据类型都能被传递,除了基本数据类型外,还必须是实现了序列化和反序列化的数据类型才可以,即实现了 Serializable 接口或 Parcelable 接口的数据类型

  • Serializable 接口是由 Java所提供的一个序列化接口,是一个空接口,为对象提供了标准的序列化和反序列化接口。类只要实现了该接口,即可自动实现默认的序列化过程。

    1
    2
    3
    4
    package java.io;

    public interface Serializable {
    }
  • 此外,为了辅助系统完成对象的序列化和反序列化过程,还可以声明一个long型数据serivalVersionUID

    1
    private static final long serivalVersionUID = 123456578689L;
    • 序列化时系统会把对象的信息以及 serivalVersionUID 一起保存到某种介质中(例如文件或内存中),当反序列化时就会把介质中的 serivalVersionUID 与类中声明的 serivalVersionUID 进行对比,如果两者相同则说明序列化的类与当前类的版本是相同的,则可以序列化成功。如果两者不相等,则说明当前类的版本已经变化(可能是新增或删减了某个方法),则会导致序列化失败
  • 没有声明serivalVersionUID怎么办?

    • 如果没有手动声明 serivalVersionUID ,编译工具则会根据当前类的结构自动去生成 serivalVersionUID ,这样在反序列化时只有类的结构完全保持一致才能反序列化成功
    • 为了当类的结构没有发生结构性变化时依然能够反序列化成功,一般是手动为 serivalVersionUID 指定一个固定的值。这样即使类增删了某个变量或方法体时,依然能够最大程度地恢复数据。当然,类的结构不能发生太大变化,否则依然会导致反序列化失败
  • 某字段可以不用序列化么

    • 此外,静态成员变量属于类不属于对象,所以不会参与序列化过程,用 transient 关键字标记的成员变量也不会参与序列化过程

05.Parcelable

  • Parcelable 接口是由 Android 系统提供的序列化接口,官方也推荐使用 Parcelable 进行序列化操作,Bundle 、 Intent 和 Bitmap 等都实现了 Parcelable 接口。

  • Parcelable 接口相比 Serializable 更为高效,但实现方式也相比麻烦些。实现 Parcelable 接口需要实现四个方法,用于进行序列化、反序列化和内容描述。一般我们也不需要手动实现 Parcelable 接口,可以通过 Android Studio的一个插件:Android Parcelable code generator 来自动完成

  • 下面看个例子

    • 可以看出,实现一个Parcelable接口,需要实现以下几个方法:

    • 1.构造函数:从序列化后的对象中创建原始对象

    • 2.describeContents :接口内容的描述,一般默认返回0即可

    • 3.writeToParcel:序列化的方法,将类的数据写到parcel容器中

    • 4.静态的parcelable.Creator接口,这个接口包含两个方法

      • 1)createFormParcel:反序列化的方法,将Parcel还原成Java对象
      • 2)newArray:提供给外部类反序列化这个数组使用。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      public class Book implements Parcelable{
      private String bookName;
      private String author;
      private int publishDate;

      public Book(){

      }

      //写一个构造方法或者set方法来方便写入数据
      public String getBookName(){
      return bookName;
      }

      public void setBookName(String bookName){
      this.bookName = bookName;
      }

      public String getAuthor(){
      return author;
      }

      public void setAuthor(String author){
      this.author = author;
      }

      public int getPublishDate(){
      return publishDate;
      }

      public void setPublishDate(int publishDate){
      this.publishDate = publishDate;
      }

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

      @Override
      public void writeToParcel(Parcel out, int flags){
      //该方法将类的数据写入外部提供的Parcel中.即打包需要传递的数据到Parcel容器保存,
      //以便从parcel容器获取数据
      out.writeString(bookName);
      out.writeString(author);
      out.writeInt(publishDate);
      }

      public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>(){

           @Override
      public Book[] newArray(int size){
      //从Parcel容器中读取传递数据值,封装成Parcelable对象返回逻辑层。
      return new Book[size];
      }

      @Override
      public Book createFromParcel(Parcel in){
      //从Parcel容器中读取传递数据值,封装成Parcelable对象返回逻辑层。
      return new Book(in);
      }
      };

      public Book(Parcel in){
      //如果元素数据是list类型的时候需要: lits = new ArrayList<?> in.readList(list);
      //否则会出现空指针异常.并且读出和写入的数据类型必须相同.如果不想对部分关键字进行序列化,可以使用transient关键字来修饰以及static修饰.
      bookName = in.readString();
      author = in.readString();
      publishDate = in.readInt();
      }
      }