美文网首页
IPC机制(Serializable、Parcelable)——

IPC机制(Serializable、Parcelable)——

作者: TuTu兔 | 来源:发表于2019-02-17 14:30 被阅读19次

    一.Android IPC简介

    IPC 是 Inter-Progress Communication 的缩写,意思为进程间通信或者跨进程通信是指两个进程之间进行数据相互交换的过程。

    1.说起进程间通信,我们先应了解什么是进程?什么是线程?

    按照操作系统中的描述,线程时 CPU 调度最小的单元,同时线程是一种有限的系统资源;二进程一般指一个执行单元,在 PC 或者移动设备上指一个程序或一个应用。

    一个进程可包括多个线程。简单情况下,一个进程可以只有一个线程,即主线程,又叫UI线程,在 UI 线程才能操作界面元素。

    如果在UI线程执行大量耗时任务会造成界面无法响应,严重影响用户体验,该情况在PC和移动设备都会存在,Android 中管他叫 ANR 异常(Application Not Responding),即应用无响应。解决这个问题就要把一些耗时的任务放在线程中。

    2.IPC 机制存在于每一种操作系统

    IPC 并不是 Android 独有的,任何操作系统都需要有对应的 IPC 机制。例如Windows 上可通过剪切板,管道等进行进程间通信;Linux 上可以通过命名管道,共享共存进行进程间通信。

    对于 Android 来说,它是一种基于 Linux 内核的移动操作系统,但它的进程间方式并不完全继承自 Linux。在 Android 中最有特色的进程间通信方式就是 Binder 了,通过它可轻松实现进程间通信;此外 Android 还支持 Socket,通过它可实现任意两个终端之间的通信,当然他也可实现同一设备上两个人进程之间的通信。

    3.多进程的使用情景

    多进程使用大致分两种情景。

    ①第一种情况是由于应用自身原因要采取多进程。原因有很多,比如有的模块由于特殊原因需运行在单独的进程中;又比如为了加大一个应用可使用的内存,需要通过多进程获取多分内存空间。(Android 对单个应用所使用的最大内存做了限制早起一些版本是 16MB,不同设备大小不同。)

    ②另一种情况是当前应用需要向其他应用获取数据,由于是两个应用,所以必须采取跨进程的方式。甚至说我们通过系统提供的 ContentProvider 去查询数据的时候,就是一种进程间通信,只不过通信细节被系统内部屏蔽了,所以我们无法感知而已。


    二. Android 中的多进程模式

    正式介绍进程间通信前,我们需理解 Android 中多进程模式。通过给四大组件指定 android:process 属性,我们可以轻易开启多进程模式,但看起来简单,实则暗藏杀机,有时通过多进程带给我们的好处远不足以弥补使用多进程带给我们的代码层面的负面影响。

    开启多进程模式

    正常情况下,在 Android 中多进程是指在一个应用中存在多个进程的情况,因此这里不讨论两个应有之间的多进程情况。首先,在 Android 中使用多进程只有一种方法,那就是通过给四大组件(Activity,Service,Receiver,ContentProvider)指定 android:process 属性,我们无法给一个线程或实体类指定其运行所在的进程。其实还有另一种非常规的多进程方法,那就是通过JNI在native层去fork一个新的进程,但这种方法属于特殊情况,并不常用,所欲暂时不用考虑这种方式。

    
    <activity
    
            android:name="com.practive.xingxinyu.myapplication.MainActivity"
    
            android:configChanges="orientation|keyboardHidden">
    
          <intent-filter>
    
            <action android:name="android.intent.action.MAIN"/>
    
            <category android:name="android.intent.category.LAUNCHER"/>
    
          </intent-filter>
    
          </activity>
    
        <activity
    
            android:name="com.practive.xingxinyu.myapplication.SecondActivity"
    
            android:process=":remote"/>
    
        <activity
    
            android:name="com.practive.xingxinyu.myapplication.ThirdActivity"
    
            android:process="com.practive.xingxinyu.myapplication.remote"/>
    
    

    上面的 SecondActivity 和 ThirdActivity 分别指定了 process 属性,且他们的属性值不同,这一位当前应用增加了两个新的进程。假设当前包名为“com.practive.xingxinyu.myapplication”,当 SecondActivity 启动,系统会为他创建一个单独的进程,进程名叫:“com.practive.xingxinyu.myapplication:remote”;当 ThirdActivity 启动,系统会为他创建一个单独的进程,进程名叫:“com.practive.xingxinyu.myapplication.remote”。同时入口 Activity 是 MainActivity ,没有为它指定 process 属性,那么它运行在默认进程中,默认进程名为包名。

    关于私有进程&公有进程:SecondActivity 的 android:process 属性为 “:remote”,这里的“:”的含义是是指在当前的进程名前附加上当前的包名,这是一种简写的方法。其次,进程名以“.”或“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一进程中;而进程名以小写字母开头的进程属于全局进程,其他应用通过 ShareUID 方式可以和它跑在同一个进程中。

    Android 系统会为每个应用分配一个唯一的 UID,具有相同UID的应用才能共享数据。我要说的是,两个应用通过 ShareUID 跑在统一进程中是有要求的,需要这两个应用有相同的 ShareUID 且签名相同才行。这种情况下,无论他们能否泡在同一进程中,他们都能互相访问对方私有数据,例如:data 目录、组件信息等。当然,如果他们跑在统一进程中,他们还可以除了能共享 data 目录、组件信息,还能共享内存数据。

    多进程模式的运行机制

    Android 为每一个应用分配了一个独立的虚拟机,也可以说为每一个进程分配了一个独立的虚拟机,不同的虚拟机在内存上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本,所以各进程间是互不干扰的。举个例子:当在一个进程内对一个 public 的静态成员变量的值进行修改,此操作对其他进程不会造成影响,其他进程中这个 public 的静态成员变量的值并不会发生改变。

    鉴于此机制,所有运行在不同进程的四大组件,只要他们之间需要通过内存来共享数据,都会共享失败,这也是多进程带来的主要影响。

    一般来说,使用多进程会造成如下几个问题:

    • 1.静态成员和单例模式完全失效。

    • 2.线程同步机制完全失效。

    • 3.SharedPreferences 的可靠性下降。

    • 4.Application 会多次重建。

    第1个问题已经举例分析。第2个问题和第1个问题本质是一致的,既然不是一块内存了,那么无论是锁对象还是锁全局类都无法保证线程同步,因为不同进程锁的是不同对象。第3个问题是由于 SharedPreferences 不支持多个进程同时进行操作,否则有一定几率丢失数据,因为 SharedPreferences 底层是通过读写 XML 文件实现的,所以并发读/写都有可能出现问题。第4个问题,当一个组件泡在一个新的进程,由于系统要创建新的进程并分配独立的虚拟机,所以这个过程就是启动一个应用的过程,也就是系统有吧这个应用重新启动了一遍,既然重启了,那么自然会新建一个 Application 了。

    这里我们分析了一些多进程带来的问题,为了解决这些问题系统提供了很多跨进程通信的方法。比如通过 Intent 来传递数据、共享文件和 SharedPreferences ;基于 Binder 的 Messenger 和 AIDL 以及 Socket 等。


    IPC 基础概念介绍

    IPC 主要包含 Serializable 接口、Parcelable 接口以及 Binder。

    Serializable 接口和 Parcelable 接口可以完成对象的序列化,当我们需要通过 Intent 和 Binder 传输数据时就需要使用到Serializable 接口和 Palrcelable 接口。还有的时候我们需要把对象持久化保存到设备上或网络传输给其他客户端,可以用到 Serializable 接口来完成对象的持久化。

    Serializable 接口

    Serializable 是 JAVA 提供的一个序列化接口,是一个空接口,可以为对象提供标准的序列化和反序列化操作。使用方法很简单,只用在类的声明中制定一个类似下面的标识即可自动实现默认序列化过程。

    
    private static final long serialVersionUID = 6286579102573831569L;
    
    

    如上所示想让一个对象实现序列化,只需要这个类实现 Serializable 接口并声明一个 serialVersionUID 即可。

    serialVersionUID可以手动编写,也可以自动生成。方法是先下载插件:Android studio -》 preferences -》 Plugins —》 GenerateSerialVersionUID 点击 install。(如下图所示)

    serialVersionUID插件安装

    下一步是设置提示:Android studio -》 preferences -》 Inspections -》 勾选中下图几项即可。

    设置serialVersionUID生成提示.png

    下面所示的 User 类是一个实现了 Serializable 接口的类,它是可以被序列化和反序列化的。

    
    public class User implements Serializable {
    
      private static final long serialVersionUID = 6286579102573831569L;
    
    
    
      public int age;
    
      public String name;
    
      public boolean isMale;
    
    }
    
    

    如何进行对象的序列化和反序列化非常简单,只需要采用 ObjectOutputStream 和 ObjectInputStream 即可轻松实现。下面简单举例。

    
    //序列化过程
    
        User user = new User(12,"jack",true);
    
        ObjectOutputStream out = new ObjectOutputStream(
    
            new FileOutputStream("cache.txt"));
    
        out.writeObject(user);
    
        out.close();
    
    
    
        //反序列化过程
    
        ObjectInputStream in = new ObjectInputStream(
    
            new FileInputStream("cache.txt"));
    
        User newUser = (User) in.readObject();
    
        in.close();
    
    

    上述代码采用 Serializable 方式序列化对象,只需把实现了 Serializable 接口的 User 对象写到文件中就可以快速恢复了,回复后的对象 newUser 和 user 内容完全一样,但不是同一对象。

    其实,即使不指定 serialVersionUID 也可以实现序列化。但是系统既然提供了这个 serialVersionUID ,那么它必定是有用的。

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

    Serializable 的详细工作机制是这样的:序列化时,系统会把当前类的 serialVersionUID 写入序列化文件中,当反序列化时系统回去检测文件中的 serialVersionUID 是否和当前类的 serialVersionUID 一致,若一致,说明序列化的类和当前类的版本是相同的,这时就可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变化,比如修改了成员变量、类型或修改了类名,这时候都是无法正常反序列化的,反序列化失败,程序就会 crash。


    Parcelable 接口

    Parcelable 也是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并通过 Intent 和Binder 传递。下面实力一个典型用法。

    
    public class PUser implements Parcelable {
    
      public int userId;
    
      public String userName;
    
      public boolean isMale;
    
      public Book book;
    
      public PUser(int userId, String userName, boolean isMale) {
    
        this.userId = userId;
    
        this.userName = userName;
    
        this.isMale = isMale;
    
      }
    
      public static final Creator<PUser> CREATOR = new Creator<PUser>() {
    
        @Override
    
        public PUser createFromParcel(Parcel in) {
    
          return new PUser(in);
    
        }
    
        @Override
    
        public PUser[] newArray(int size) {
    
          return new PUser[size];
    
        }
    
      };
    
      @Override
    
      public int describeContents() {
    
        return 0;
    
      }
    
      @Override
    
      public void writeToParcel(Parcel dest, int flags) {
    
        dest.writeInt(userId);
    
        dest.writeString(userName);
    
        dest.writeByte((byte) (isMale ? 1 : 0));
    
      }
    
      protected PUser(Parcel in) {
    
        userId = in.readInt();
    
        userName = in.readString();
    
        isMale = in.readByte() != 0;
    
        book = in.readParcelableArray(Thread.currentThread().getContextClassLoader());
    
      }
    
    }
    
    

    从上述代码可看出,Parcel 内部包装了可序列化的数据,在序列化过程中需要实现的功能有:序列化、反序列化和内容描述。

    序列化功能由 writeToParcel 方法来完成,最终是通过 Parcel 中的一系列 write 方法完成的;反序列化功能由 CREATOR 来完成,其内部标明了如何创建序列化对象和数组,并通过 Parcel 的一系列 read 方法完成反序列化过程;内容描述由 describeContents 方法完成,几乎所有情况下该方法的返回都为0,仅在当前对象中存在文件描述时此方法返回为1。特别需要注意的是,PUser(Parcel in) 方法中,因为book是另一个可序列化对象,所以它的反序列化过程需要传递当前线程的上下文加载器,否则会报无法找到类的错误。

    系统为我们提供了许多实现了 Parcelable 接口的类,他们都是可以直接序列化的,例如:Intent 、 Bundle 、 Bitmap 等,同时 List 和 Map 也可以序列化,前提是他们里面的每一个元素也是序列化的。

    那么在实现序列化的时候,我们在 Serializable 和 Parcelable 二者之间该如何选去呢?—— Serializable 和 Parcelable 之间的区别

    Serializable 是 Java 中的序列化接口,其使用简单但开销较大,序列化和反序列化过程需要大量的 I/O 操作。

    而 Parcelable 是 Android 中的序列化方式,因此更适合用在 Android 平台,它的缺点是使用起来稍微麻烦,但效率很高,因此我们要首选 Parcelable。 Parcelable 主要用在内存序列化上;通过 Parcelable 将对象序列化到储存设备中或将对象序列化通过网络传输也是可以的,但这个过程会稍显复杂,因此这两种情况下建议使用 Serializable。

    • 帮忙点个赞再走嘛~


      点赞暴富
    • 【个人主页】 点击关注我,TuTu兔 会持续更新分享更多姿势哟~ (✿◡‿◡) ~

    相关文章

      网友评论

          本文标题:IPC机制(Serializable、Parcelable)——

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