1 Android中用Intent传递数据
1.1 基本类型数据
//发送方
Intent intent=new Intent(FirstActivity.this,Second.class);
intent.putExtra("string_data","hello");
intent.putExtra("int_data",100);
startActivity(intent);
//接收方
getIntent().getStringExtra("string_data");
getIntent().getIntExtra("int_data",0);
1.2 Bundle类型数据
Intent intent=new Intent(FirstActivity.this,Second.class);
Bundle bundle=new Bundle();
bundle.putString("string_data","hello");
bundle.putInt("int_data",100);
intent.putExtras(bundle);
startActivity(intent);
Bundle bundle=getIntent().getExtras();
String s=bundle.getString("string_data");
int a=bundle.getInt("int_data");
使用intent可以在Android四大组件中的三大组件(Activity,Service,Receiver)间传递数据,也可以在进程间传递数据;但是使用Intent传递的对象必须是可序列化的,该对象所属类必须实现Serializable/Externalizable/Pacelable其中一个接口;基本数据类型天生是可序列化的,Bundle实现了Pacelable接口。
2 对象序列化
2.1 序列化和反序列化概念
1.定义
序列化:把对象转换为字节序列的过程;
反序列化:把字节序列恢复为对象的过程;
字节序列包括类名、属性类型、属性名、属性值等信息;
2.用途
对象的序列化主要有两种用途:
- 本地存储:把对象的字节序列永久地保存到硬盘上,通常存放在一个二进制文件中;
- 网络传输:在网络上传输对象的字节序列。
2.2 序列化和反序列化过程
- 序列化:
ObjectOutputStream
的writeObject(Object obj)
方法; - 反序列化:
ObjectInputStream
的readObject(Object obj)
方法;
使用Intent和Binder传递的对象要求是可序列化的(只要求实现接口),但是本地存储或者网络传输的对象要求是序列化后的(既要实现接口,又要调用writeObject)。
一个Person实体类:
public class Person implements Serializable {//当然也可以是其他俩接口
private String name;
private int age;
private static boolean sex=true;//static属性不能被序列化
private transient int action;//transient不能被序列化
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;
}
public int getAction() {
return action;
}
public void setAction(int action) {
this.action = action;
}
}
序列化和反序列化测试
public class Test {
public static void main(String[] args) {
Person p=new Person();
p.setName("Tom");
p.setAge(22);
p.setAction(1);
serialize(p);
deserialize();
}
//序列化过程
public static void serialize(Person person){
try {
FileOutputStream fileOut=new FileOutputStream("person.txt");
ObjectOutputStream out=new ObjectOutputStream(fileOut);
out.writeObject(person);//序列化
out.close();
fileOut.close();
System.out.println("serialize success! ");
} catch (IOException e) {
e.printStackTrace();
}
}
//反序列化过程
public static void deserialize(){
try {
FileInputStream fileIn=new FileInputStream("person.txt");
ObjectInputStream in=new ObjectInputStream(fileIn);
Person person=(Person) in.readObject();//反序列化
System.out.println(person.getName()+" , "+person.getAge()+" , "+person.getAction());
in.close();
fileIn.close();
System.out.println("Deserialize success! ");
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
运行结果:
serialize success!
Tom , 22 , 0
Deserialize success!
先将一个Person对象序列化,但是不序列化带有static ,transient关键字的属性;所以反序列化后action输出值为默认初值0。
2.3 可序列化的方式
2.3.1 Serializable方式
1.特征
- static ,transient属性不能被序列化;
- 实现该接口的类默认无参构造函数要能被访问;
- 原理:将一个对象转换成可存储或可传输的状态;
- 特征:使用简单效率低,开销大;因为序列化和反序列化过程需要大量的IO操作。
2.可序列化
public class Person implements Serializable {
private static final long serialVersionUID = -2799179422238343038L;
private String name;
private int age;
private static boolean sex=true;//static属性不能被序列化
private transient int action;//transient不能被序列化
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;
}
public int getAction() {
return action;
}
public void setAction(int action) {
this.action = action;
}
}
3.使用Intent传递可序列化对象
现在Person对象是可序列化的,可以使用Intent传递。
发送方:
Person person=new Person();
person.setName("Tom");
person.setAge(22);
person.setAction(1);
Intent intent=new Intent(FirstActivity.this,Second.class);
intent.putExtra("person_data",person);
startActivity(intent);
接收方
Person person=(Person) getIntent().getSerializableExtra("person_data");
4. serialVersionUID
在运行时这个版本号唯一标识了一个可序列化的类。
也就是说,一个类序列化时,运行时会保存它的版本号,然后在反序列化时检查你要反序列化成的对象版本号是否一致,不一致的话就会报错:InvalidClassException
。
建议显示写写上一个serialVersionUID,不写也会有一个默认的serialVersionUID。可以在IDE里进行一些设置,当没显示写明serialVersionUID时会报一个Warning,然后可以使用IDE自动生成的serialVersionUID。serialVersionUID是根据类信息计算的,对类进行一点小改动,serialVersionUID值就会发生变化。
在这里我对这个Person类做一点小改动,你会发现自动生成的serialVersionUID变了。
public class Person implements Serializable {
private static final long serialVersionUID = -3956446717616766848L;//变了
private String name;
private int age;
private static boolean sex=true;//static属性不能被序列化
private transient int action;//transient不能被序列化
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;
}
public int getAction() {
return action;
}
public void setAction(int action) {
this.action = action;
}
public static boolean isSex() {//改动
return sex;
}
public static void setSex(boolean sex) {//改动
Person.sex = sex;
}
}
序列化和反序列化测试代码
public static void main(String[] args) {
Person p=new Person();
p.setName("Tom");
p.setAge(22);
p.setAction(1);
// serialize(p);
deserialize();
}
报异常
java.io.InvalidClassException: Person; local class incompatible: stream classdesc serialVersionUID = -2799179422238343038, local class serialVersionUID = -3956446717616766848
serialVersionUID验证:在反序列化时,Person person=(Person) in.readObject();
左边是serialVersionUID改变后的类引用,右边从流中读取的是之前的serialVersionUID类的对象。所以会抛异常,导致反序列化失败。总言之,如果类信息有改动,会导致异常,反序列化失败。
但是如果只改动类,却不更新版本号(也是程序员常规操作),并不会报异常,也能成功反序列化。
如果我们不手动指定serialVersionUID,系统会隐式地自动生成一个版本号;当类信息发生改变时,会自动更新这个版本号。如果我们在序列化后修改类信息,然后进行反序列化操作,会出现反序列化失败情况。
总结:实现了Serializable接口的类要,手动指定serialVersionUID,可以直接赋值任意long值,也可由IDE生成导出。注意类信息更新后不要手贱手动去更新serialVersionUID,这样会导致反序列化失败。
2.3.2 Externalizable方式
1.特征
可以指定要序列化哪些属性。
Externalizable接口继承了Serializable接口,增加了writeExternal(ObjectOutput out),readExternal(ObjectInput in)
两个抽象方法;在这两个方法中可以指定要序列化的属性。一个类对象想要可序列化,可以去实现Externalizable接口,同时复写这两个抽象方法。当序列化和反序列化化时自动回调这两个方法。
和Serializable一样,实现该接口的类默认无参构造函数要能被访问。
2.可序列化
public class Person implements Externalizable {
private static final long serialVersionUID = -4025176066206538191L;
private String name;
private int age;
private static boolean sex=true;//static属性不能被序列化
private transient int action;//transient不能被序列化
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name=(String)in.readObject();
}
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;
}
public int getAction() {
return action;
}
public void setAction(int action) {
this.action = action;
}
}
运行结果
serialize success!
Tom , 0 , 0
Deserialize success!
我们在writeExternal和readExternal指定了可序列化和反序列化的属性只有name,所有age并没有被序列化。所以在反序列化时会去调用默认无参构造器给这个没有被序列化的属性赋值。这也就解释了输出age为0,同时也解释了,实现了Serializable或者Externalizable接口的类的默认无参构造器一定要可以访问。
2.3.3 Parcelable方式
Parcelable时Android中的一种高效可序列化方式,不同于
Serializable的是,它是把一个完整的对象进行分解,而分解后的每一部分都是Intent支持的数据类型,这也就实现了用Intent传递对象的功能了。
和Serializable一样,Parcelable不支持static和transient属性序列化。
public class Person implements Parcelable {
//...
@Override
public int describeContents() {//一般都返回0
return 0;
}
//将Person对象写到parcel对象中
@Override
public void writeToParcel(Parcel parcel, int i) {//序列化时调用
parcel.writeString(name);//写name
parcel.writeInt(age);//写age
}
protected Person(Parcel in) {
name = in.readString();
age = in.readInt();
}
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {//反序列化时调用
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
}
将Person类实现Pacelable接口后,按⌥ + ↩︎
(MAC OS)可自动导入序列化相关代码。
这样,Person对象就是可序列化的,可以用Intent传递。发送方代码和Serializable一样,接收方代码也很简单:
Person person=(Person) getIntent().getParcelableExtra("person_data");
3.总结
3.1 序列化方式不同点(重点)
Serializabl | Parcelable | |
---|---|---|
平台 | Java序列化接口 | Android序列化接口 |
原理 | 将一个对象转换成可存储或可传输的状态 | 将一个对象进行分解,而分解后的每一部分都是 Intent 所支持的数据类型 |
优缺点 | 使用简单效率低 | 高效但使用麻烦 |
使用场景 | Java;对象的本地存储或网络传输 | Android;内存序列化,比如Intent,Binder |
PS:使用Intent传递数据有大小限制,最大1MB;