Parceler

标签(空格分隔): Android Serialize 翻译 code_generator
Android的Parcelable代码生成器 原码


[TOC]

介绍

在Android 中,Parcelables是在Contexts之前传递数据非常棒的类序列化方式。和传统的Serialization相比(两者之前的差异),parcelable在进行序列化和反序列化时所需要的时间是传统序列化的十分之一。然而Parcelables有一个严重的瑕疵就是包含大量模板代码。实现一个Parceable,必需实现writeToParcel()createFromParcel() 方法,按同样的顺序读和写转化成Parcel。而且,为了使能获取拆包的类型, Parcelable 必需定义public final static Parcelable.Creator CREATOR<T>变量,明确T的类型。更多关于Pacelable接口参数Parcelable接口的使用
Parceler 是一个代码代码生成器包,用于生成Android Parcelable 模板代码。你不再需要实现Parcelable接口,实现 writeToParcel() 、 createFromParcel() 方法,定义 the public static final CREATOR变量。只需要使用@Parcel注解POJO类,Parcelerw会做上面的工作。Parceler使用the Java JSR-269 Annotation Processor,所以我们不再需要手动运行工具去生成Parcelable代码。仅仅注解Java Bean,编译完成,默认(可配置)情况下Parceler会直接序列化Java Bean域.

@Parcel
public class Example {
    String name;
    int age;

    public Example(){ /*Required empty bean constructor*/ }

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

    public String getName() { return name; }

    public int getAge() { return age; }
}

注意:使用默认的序列策略,不能定义private 变量,因为它会产生反射的性能损失。(定义成private也是能编译和运行的)

能直接引用生成的类Example$Parcelable,或者通过Parcels工具类:

Parcelable wrapped = Parcels.wrap(new Example("Andy", 42));
Example example = Parcels.unwrap(wrapped);
example.getName(); // Andy
example.getAge(); // 42

当然,打包了的Parcelable能直接加入到Android Bundle中在不同Activity之前传递。

Bundle bundle = new Bundle();
bundle.putParcelable("example", Parcels.wrap(example));
Example example = Parcels.unwrap(getIntent().getParcelableExtra("example"));

打包和拆包技术是工厂方式很好实现。此外,Parceler被以下libraries支持。

  • Transfuse - Allows @Parcel annotated beans to be used with the @Extra injection.
  • FragmentArgs - Uses the ParcelerArgsBundler adapter to wrap and unwrap @Parcel annotated beans with fragment parameters.
  • Dart - Autodetects @Parcel annotated beans and automatically unwraps them when using @InjectExtra.

Parcel支持的属性类型

仅有一部分类型能用于注解的@Parcel类的属性,以下列表包括能映射的类型:

  • byte
  • double
  • float
  • int
  • long
  • char
  • boolean
  • String
  • IBinder
  • Bundle
  • SparseArray of any of the mapped types*
  • SparseBooleanArray
  • List, ArrayList and LinkedList of any of the mapped types*
  • Map, HashMap, LinkedHashMap, SortedMap, and TreeMap of any of the mapped types*
  • Set, HashSet, SortedSet, TreeSet, LinkedHashSet of any of the mapped types*
  • Parcelable
  • Serializable
  • Array of any of the mapped types
  • Any other class annotated with @Parcel

如果泛型参数不能被映射Parcel会报错

Parceler 直接支持上面的所有类型,这非常方便在处理集合类:

Parcelable listParcelable = Parcels.wrap(new ArrayList<Example>());
Parcelable mapParcelable = Parcels.wrap(new HashMap<String, Example>());

多态

注意Parceler不能拆包继承关系,所以任何多态域都会被解压成基础类,之所以这样是因为Parceler 选择了性能,并没有校验每份数据.getClass()

@Parcel
public class Example {
    public Parent p;
    //必需添加@ParcelConstructor注解,否则会因为找不到默认构造函数报错
    Example(Parent p) { this.p = p; }
}

@Parcel public class Parent {}
@Parcel public class Child extends Parent {}

Example example = new Example(new Child());
System.out.println(example.p instanceof Child); // true
example = Parcels.unwrap(Parcels.wrap(example));
System.out.println(example.p instanceof Child); // false
/* 说明一下,上面测试代码如果在同一个context里面则两个都是true,看Parcels.unwrap就知道,他是直接获取的之前的对象,中间并没有打包和拆包的过程。*/

参考自定义序列化部分,实现多态域。

序列化策略

Parceler 除了上面以基本域的序列化方法外,还提供几种选择,如何去序列化和反序列化一个对象。

Getter/setter serialization

Parceler能配置序列化使用的gettersetternon-empty constructor.另外,field可通过@ParcelProperty注解与方法和构造函数的参数关联,这将支持很多bean,包括不可变的bean
配置使用getter/settter方法序列化,只需简单给@Parcel注解设置其value值为Serialization.BEAN

@Parcel(Serialization.BEAN)
public class Example {
    private String name;
    private int 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; }
}

使用带参构造函数序列化,必需在带参函数上添加 @ParcelConstructor注解:

@Parcel(Serialization.BEAN)
public class Example {
    private final String name;
    private final int age;

    @ParcelConstructor
    public Example(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public String getName() { return name; }

    public int getAge() { return age; }
}

默认会使用无参构造函数,除非带参构造函数有注解。如果没有无参构造函数且没@ParentConstructor注解的带参构造函数,编译无法通过。

getters/setters and fields混合使用

你能混合使用序列化策略,使用@ParcelProperty。下面的例子中firstName和lastName使用构造函数打包,然而拆包fistName通过引用直接读取,而lastName则通过getLastName()方法读取。first、last作为@ParcelProperty()标识,必需成对出现 。

@Parcel
public class Example {
    //关联使用该注解的属性
    @ParcelProperty("first")
    String firstName;
    String lastName;

    @ParcelConstructor
    public Example(@ParcelProperty("first") String firstName, @ParcelProperty("last") String lastName){
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() { return firstName; }

    @ParcelProperty("last")
    public String getLastName() { return lastName; }
}

不需要序列化的属性能在其getter/settter方法上注解@Transient,另外带transient关键字的域也不会被序列化。

Parceler 支持不同种类的POJO,允许@Parcel注解的类用于其它处理POJO的包,包括以下

静态工厂支持

直接使用构造函数是可选的,Parceler支持使用注解静态工厂创建给定类的实例。这种实现支持Google的AutoValue注解处理器生成不可变beans. 实现了AutoValue的抽象类,通过 @ParcelFactory 注解工厂方法,映射成有@Parcel的类。
使用@AutoValue可能会报空指针异常,问题可参考https://github.com/google/auto/issues/240

@AutoValue
@Parcel
public abstract class AutoValueParcel {

    @ParcelProperty("value") public abstract String value();

    @ParcelFactory
    public static AutoValueParcel create(String value) {
        return new AutoValue_AutoValueParcel(value);
    }
}

AutoValue 能生成同的类,因此告诉Parcles工具类应该实例化哪个类:

Parcelable wrappedAutoValue = Parcels.wrap(AutoValueParcel.class, AutoValueParcel.create("example"));

反序列化

AutoValueParcel autoValueParcel = Parcels.unwrap(wrappedAutoValue);

自定义序列化

@Parcel可通过@ParcelPropertyConverter(ParcelConverter)去指定任意一个field序列化过程.
下面例子演示了使用ParcelConverter反序化有继承关系的类

@Parcel
public class Item {
    @ParcelPropertyConverter(ItemListParcelConverter.class)
    public List<Item> itemList;
}
@Parcel public class SubItem1 extends Item {}
@Parcel public class SubItem2 extends Item {}

public class ItemListParcelConverter implements ParcelConverter<List<Item>> {
    @Override
    public void toParcel(List<Item> input, Parcel parcel) {
        if (input == null) {
            parcel.writeInt(-1);
        }
        else {
            parcel.writeInt(input.size());
            for (Item item : input) {
                parcel.writeParcelable(Parcels.wrap(item), 0);
            }
        }
    }

    @Override
    public List<Item> fromParcel(Parcel parcel) {
        int size = parcel.readInt();
        if (size < 0) return null;
        List<Item> items = new ArrayList<Item>();
        for (int i = 0; i < size; ++i) {
            items.add((Item) Parcels.unwrap(parcel.readParcelable(Item.class.getClassLoader())));
        }
        return items;
    }
}

Parceler在api的org.parcler.converter包下实现一系列对集体的转换,这些类处理了一些判空和迭代器,上面的ParcelConverter可以简单的实现:

public class ItemListParcelConverter extends ArrayListParcelConverter<Item> {
    @Override
    public void itemToParcel(Item item, Parcel parcel) {
        parcel.writeParcelable(Parcels.wrap(item), 0);
    }

    @Override
    public Item itemFromParcel(Parcel parcel) {
        return Parcels.unwrap(parcel.readParcelable(Item.class.getClassLoader()));
    }
}

没有source 的 classes

对于那些没有Java source 的classses ,也能 通过使用@ParcelClass注解进行序列化。这个注解能方便有用于可编译资源的任何地方。例如,在Android Appliction 中可对LibarayParcel.class 序列化:

@ParcelClass(LibraryParcel.class)
public class AndroidApplication extends Application{
    //...
}

序列化多个类时使用@ParcelClasses注解

另外,@ParcelClass关联的类可以annotation属性配置其将要使用的@Parcel注解的参数,这样我们就能配置关联类的序列化策略,以及哪些类需要分析。
一个有用的策略,规定一个类型使用自定的converters.

@ParcelClass(
    value = LibraryParcel.class,
    annotation = @Parcel(converter = LibraryParcelConverter.class))
class SomeClass{}

这将在class的基础上进行控制,所以不能再动态更改。

高级配置

跳过分析

libraries中可能需要bean去扩展一个base class,并要求基类中的数据不需要打包。Parceler可通过analysis配置该类中的哪基类类数据打包:

@Parcel(analyze = {One.class, Three.class})
class One extends Two {}
class Two extends Three {}
class Three extends BaseClass {}

这个例子,只有OneThree会被序列化,避开了TwoBaseClass的参数

定义打包过程

Parcels工具类分类查找所有需要打包的类,由于性能优化的原因它直接忽略了class的继承关系,无法对@Parcel的bean的子类进行打包.有两种方法可解决这个问题:
第一种,通过implementations参数去关系他的子类:

class ExampleProxy extends Example {}
@Parcel(implementations = {ExampleProxy.class})
class Example {}

ExampleProxy proxy = new ExampleProxy();
Parcels.wrap(proxy);  // ExampleProxy will be serialized as a Example

第二种,在使用Parcels.wrap()方法时,指定要打包的类(已注解@Parcel,并且是继承关系)

ExampleProxy proxy = new ExampleProxy();
Parcels.wrap(Example.class, proxy);

避免indexing错误

Libraries中使用Parceler将会面临挑战,因为Parceler生成唯一个映射类Parceler$$Parcels去关联一个bean和bean所对应的Parcelable,编译时这个映射类可能冲突,并会抛出下面的错误:

Error Code:
    2
Output:
    UNEXPECTED TOP-LEVEL EXCEPTION:
    com.android.dex.DexException: Multiple dex files define Lorg/parceler/Parceler$$Parcels$1;
        at com.android.dx.merge.DexMerger.readSortableTypes(DexMerger.java:594)
        at com.android.dx.merge.DexMerger.getSortedTypes(DexMerger.java:552)
        at com.android.dx.merge.DexMerger.mergeClassDefs(DexMerger.java:533)
        ....

为了避免去这个公共的映射类,设置parcelsIndex = false到每一个使用@Parcel注解的类,Parceler将不会生成Parceler$$Parcels映射类,若没有没映射做为索引,Parceler会退回去根据类名查找生成的class
或者,在顶层项目使用@ParcelClass配置,而不需要在每@Parcel注解上进行配置。

配置混淆

对于使用了代码混淆,添加以下几行到你的混淆配置文件里。这将保留Parcels 相关工具类以及Parcelable CARETOR [静态工厂]实例

# Parcel library
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}

-keep class org.parceler.Parceler$$Parcels

引入Parceler

Maven

<dependency>
    <groupId>org.parceler</groupId>
    <artifactId>parceler</artifactId>
    <version>1.0.4</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.parceler</groupId>
    <artifactId>parceler-api</artifactId>
    <version>1.0.4</version>
</dependency>

Gradle

compile "org.parceler:parceler-api:1.0.4"
apt "org.parceler:parceler:1.0.4"

详细使用请参考android-apt project

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容

  • Android跨进程通信IPC整体内容如下 1、Android跨进程通信IPC之1——Linux基础2、Andro...
    隔壁老李头阅读 11,545评论 6 38
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,375评论 6 343
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,103评论 18 139
  • 古典老师提出的一个观点是,好的心态其实是一个良好的心理模式,人与人之间的智商不会相差很多,只是心智运营模式不同罢了...
    Sunny莹阅读 656评论 0 1
  • 《杀戮与文化》一书中第一章中讲到了小居鲁士雇佣希腊人做雇佣兵,帮助其复国,希腊雇佣兵伤亡惨重,他们还是坚持...
    从彦阅读 1,931评论 0 2