标签(空格分隔): 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能配置序列化使用的getter
、setter
和non-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 {}
这个例子,只有One
和Three
会被序列化,避开了Two
和BaseClass
的参数
定义打包过程
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