[TOC]
## Serializable(谨慎使用序列化)
序列化和持久化很相似,有些人甚至混为一谈,其实还是有区别的,**序列化是为了解决对象的传输问题,传输可以在线程之间、进程之间、内存外存之间、主机之间进行**。在安卓中绝大部分场景是通过Binder来进行对象的传输.
### serialVersionUID
如果没有指定UID,那么JDK会自动帮我们生成一个UID.手动指定这个UID可以减少运行时的开销,如果我们不加,系统后根据通过一套复杂的运算,自动赋值,该值与类名,接口名,成员名都有关.
### 增加了潜在的风险
引入了隐藏的构造函数
### 测试的代价大
每个版本都得保证序列化和反序列化的成功
### 保护字段不被序列化
1. transient关键字修饰的变量,不会被序列化;Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
2. static关键字修饰的也不会被序列化, 因为static是类的特征,和对象无关。
3. 放在父类中,如果父类实现了序列化,子类默认实现序列化。
### 原理
![](https://img.kancloud.cn/6b/a4/6ba4a5d08cc26eec260570795eca819c_740x286.png)
## Parcelable
### 源码解析
首先我们在一个实体对象在实现parcelable的时候,这个时候,我们会重写writeToParcel方法,其中执行 dest.writeInt(this.offLineBtn); writeLong 等等类型的数据,实际是执行native方法,在这里我们就不分析各种数据类型的存取了,我们现在拿一个代表int来分析下,看下jni方法:
~~~
static void android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, jint nativePtr, jint val) {
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
const status_t err = parcel->writeInt32(val);
if (err != NO_ERROR) {
signalExceptionForError(env, clazz, err);
}
}
~~~
在这里我们要特别注意两个参数,一个是之前传上去的指针以及需要保存的int数据,这两个值分别是:
(jint nativePtr,jint val)
首先是根据这个指针,这里说一下,指针实际上就是一个整型地址值,所以这里使用强转将int值转化为parcel类型的指针是可行的,然后使用这个指针来操作native的parcel对象,即:
const status\_t err = parcel->writeInt32(val);
writeInt32是调用了parcel中的方法,parcel的实现类是在Framework/native/libsbinderParcel.cpp,我们看下writeInt32方法:
~~~
status_t Parcel::writeInt32(int32_t val)
{
return writeAligned(val);
}
~~~
~~~
status_t Parcel::writeAligned(T val) {
COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));
if ((mDataPos+sizeof(val)) <= mDataCapacity) {
restart_write:
*reinterpret_cast<T*>(mData+mDataPos) = val;
return finishWrite(sizeof(val));
}
status_t err = growData(sizeof(val));
if (err == NO_ERROR) goto restart_write;
return err;
~~~
分析上面的之前,首先要知道mData、mDataPos、mDataCapacity三个变量的意义,mData指向parcel缓存的首地址,mDataCapacity表示parcel缓存容量(大小),mDataPos指向parcel缓存中空闲区域的首地址**,整个parcel缓存是一块连续的内存。**
**物理地址 = 有效地址+偏移地址,首先会判断先写入的int数据的字节数是否超过了data的容量,如果没有超过,会执行数据的写入,**reinterpret\_cast是c++的一种再解释,强制转换,上面首先会将mData+mDataPos得到物理地址,转成指向T类型的指针(T类型就是你传进来的变量的类型),然后将val赋值给指针指向的内容。然后修改偏移地址,finishWrite(sizeof(val)):
~~~
status_t Parcel::finishWrite(size_t len)
{
if (len > INT32_MAX) {
// don't accept size_t values which may have come from an
// inadvertent conversion from a negative int.
return BAD_VALUE;
}
//printf("Finish write of %d\n", len);
mDataPos += len;
ALOGV("finishWrite Setting data pos of %p to %zu", this, mDataPos);
if (mDataPos > mDataSize) {
mDataSize = mDataPos;
ALOGV("finishWrite Setting data size of %p to %zu", this, mDataSize);
}
//printf("New pos=%d, size=%d\n", mDataPos, mDataSize);
return NO_ERROR;
}
~~~
上面主要是将修改偏移地址,将偏移地址加上新增加的数据的字节数。
如果增加的数据大于容量的话,那么首先扩展parcel的缓存空间,growData(sizeof(val)):
~~~
status_t Parcel::growData(size_t len)
{
if (len > INT32_MAX) {
// don't accept size_t values which may have come from an
// inadvertent conversion from a negative int.
return BAD_VALUE;
}
size_t newSize = ((mDataSize+len)*3)/2;
return (newSize <= mDataSize)
? (status_t) NO_MEMORY
: continueWrite(newSize);
}
~~~
扩展成功,就继续goto restart\_write,在writeAligned方法中有restart\_write,执行restart\_write后面code,写入数据。
通过上面的解释相信大家已经明白int类型的数据写入parcel缓存了,既然知道存数据,那我们也要明白取数据了,在取数据的时候,我们会通过this.age = in.readInt();来取得int类型数据
~~~
static jint android_os_Parcel_readInt(jlong nativePtr)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
return parcel->readInt32();
}
return 0;
}
~~~
调用的parcel的readInt32方法:
~~~
int32_t Parcel::readInt32() const
{
return readAligned<int32_t>();
}
T Parcel::readAligned() const {
T result;
if (readAligned(&result) != NO_ERROR) {
result = 0;
}
return result;
}
status_t Parcel::readAligned(T *pArg) const {
COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));
if ((mDataPos+sizeof(T)) <= mDataSize) {
const void* data = mData+mDataPos;
mDataPos += sizeof(T);
*pArg = *reinterpret_cast<const T*>(data);
return NO_ERROR;
} else {
return NOT_ENOUGH_DATA;
}
~~~
读取数据的时候,首先我们会从parcel的起始地址+parcel偏移地址,得到读取的数据的地址,然后取出数据,然后将parcel的偏移地址+取出的数据的字节数,这样指针就可以指向下一个数据,这样说太抽象了,举个例子:
比如我们现在有一个对象,里面是
~~~
stu{
int age = 32;
double score = 99;
}
~~~
我们在写数据的时候,会在一块parcel的内存地址中,写32,99,然后读取的时候,会从起始地址+读取的字节数,来一一读取,首先读取parcel起始地址指向的数据,取出32,然后将指针地址偏移int字节数,指针指向99的地址,然后读取99,然后取出数据,这也就是parcelable在实现的时候为什么需要存和读取的顺序需要一致的原因。
### 参考资料
[Parcelable最强解析](https://segmentfault.com/a/1190000012522154#articleHeader1)
## Serializable 和Parcelable 的区别
### 作用
Serializa ble的作用是为了保存对象的属性到本地文件、数据库、网络流、RMI(Remote Method Invocation)以方便数据传输,当然这种传输可以是程序内的也可以是两个程序间的。使用了反射技术,并且期间产生临时对象
Android的Parcelable的设计初衷是因为Serializable效率过慢,为了在程序内不同组件间以及不同Android程序间(AIDL)高效的传输数据而设计,这些数据仅在内存中存在,Parcelable是通过IBinder通信的消息的载体。
### 效率及选择
Parcelable的性能比Serializable好,在内存开销方面较小,所以在内存间数据传输时推荐使用Parcelable,如activity间传输数据,而Serializable可将数据持久化方便保存,所以在需要保存或网络传输数据时选择Serializable,因为android不同版本Parcelable可能不同,所以不推荐使用Parcelable进行数据持久化
### 高级功能上
Serializable序列化不保存静态变量,可以使用transient关键字对部分字段不进行序列化,也可以覆盖writeObject、readObject方法以实现序列化过程自定义
如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。换句话说,用transient关键字标记的成员变量不参与序列化过程。
###编程实现
对于Serializable,类只需要实现Serializable接口,并提供一个序列化版本id(serialVersionUID)即可。
而Parcelable则需要实现writeToParcel、describeContents函数以及静态的CREATOR变量(AS有相关插件 一键生成所需方法),实际上就是将如何打包和解包的工作自己来定义,而序列化的这些操作完全由底层实现。
## Bundle
### 源码解析
Bundle位于android.os包中,是一个final类,这就注定了Bundle不能被继承。Bundle继承自BaseBundle并实现了Cloneable和Parcelable两个接口,因此对Bundle源码的分析会结合着对BaseBundle源码进行分析。**由于实现了Cloneable和Parcelable接口**,因此以下几个重载是必不可少的:
* public Object clone()
* public int describeContents()
* public void writeToParcel(Parcel parcel, int flags)
* public void readFromParcel(Parcel parcel)
* public static final Parcelable.Creator CREATOR = new Parcelable.Creator()
以上代码无需过多解释。
Bundle的功能是用来保存数据,那么必然提供了一系列Bundle的put与get方法族数据的方法,这些方法太多了,几乎能够存取任何类型的数据;
### 重点
Bundle之所以能以键值对的方式存储数据,实质上是因为它内部维护了一个ArrayMap,具体定义是在其父类BaseBundle中:
~~~
ArrayMap<String, ObjectmMap = null;
~~~
:
~~~
void putBoolean(String key, boolean value) {
unparcel();
mMap.put(key, value);
}
~~~
这里的mMap就是ArrayMap了,存储数据就是把键值对保存到ArrayMap里。
布尔类型数据的读取源码如下:
~~~
boolean getBoolean(String key) {
unparcel();
if (DEBUG) Log.d(TAG, "Getting boolean in "+ Integer.toHexString(System.identityHashCode(this)));
return getBoolean(key, false);
}
~~~
getBoolean(String key, boolean defaultValue)的具体实现如下:
~~~
boolean getBoolean(String key, boolean defaultValue) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
return defaultValue;
}
try {
return (Boolean) o;
} catch (ClassCastException e) {
typeWarning(key, o, "Boolean", defaultValue, e);
return defaultValue;
}
}
~~~
数据读取的逻辑也很简单,就是通过key从ArrayMap里读出保存的数据,并转换成对应的类型返回,当没找到数据或发生类型转换异常时返回缺省值。
注意到这里出现了一个方法:unparcel(),它的具体源码如下:
~~~
synchronized void unparcel() {
if (mParcelledData == null) {
return;
}
if (mParcelledData == EMPTY_PARCEL) {
if (mMap == null) {
mMap = new ArrayMap<String, Object>(1);
} else {
mMap.erase();
}
mParcelledData = null;
return;
}
int N = mParcelledData.readInt();////通过parel可以看出这个方法通过调用native方法返回当前data的dataposition
if (N < 0) {
return;
}
if (mMap == null) {
mMap = new ArrayMap<String, Object>(N);
} else {
mMap.erase();
mMap.ensureCapacity(N);
}
//读取数据并存放到mMap中
mParcelledData.readArrayMapInternal(mMap, N, mClassLoader);
//回收解包完的数据
mParcelledData.recycle();
mParcelledData = null;
}
~~~
### 参考资料
[Bundle源码解析](https://blog.csdn.net/qq_33288248/article/details/70143275)
[序列化原理](https://blog.csdn.net/u011315960/article/details/89963230)
- Android
- 四大组件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介绍
- MessageQueue详细
- 启动流程
- 系统启动流程
- 应用启动流程
- Activity启动流程
- View
- view绘制
- view事件传递
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大数据
- Binder小结
- Android组件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 迁移与修复
- Sqlite内核
- Sqlite优化v2
- sqlite索引
- sqlite之wal
- sqlite之锁机制
- 网络
- 基础
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP进化图
- HTTP小结
- 实践
- 网络优化
- Json
- ProtoBuffer
- 断点续传
- 性能
- 卡顿
- 卡顿监控
- ANR
- ANR监控
- 内存
- 内存问题与优化
- 图片内存优化
- 线下内存监控
- 线上内存监控
- 启动优化
- 死锁监控
- 崩溃监控
- 包体积优化
- UI渲染优化
- UI常规优化
- I/O监控
- 电量监控
- 第三方框架
- 网络框架
- Volley
- Okhttp
- 网络框架n问
- OkHttp原理N问
- 设计模式
- EventBus
- Rxjava
- 图片
- ImageWoker
- Gilde的优化
- APT
- 依赖注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 协程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 运行期Java-hook技术
- 编译期hook
- ASM
- Transform增量编译
- 运行期Native-hook技术
- 热修复
- 插件化
- AAB
- Shadow
- 虚拟机
- 其他
- UI自动化
- JavaParser
- Android Line
- 编译
- 疑难杂症
- Android11滑动异常
- 方案
- 工业化
- 模块化
- 隐私合规
- 动态化
- 项目管理
- 业务启动优化
- 业务架构设计
- 性能优化case
- 性能优化-排查思路
- 性能优化-现有方案
- 登录
- 搜索
- C++
- NDK入门
- 跨平台
- H5
- Flutter
- Flutter 性能优化
- 数据跨平台