序列化知识点总结
1. 序列化
在Java中,由于Java程序生成的数据变量都是保存在内存中,一旦程序结束,这些数据也会随之消失。如果想要将它们保存的更久一点,就会用上序列化。Android基于Java,所以概念同理。
具体来说,序列化的作用大体有如下几个:
- 长时间的保存数据到本地或者磁盘。
- 不同组件之间进行数据交换。
- 网络数据交换。
2. 基本用法
- 在开始之前,我会new一个新的Android Project,并且添加一个Empty Activity。
- 接下来边写文章边实践代码。
- 最终项目,都是正文的代码:https://github.com/wodongx123/SerializationDemo
- 先做一个场景的模拟,假设两个Activity要进行数据交换(通过Intent)。
- 在MainActivity中添加一个按钮,再添加一个新的SecondActivity,点击按钮切换到SecondActivity。
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 最基本的切换Activity代码 Button button = findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, SecondActivity.class); startActivity(intent); } }); } }
- 为了数据交换有一个实体,定义一个User类和User2类,里面就一个id和name以及setter和getter方法且两者内容完全相同。
public class User { private String id; private String name; public void setId(String id) { this.id = id; } public void setName(String name) { this.name = name; } public String getId() { return id; } public String getName() { return name; } }
2.1 Serializable
Java自带的序列化接口,用起来非常简单。
- 让目标类implement Serializable,并且新建一个字段serialVersionUID(如何快速新建serialVersionUID看4.1)。
public class User implements Serializable { private static final long serialVersionUID = -1418746155475547469L; private String id; private String name; public void setId(String id) { this.id = id; } public void setName(String name) { this.name = name; } public String getId() { return id; } public String getName() { return name; } }
- 然后就可以直接使用,我们将一个User的数据从MainActivity传到SecondActivity。
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button button = findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, SecondActivity.class); User user = new User(); user.setId("1111"); user.setName("2222"); intent.putExtra("user", user); startActivity(intent); } }); } }
- 再让SecondActivity读取,并且打印到log中(你愿意的话也可以用Toast)
public class SecondActivity extends AppCompatActivity { private static final String TAG = "SecondActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); Intent intent = getIntent(); User user = (User) intent.getSerializableExtra("user"); Log.i(TAG, "onCreate: " + user.getId() + " " + user.getName()); } }
- 结果(有个这个基本用法就很容易举一反三了)。
2.2 Parcelable
Parcelable是Android特有的序列化方式,也就是说Parcelable在Java上是用不了的,用法会比Serializable较难一些。
- 这回用User2类,先implements Parcelable,再为了消除报错添加相关代码,这些方法的作用我都写在注释中。
- 基本上用法比较死板,注意读取和保存的代码书写顺序(参数为Parcel的构造方法和writeToParcel方法)要完全一样即可(比如我保存读取时都是先id再name)。
- 一般都是用编译器自动生成相关代码,很省事。
public class User2 implements Parcelable { // 省略了setter和getter方法,实际上还在 private String id; private String name; /** * 从序列化结构中新建对象 */ protected User2(Parcel in) { id = in.readString(); name = in.readString(); } /** * 负责将序列化后的数据再反序列化,得到原先的数据 */ public static final Creator<User2> CREATOR = new Creator<User2>() { @Override public User2 createFromParcel(Parcel in) { return new User2(in); } @Override public User2[] newArray(int size) { return new User2[size]; } }; /** * 当前对象的描述,一般都是return 0,有特殊需要的时候才返回别的值 */ @Override public int describeContents() { return 0; } /** * 将当前对象写入序列化结构 */ @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(id); dest.writeString(name); } }
- 写入和读取这里不说别的,Intent来说用法完全相同,就是换了个方法名。
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button button = findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, SecondActivity.class); User2 user2 = new User2(); user2.setId("3333"); user2.setName("4444"); intent.putExtra("user2", user2); startActivity(intent); } }); } }
public class SecondActivity extends AppCompatActivity { private static final String TAG = "SecondActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); Intent intent = getIntent(); User2 user2 = intent.getParcelableExtra("user2"); Log.i(TAG, "onCreate: " + user2.getId() + " " + user2.getName()); } }
3. Serializable和Parcelable的特点和区别
既然Java已经有Serializable了,那为何Android还要特地再弄一个Parcelable来进行序列化呢。
- Serializable再Java设计出来的初衷是为了数据的硬盘存储和网络交互,所以Serializable主要是通过IO读写到硬盘上进行序列化,序列化的过程中还用上了反射的技术。
- Parcelable的设计初衷就是在Android平台上,Serializable的效率太慢,使用Parcelable可以通过内存进行序列化和反序列化,这样在Android平台上,Parcelable的效率就会比Serializable高很多。
- Serializable在运行时会产生大量的临时变量,容易引起GC。
- 无论是Serializable还是Parcelable,反序列化后生成的对象都不是同一对象,只是里面的内容相等而已。
所以使用一般如下:
- 在涉及到硬盘存储和网络连接的时候序列化使用Serializable。
- 在涉及到不同组件或者不同进程直接通信的时候,使用Parcelable。
4. 其他
4.1 Android Studio如何快速生成serialVersionUID
Windows:
- File–>Settings–>Editor–>Inspections–>Java–>Serialization issues–>Serializable class without ‘serialVersionUID’ 勾选中该选项,退出。
- 进入实现了Serializable中的类,选中类名(比如正文的User类),Alt+Enter弹出提示,然后直接导入完成。
MAC:
- Android Studio–>Preference–>Editor–>Inspections–>Java–>Serialization issues–>Serializable class without ‘serialVersionUID’ 勾选中该选项,退出。
- 进入实现了Serializable中的类,选中类名(比如正文的User类),Alt+Enter弹出提示,然后直接导入完成。
4.2 关于serialVersionUID有什么用
它用于判断序列化和反序列化后的类是否是同一个类,通过比较两个值是否相等的方式。如果你没有定义这个值,那么java会根据类生成的class文件自动给出一个serialVersionUID值,如果这个类哪天多了空格,少了一行什么的,给出的值就会改变。这个时候再判断,就会变成不同的类了。所以一般我们都会主动定义一个定值,来避免这种识别错误的情况
4.3 两种序列化的使用率比较低
一般现在的项目中,不怎么会用上Serializable和Parcelable的序列化内容。正常都是用EventBus或LiveData进行不同组件中的通信;网络通信那就是直接用json串了。所以本文的实践意义不大,仅供入门学习。
参考材料
Android序列化总结 - 简书
https://www.jianshu.com/p/208ac4a71c6f
Android中Parcelable的原理和使用方法 - 简书
https://www.jianshu.com/p/df35baa91541