[TOC]
## Serializable(謹慎使用序列化)
序列化和持久化很相似,有些人甚至混為一談,其實還是有區別的,**序列化是為了解決對象的傳輸問題,傳輸可以在線程之間、進程之間、內存外存之間、主機之間進行**。在安卓中絕大部分場景是通過Binder來進行對象的傳輸.
### serialVersionUID
如果沒有指定UID,那么JDK會自動幫我們生成一個UID.手動指定這個UID可以減少運行時的開銷,如果我們不加,系統后根據通過一套復雜的運算,自動賦值,該值與類名,接口名,成員名都有關.
### 增加了潛在的風險
引入了隱藏的構造函數
### 測試的代價大
每個版本都得保證序列化和反序列化的成功
### 保護字段不被序列化
1. transient關鍵字修飾的變量,不會被序列化;Transient 關鍵字的作用是控制變量的序列化,在變量聲明前加上該關鍵字,可以阻止該變量被序列化到文件中,在被反序列化后,transient 變量的值被設為初始值,如 int 型的是 0,對象型的是 null。
2. static關鍵字修飾的也不會被序列化, 因為static是類的特征,和對象無關。
3. 放在父類中,如果父類實現了序列化,子類默認實現序列化。
### 原理

## 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 性能優化
- 數據跨平臺