Andorid Parcelable序列化遇见List、数组、Map如何处理

Andorid Parcelable序列化遇见List、数组、Map如何处理

安卓开发中,如果遇见需要序列化的场景,我们一般都会把我们的Bean类实现Parcelable接口,如下:

import android.os.Parcel;
import android.os.Parcelable;

public class Test implements Parcelable {
    private int a;
    private String s;

    protected Test(Parcel in) {
        a = in.readInt();
        s = in.readString();
    }

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

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

    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }

    public String getS() {
        return s;
    }

    public void setS(String s) {
        this.s = s;
    }

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

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

这里稍作解释:

1、describeContents(),该方法不用管,一般直接reurn 0就可以了。

2、writeToParcel,该部分是序列化时候写的逻辑,调用的事Parcel提供的API,可以writeInt、writeLong、writeString等,基本数据类型都可以支持。

3、Creator接口实现类,这个一般也都是模板化的,只需要泛型替换成当前需要序列化的类即可。

4、形参为Parcel的构造方法,构造方法是序列化读的逻辑,和写过程对应,也都是利用Parcel提供的API进行读取,基本数据类型当然可以支持的,调用readInt、readLong、readString即可。

上面需要注意的一点是成员变量写的顺序要和读的顺序保持一致,因为序列化其实就是把类的成员信息写到二进制流里面,接收端接受二进制流然后解析二进制流,构造我们需要的对象。

好了,介绍了实现Parcelable的基本知识,下面我们来介绍一些特殊情况下如何实现Parcelable。

1、遇到自定义对象成员如何序列化

这里的意思是我们要序列化的Bean A里面有一个成员变量的数据类型是另一个Bean B,这时候有两种处理方法:

(1)B类实现Serializable接口

实现Serializable接口很简单,只需要加上implements Serializable即可,也不需要实现什么方法。那么在A类里面在writeToParcel方法里面,往Parcel里面写入的时候,调用方法public final void writeSerializable(Serializable s)。在构造方法里面从Parcel里面读取的时候,调用public final Serializable readSerializable(),如下面代码所示:

import android.os.Parcel;
import android.os.Parcelable;
import java.util.List;

public class Test implements Parcelable {
    private int a;
    private String s;
    private Another another;

    。。。。。

    protected Test(Parcel in) {
        a = in.readInt();
        s = in.readString();
        another = (Another) in.readSerializable();
    }

   。。。。。。

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(a);
        dest.writeString(s);
        dest.writeSerializable(another);
    }
}

(2)B类实现Parcelable接口

B类实现Parcelable接口,A类的writeToParcel方法里面,往Parcel里面写入的时候,调用方法public final void writeParcelable(Parcelable p, int parcelableFlags),在构造方法里从Parcel读取的时候,调用方法public final <T extends Parcelable> T readParcelable(ClassLoader loader),如下面代码:

public class Test implements Parcelable {
    private int a;
    private String s;
    private Another another;

    。。。。。。

    protected Test(Parcel in) {
        a = in.readInt();
        s = in.readString();
        another = in.readParcelable(Another.class.getClassLoader());
    }

    。。。。。。

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(a);
        dest.writeString(s);
        dest.writeParcelable(another,0);
    }
}

2、遇到List类型成员变量如何序列化

这里又要根据List里面的元素的实际类型来区分处理:

(1)List的元素类型是String类型,我们在writeToParcel方法里面,往Parcel里面写入的时候,调用方法

public final void writeStringList(List<String> val)。在构造方法从Parcel里面读的时候,调用public final void readStringList(List<String> list)或者public final ArrayList<String> createStringArrayList()方法。如下所示:

import android.os.Parcel;
import android.os.Parcelable;
import java.util.List;

public class Test implements Parcelable {
    private int a;
    private String s;
    private List<String> strList;

    。。。。。

    protected Test(Parcel in) {
        a = in.readInt();
        s = in.readString();
        strList = in.createStringArrayList();
    }

    
    。。。。。

  
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(a);
        dest.writeString(s);
        dest.writeStringList(strList);
    }
}

(2)List的元素类型是自定义对象类型,那么这个自定义对象必须实现Parcelable接口,Serializable接口这种情境下是不支持的。这种情况下,我们在writeToParcel方法里面,往Parcel里面写入的时候,调用方法 public final <T extends Parcelable> void writeTypedList(List<T> val) ,在构造方法里面从Parcel里面读取的时候,调用方法public final <T> ArrayList<T> createTypedArrayList(Parcelable.Creator<T> c)或者public final <T> void readTypedList(List<T> list, Parcelable.Creator<T> c)。示例代码如下:

public class Test implements Parcelable {
    private int a;
    private String s;
    private List<Another> anothers;

    。。。。。。

    protected Test(Parcel in) {
        a = in.readInt();
        s = in.readString();
        anothers = in.createTypedArrayList(Another.CREATOR);
    }

    。。。。。。

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(a);
        dest.writeString(s);
        dest.writeTypedList(anothers);
    }
}

这里补充提示一点,List里面的元素如果没有实现Parcelable接口,是不能被Parcelable支持的。

3、遇见数组类型的成员变量如何处理

如果遇见数组类型的成员变量,比如int[],我们在writeToParcel方法里面往Parcel里面写入的时候,可以调用其方法

public final void writeIntArray(int[] val) {
    if (val != null) {
        int N = val.length;
        writeInt(N);
        for (int i=0; i<N; i++) {
            writeInt(val[i]);
        }
    } else {
        writeInt(-1);
    }
}

在构造方法里面从Parcel里面读取的时候可以调用其方法

public final void readIntArray(int[] val) {
    int N = readInt();
    if (N == val.length) {
        for (int i=0; i<N; i++) {
            val[i] = readInt();
        }
    } else {
        throw new RuntimeException("bad array lengths");
    }
}

这里之所以要贴一下源码,是因为从源码我们要知道一个问题,就是我们的数组对象必须是一个长度确定的数组,否则就会抛异常。

4、遇见Map类型的成员时如何序列化

遇见成员类型是Map时,其实key和value是基本数据类型、或者是实现了Parcelable或者Serializable接口的自定义类时,都是可以被支持的。在writeToParcel方法里面往Parcel里面写入的时候,可以调用其方法

public final void writeMap(Map val) {
    writeMapInternal((Map<String, Object>) val);
}

在构造方法里面从Parcel里面读取的时候可以调用其方法

public final void readMap(Map outVal, ClassLoader loader) {
    int N = readInt();
    readMapInternal(outVal, N, loader);
}

示例代码如下:

public class Test implements Parcelable {
    private int a;
    private String s;
    private Map<Integer,Another> map;

    。。。。。。

    protected Test(Parcel in) {
        a = in.readInt();
        s = in.readString();
        map = new HashMap<>();
        in.readMap(map,getClass().getClassLoader());
    }
    
    。。。。。。

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(a);
        dest.writeString(s);
        dest.writeMap(map);
    }
}

需要注意一点的是,在读取的时候,调用readMap方法是需要传入一个类构造器的,这个一定要注意,如果你传的是String.class.getClassLoader或者Integer.class.getClassLoader,那么如果key或者value是自定义类型,那么可能会导致类构造器找不到类,因为String类的构造器是RootClassLoader,其找不到我们应用程序自定义的类,会直接报异常如下:

Caused by: java.lang.ClassNotFoundException: com.example.localuser.retrofittest.SerializeTest.Another
        at java.lang.Class.classForName(Native Method)
        at java.lang.Class.forName(Class.java:453)
        at android.os.Parcel$2.resolveClass(Parcel.java:2927)
        at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1615)
        at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1520)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1776)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)

解决办法就是直接传入当前序列化的类的类构造器就可以了,该类构造器也可以满足String、Integer等对象的构造,因为系统构造器找不到类会交给父类构造器去查找类的,这是类构造器的双亲委派机制,所以这个地方的类构造器选择级别低的构造器就不会有问题了。

 

综述,上面是开发过程中遇到序列化时候的一些经验积累,这里总结一下供大家参考,希望可以有所帮助,如有问题欢迎指正。

  • 11
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
根据引用\[1\]和引用\[3\]的内容,可以得知有一位博主在一年前发布了一些Android项目和教程,旨在分享给大家,帮助解决遇到的难题,少走弯路。这些教程包括Android基础教程,涵盖了常见界面布局、常见界面控件、程序活动单元Activity、数据存储、广播机制、服务和Android事件处理等方面的内容。这些教程详细、细心,而且博主还免费分享了源码,受到了很多人的肯定和支持。所以,如果你想学习Android开发,可以参考这些教程来提升自己的技能。根据引用\[2\]的内容,Android开发相关的人才需求量依旧不减,尤其是高级的Android架构师非常受欢迎。对于初中级Android工程师来说,想要提升技能,建议系统地学习,避免自己摸索成长,以提高学习效果。可以选择以视频学习为主,辅以图书查漏补缺。在学习过程中遇到问题可以通过百度等搜索引擎寻找解答,因为入门的问题通常会有很多人遇到并给出了比较好的解答。 #### 引用[.reference_title] - *1* [史上最全的Android基础教程+入门实战训练+处理技巧(建议收藏)|寻找C站宝藏](https://blog.csdn.net/qq_42257666/article/details/117952517)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [【Android】全网最详细的Android入门基础教程,零基础速领](https://blog.csdn.net/datian1234/article/details/126777580)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Android海纳百川

打赏加微信,送跑车加管理

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值