Android开发之漫漫长途 X——Android序列化

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!另外,本系列文章知识可能需要有一定Android开发基础和项目经验的同学才能更好理解,也就是说该系列文章面向的是Android中高级开发工程师。


前言

上一篇中我们比较详尽的分析了ServiceManager。那么本篇我们来讲一下Android序列化的相关知识。为什么跨度那么大,因为“任性”?其实不是的,同志们还记得上两篇出现的Parcel吗,Parcel是一个容器,他可以包含数据或者是对象引用,并且能够用于Binder的传输。同时支持序列化以及跨进程之后进行反序列化,同时其提供了很多方法帮助开发者完成这些功能。从上面的描述可以看出Parcel是进程间通信的数据载体。我们常常需要持久化一些对象,除了数据库等持久化方案之外,把对象转换成字节数组并通过流的方式存储在本地也是一个不错的方法,另外当我们需要通过Intent和Binder传输数据是就需要使用序列化后的数据。

Java中的Serializable

Serializable 是Java所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable来实现序列化相当简单,只需要在需要序列化的类实现Serializable接口并在其中声明一个类似下面的标识即可自动实现默认的序列化过程。

public class Person extends PersonParent implements Serializable {
    private static final long serialVersionUID = 1L;

    //静态域
    public static int static_field;
    //transient域
    public transient int transient_field;
    //一个普通的域
    public String desc;

    public Person(String desc) {
        this.desc = desc;
    }

    static class PersonSerializableProxy implements Serializable{
        private String desc;

        private PersonSerializableProxy(Person s) {
            this.desc = s.desc;
        }

        /**
         * 与writeReplace相同,ObjectInputStream会通过反射调用 readResolve()这个方法,
         * 决定是否替换反序列化出来的对象。
         * @return
         */
        private Object readResolve() {
            return new Person(desc);
        }

    }

    /**
     *
     * 在序列化一个对象时,ObjectOutputStream会通过反射首先调用writeReplace这个方法,
     * 在这里我们可以替换真正送去序列的对象,
     * 如果我们没有重写,那序列化的对象就是最开始的对象。
     * @return
     */
    private Object writeReplace() {
         //序列化Person的时候我们并没有直接写入Person对象,而是写入了PersonSerializableProxy对象
        return new PersonSerializableProxy(this);
    }

    /**
     * 这里主要是为了防止攻击,任何以Person声明的对象字节流都是流氓!!
     * 因为我在writeReplace中已经把序列化的实例指向了SerializableProxy
     * @param stream
     * @throws InvalidObjectException
     */
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
        throw new InvalidObjectException("proxy requied!");
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Person person = new Person("desc");
        person.transient_field = 100;
        person.static_field = 10086;

        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cache.txt"));
        outputStream.writeObject(person);
        outputStream.flush();
        outputStream.close();

        person.static_field = 10087;

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("cache.txt"));
        Person deserialObj = (Person) objectInputStream.readObject();
        System.out.println(deserialObj);
}

}
class PersonParent{
    private String name;
    //PersonParent类要么继承自Serializable,要么需要提供一个无参构造器。
    public PersonParent() {
    }

    public PersonParent(String name) {
        this.name = name;
    }
}

不过在使用中也需要注意以下几个问题:

  1. serialVersionUID用来标识当前序列化对象的类版本,建议每一个实现Serialization的类都指定该域。当然如果我们没有指定,JVM会根据类的信息自动生成一个UID。
  2. 被transient描述的域和类的静态变量是不会被序列化的,序列化是针对类实例。
  3. 需要进行序列化的对象所有的域都必须实现Serializable接口,不然会直接报错NotSerializableException。当然,有两个例外:域为空 或者域被transient描述是不会报错的。
  4. 如果一个实现了Serializable类的对象继承自另外一个类,那么这个类要么需要继承自Serializable,要么需要提供一个无参构造器。
  5. 反序列化产生的对象并不是通过构造器创建的,那么很多依赖于构造器保证的约束条件在对象反序列化时都无法保证。比如一个设计成单例的类如果能够被序列化就可以分分钟克隆出多个实例...

Android中的Parcelable

相对于Serializable而言,Parcelable的使用要复杂一些

public class Book implements Parcelable {
    private String name;

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

    protected Book(Parcel in) {
        name = in.readString();
    }

    //反序列化功能由CREATOR完成,在CREATOR的内部标明的如何创建序列对象和数组
    public static final Creator<Book> CREATOR = new Creator<Book>() {
        //从Parcel中反序列化对象
        @Override
        public Book createFromParcel(Parcel in) {
            //其内部调用Parcel的一系列readXXX方法实现反序列化过程
            return new Book(in);
        }
        //创建序列化数组
        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }
    //序列化过程:
    //重写writeToParcel方法,我们要在这里逐一对需要序列化的属性用Parcel的一系列writeXXX方法写入
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
    }
}

从上述代码注释可以看出,写一个实现Parcelable接口的类还是比较麻烦的,和Serailable相比,我们需要在writeToParcel中按序写入各个域到流中,同样,在createFromParcel中我们需要自己返回一个Book对象。

Parcelable在使用上也与Serializable稍有不同

public class TestActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test2);

        //获取一个Parcel容器
        Parcel parcel = Parcel.obtain();
        //需要序列化的对象
        Book book = new Book("c++");

        //把对象写入Parcel
        parcel.writeParcelable(book,0);
        //Parcel读写共用一个位置计数,这里一定要重置一下当前的位置
        parcel.setDataPosition(0);
        //读取Parcel
        Book book1 = parcel.readParcelable(Book.class.getClassLoader());
        Log.d("TestActivity",book1.toString());
    }
}

Parcelable的写

我们来看一下writeParcelable方法

[Parcel.java]

public final void writeParcelable(Parcelable p, int parcelableFlags) {
    //判断p是否为空
    if (p == null) {
        writeString(null);
        return;
    }
    //① 先写入p的类名
    writeParcelableCreator(p);
    //② 调用我们重写的writeToParcel方法,按顺序写入域
    p.writeToParcel(this, parcelableFlags);
}

public final void writeParcelableCreator(Parcelable p) {
    //① 先写入p的类名
    String name = p.getClass().getName();
    writeString(name);
}

Parcelable的读

我们来看readParcelable方法

[Parcel.java]

public final <T extends Parcelable> T readParcelable(ClassLoader loader) {
    //① 调用readParcelableCreator
    //这时获得就是我们自定义的CREATOR
    Parcelable.Creator<?> creator = readParcelableCreator(loader);

    if (creator == null) {
        return null;
    }
    // 判断当前creator是不是Parcelable.ClassLoaderCreator<?>的实例
    if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
      //如果是的话,,我们调用reateFromParcel(this, loader);
      Parcelable.ClassLoaderCreator<?> classLoaderCreator =
          (Parcelable.ClassLoaderCreator<?>) creator;
      return (T) classLoaderCreator.createFromParcel(this, loader);
    }
    //调用我们自定义的CREATOR中重写的createFromParcel方法
    return (T) creator.createFromParcel(this);
}

public final Parcelable.Creator<?> readParcelableCreator(ClassLoader loader) {
    //首先把类名读取出来
    String name = readString();

    Parcelable.Creator<?> creator;
    //mCreators做了一下缓存,如果之前某个classloader把一个parcelable的Creator获取过
    //那么就不需要通过反射去查找了

    synchronized (mCreators) {
        HashMap<String,Parcelable.Creator<?>> map = mCreators.get(loader);
        if (map == null) {
            map = new HashMap<>();
            mCreators.put(loader, map);
        }
        creator = map.get(name);
        if (creator == null) {
            try {
                ClassLoader parcelableClassLoader =
                        (loader == null ? getClass().getClassLoader() : loader);
                //加载我们自己实现Parcelable接口的类
                Class<?> parcelableClass = Class.forName(name, false,
                        parcelableClassLoader);

                Field f = parcelableClass.getField("CREATOR");
                Class<?> creatorType = f.getType();
                creator = (Parcelable.Creator<?>) f.get(null);
            }
            catch (Exception e) {
                    //catch exception
            }
            if (creator == null) {
                throw new BadParcelableException("Parcelable protocol requires a "
                        + "non-null Parcelable.Creator object called "
                        + "CREATOR on class " + name);
            }

            map.put(name, creator);
        }
    }

    return creator;
}

我们的测试例子读取Parcel

Book book1 = parcel.readParcelable(Book.class.getClassLoader());

可以看到我们在使用

readParcelable的时候,传入的参数是Book类的类加载器,根据我们上面的代码,我们知道我们先会通过反射获取定义在Book类中的CREATOR属性,我们回想一下在Book类中是怎么定义CREATOR的

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        //从Parcel中反序列化对象
        @Override
        public Book createFromParcel(Parcel in) {
            //其内部调用Parcel的一系列readXXX方法实现反序列化过程
            return new Book(in);
        }
        //创建序列化数组
        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

我们得到CREATOR属性后,调用它的createFromParcel方法,由多态可知调用的实际我们定义在CREATOR内的createFromParcel方法,在该方法内我们创建了Book对象(内部实现是通过Parcel的一系列readXXX方法)并返回。至此我们就得到了反序列化的对象


本篇总结

我们本篇详细分析了Android序列化相关知识,你可以使用Java中的Serializable也可以使用Parcelable。


下篇预告

前面的文章中是对前面所讲文章做一个小结。读者敬请期待哦。

此致,敬礼

原文地址:https://www.cnblogs.com/wangle12138/p/8257016.html

时间: 2024-10-03 22:50:37

Android开发之漫漫长途 X——Android序列化的相关文章

Android开发之漫漫长途 Ⅷ——Android Binder(也许是最容易理解的)

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解Android 卷Ⅰ,Ⅱ,Ⅲ>中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!另外,本系列文章知识可能需要有一定Android开发基础和项目经验的同学才能更好理解,也就是说该系列文章面向的是Android中高级开发工程师. 前言 我们在上一篇中比较详尽的介绍了Android的消息机

Android开发之漫漫长途 Fragment番外篇——TabLayout+ViewPager+Fragment

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解Android 卷Ⅰ,Ⅱ,Ⅲ>中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!! 前言 上一篇文章中我们使用底部导航+Fragment的方式实现了Android主流App中大都存在的设计.并命名其为"Fragment最佳实践",作为想到单独使用Fragment的用

Android开发之漫漫长途 XIV——RecyclerView

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解Android 卷Ⅰ,Ⅱ,Ⅲ>中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!! 前言 上文我们很详细的分析了ListView的使用.优化.及ListView的RecycleBin机制,读者如果对ListView不太清楚,那么请参看我的上篇博文.不过呢,Google Material

Android开发进阶:如何读写Android文件

Android主要有四大主要组件组成:Activity.ContentProvider.Service.Intent组成.Android文件的运行主要需要读写四大组件的文件.本文将介绍如何读写Android文件,希望对正在进行Android开发的朋友有所帮助. 文件存放位置 在Android中文件的I/O是存放在/data/data/<package name>/file/filename目录下. 提示:Android是基于linux系统的,在linux的文件系统中不存在类似于Windows的

Android开发面试经——4.常见Android进阶笔试题(更新中...)

Android开发(29)  版权声明:本文为寻梦-finddreams原创文章,请关注:http://blog.csdn.net/finddreams 关注finddreams博客:http://blog.csdn.net/finddreams/article/details/44301359 上一篇文章我们已经了解了Android笔试的一些基础题目, [<Android开发面试经——2.常见Android基础笔试题> ] (http://blog.csdn.net/finddreams/a

[Android开发那点破事]解决android.os.NetworkOnMainThreadException

[Android开发那点破事]解决android.os.NetworkOnMainThreadException 昨天和女朋友换了手机,我的iPhone 4S 换了她得三星I9003.第一感觉就是好卡,果断刷机.以前是Android 2.3的系统.回来刷成了4.4. 好了,问题来了.在我用手机测试我们的APP的时候,抛出一个如题的异常:android.os.NetworkOnMainThreadException 第一次看到这异常,字面意思是说:在主线程中的网络异常.然后我就去了解了下这个异常,

android开发里跳过的坑——android studio 错误Error:Execution failed for task &#39;:processDebugManifest&#39;. &gt; Manifest merger failed with multiple errors, see logs

使用AS在gradle里配置了多个定制版本,发现在编译版本切换时,会出现错误: Error:Execution failed for task ':processDebugManifest'.> Manifest merger failed with multiple errors, see logsInformation:Gradle tasks [:app:generatePhoneWulianDebugSources, :app:mockableAndroidJar, :app:prepa

Android 开发艺术探索——第十章 Android的消息机制

Android 开发艺术探索--第十章 Android的消息机制读书笔记 Handler并不是专门用于更新UI的,只是常被用来更新UI 概述 Android的消息机制主要值得就是Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑. MessageQueue即为消息队列,顾名思义,它的内部存储了一组消息,以队列的的形式对外提供插入和删除的工作.虽然叫队列,但内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表. Looper意思为循

Android开发面试经——3.常见Android进阶笔试题

关注finddreams博客:http://blog.csdn.net/finddreams/article/details/44301359 上一篇文章我们已经了解了Android笔试的一些基础题目,<Android开发面试经--2.常见Android基础笔试题> 但是做为一个有经验的开发者,仅仅知道基础题还是不够的,你的简历上说有两年以上工作经验的话,那面试官肯定会问一些深入性的问题,看你能否回答的出.所以为了找一个更好的工作,我们还需要去了解一下Android进阶的笔试题目: 1.什么是