美文网首页
022 Android多进程-序列化-Serializable和

022 Android多进程-序列化-Serializable和

作者: 凤邪摩羯 | 来源:发表于2021-01-12 09:11 被阅读0次

一、前言

在之前一节,我们了解到,进程间通信的数据,除了基本数据类型,其他的数据都要进行序列化,而Parcelable是Android为我们提供的序列化的接口。

序列化和反序列化的概念

那什么是序列化呢,这几涉及到系列化和反序列化

把对象转换为字节序列的过程称为对象的序列化。
把字节序列恢复为对象的过程称为对象的反序列化。

对象的序列化主要用途:

  • 1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
  • 2) 在网络上传送对象的字节序列。
  • 2) 进程间传递对象。

在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。

当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

二、Serializable

Serializable是Java为我们提供的一个标准化的序列化接口

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

package java.io;

public interface Serializable {
}

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

private static final long serivalVersionUID = 123456578689L;

序列化时系统会把对象的信息以及 serivalVersionUID 一起保存到某种介质中(例如文件或内存中),当反序列化时就会把介质中的 serivalVersionUID 与类中声明的 serivalVersionUID 进行对比,如果两者相同则说明序列化的类与当前类的版本是相同的,则可以序列化成功。如果两者不相等,则说明当前类的版本已经变化(可能是新增或删减了某个方法),则会导致序列化失败
  如果没有手动声明 serivalVersionUID ,编译工具则会根据当前类的结构自动去生成 serivalVersionUID,这样在反序列化时只有类的结构完全保持一致才能反序列化成功
  为了当类的结构没有发生结构性变化时依然能够反序列化成功,一般是手动为 serivalVersionUID 指定一个固定的值。这样即使类增删了某个变量或方法体时,依然能够最大程度地恢复数据。当然,类的结构不能发生太大变化,否则依然会导致反序列化失败
  此外,静态成员变量属于类不属于对象,所以不会参与序列化过程,用 transient 关键字标记的成员变量也不会参与序列化过程

  • 示例
public class Person implements Serializable {

    private static final long serialVersionUID = -4753925637112958617L;

    public String name;

    public int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • 传输
      //Serializable数据
                Person p = new Person("刘明哲", 22);
                Intent intent = new Intent(MainActivity.this, XLHActivity.class);
                intent.putExtra("person", p);
                startActivity(intent);
  • 获取
        Intent intent = getIntent();
        Person data = (Person) intent.getSerializableExtra("person");
        LogUtils.e("获取数据:" + data.toString());

3、Parcelable

进行Android开发的时候,我们是无法将对象的引用传给Activities或者Fragments,我们需要将这些对象放到一个Intent或者Bundle里面,然后再传递。简单来说就是将对象转换为可以传输的二进制流(二进制序列)的过程,这样我们就可以通过序列化,转化为可以在网络传输或者保存到本地的流(序列),从而进行传输数据 ,那反序列化就是从二进制流(序列)转化为对象的过程。

Parcelable是Android为我们提供的序列化的接口,Parcelable相对于Serializable的使用相对复杂一些,但Parcelable的效率相对Serializable也高很多,这一直是Google工程师引以为傲的,有时间的可以看一下Parcelable和Serializable的效率对比 Parcelable vs Serializable 号称快10倍的效率

3.1 Parcelable接口

Android采用Parcel来表示封装消息数据。这个是通过IBinder通信的消息的载体。需要明确的是Parcel用来存放数据的是内存(RAM),而不是永久性介质(Nand等)。

Parcelable,定义了将数据写入Parcel,和从Parcel中读出的接口。如果需要将目标数据(实体类)封装到消息中去,就该实体类就必须实现这一接口,让该实体类可序列化和反序列化。

接口定义如下

public interface Parcelable {
    //内容描述接口,基本不用管
    public int describeContents();
    //写入接口函数,打包
    public void writeToParcel(Parcel dest, int flags);
     //读取接口,目的是要从Parcel中构造一个实现了Parcelable的类的实例处理。因为实现类在这里还是不可知的,所以需要用到模板的方式,继承类名通过模板参数传入。
    //为了能够实现模板参数的传入,这里定义Creator嵌入接口,内含两个接口函数分别返回单个和多个继承类实例。
    public interface Creator<T> {
           public T createFromParcel(Parcel source);
           public T[] newArray(int size);
    }
}

通过定义可以知道,Parcelable接口的实现类是可以通过Parcel写入和恢复数据的,并且必须要有一个非空的静态变量 CREATOR
Parcel可以包含原始数据类型(用各种对应的方法写入,比如writeInt(),writeFloat()等),可以包含Parcelable对象,它还包含了一个活动的IBinder对象的引用,这个引用导致另一端接收到一个指向这个IBinder的代理IBinder。

3.2、实现Parcelable

Parcelable实现过程主要分为序列化,反序列化,描述三个过程,下面通过一个音乐数据类MuiscData的例子,来讲解Parcelable的实现。

public class MuiscData {
    //专辑名称
    public String mAlbum;
    //封面地址
    public String mCover;
    //歌名
    public String mName;
    //演唱人
    public String mSinger;

 
    public MuiscData(String mAlbum, String mCover, String mName, String mSinger) {
        this.mAlbum = mAlbum;
        this.mCover = mCover;
        this.mName = mName;
        this.mSinger = mSinger;
    }

    public String getmAlbum() {
        return mAlbum;
    }

    public void setmAlbum(String mAlbum) {
        this.mAlbum = mAlbum;
    }

    public String getmCover() {
        return mCover;
    }

    public void setmCover(String mCover) {
        this.mCover = mCover;
    }

    public String getmName() {
        return mName;
    }

    public void setmName(String mName) {
        this.mName = mName;
    }

    public String getmSinger() {
        return mSinger;
    }

    public void setmSinger(String mSinger) {
        this.mSinger = mSinger;
    }

    @Override
    public String toString() {
        return "MuiscData{" +
                "mAlbum='" + mAlbum + '\'' +
                ", mCover='" + mCover + '\'' +
                ", mName='" + mName + '\'' +
                ", mSinger='" + mSinger + '\'' +
                '}';
    }

}

这就是我们要传输的数据的格式,要将其在进程间进行传输,就要对其实现序列化,也就是实现Parcelable接口,这个接口中需要我们实现的主要方法有

writeToParcel :完成序列化
describe :描述
ContentsParcelable.Creator<?> :对对象进行反序列化并且返回一个对象实例。

也就是Parcelable实现的三个过程

接下来,我们就把MuiscData类实现Parcelable接口

public class MuiscData implements Parcelable {
  ...
  ...
  ...
  protected MuiscData(Parcel in) {
        mAlbum = in.readString();
        mCover = in.readString();
        mName = in.readString();
        mSinger = in.readString();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mAlbum);
        dest.writeString(mCover);
        dest.writeString(mName);
        dest.writeString(mSinger);
    }

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

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

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

完成了这三个过程,就可以对对象进行序列化了,也就是说我们可以传递对象了。

最后,我们就可以在俩个不同进程(或相同进程)的activity传递数据了。

发送者MusicActivity

public class MusicActivity extends Activity {
  ...
  ...
  ...
                //Parcelable数据
                MuiscData musicD = new MuiscData("七里香", "dab64034f1a36.jpg", "周杰伦", "七里香");
                Intent intent = new Intent(MainActivity.this, XLHActivity.class);
                intent.putExtra("music", musicD);
                startActivity(intent);
    
  ...
  ...
  ...
}

接受者OtherActivity

public class OtherActivity extends Activity {
  ...
  ...
  ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.myprocesses_activity_layout);

        Intent intent = getIntent();
//        Person data = (Person) intent.getSerializableExtra("person");
        MuiscData data = intent.getParcelableExtra("music");
        LogUtils.e("获取数据:" + data.toString());


    }
  ...
  ...
  ...
}

实现 Parcelable 接口需要实现以上四个方法,用于进行序列化、反序列化和内容描述。一般我们也不需要手动实现 Parcelable 接口,可以通过 Android Studio的一个插件:Android Parcelable code generator 来自动完成
  实现 Parcelable 接口需要实现以上四个方法,用于进行序列化、反序列化和内容描述。一般我们也不需要手动实现 Parcelable 接口,可以通过 Android Studio的一个插件:Android Parcelable code generator 来自动完成

image.png

五、结语

Parcelable和Serializable都是实现序列化并且都可以用于Intent间传递数据。

Serializable是Java的实现方式,可能会频繁的IO操作,所以消耗比较大,但是实现方式简单 。
Parcelable是Android提供的方式,效率比较高,但是实现起来复杂一些 。

二者的选取规则是:内存序列化上选择Parcelable, 存储到设备或者网络传输上选择Serializable(当然Parcelable也可以但是稍显复杂)

  • 选择序列化方法的原则

1)在使用内存的时候,Parcelable比Serializable性能高,所以推荐使用Parcelable。

2)Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。

3)Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable不能很好的保证数据的持续性在外界有变化的情况下。尽管Serializable效率低点,但此时还是建议使用Serializable 。

相关文章

网友评论

      本文标题:022 Android多进程-序列化-Serializable和

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