Parcelable最强解析
這兩天有個同事在使用泛型的過程中,T extends BaseBean,對BaseBean類實現(xiàn)了parceable接口,當(dāng)一個Activity中跳轉(zhuǎn)到另一個Activity的時候,intent.putExtra("key",childBean),用到ChildBean對象,該類直接繼承了BaseBean,他覺得在另外一個Acitivty拿不到ChildBean中的數(shù)據(jù)信息,甚至當(dāng)他在用ChildBean=getIntent().getParcelableExtra()的時候出現(xiàn)了類型轉(zhuǎn)換錯誤,用BaseBean=getIntent().getParcelableExtra()確沒有問題,一時間對父類實現(xiàn)parcelable接口,子類是否有必要實現(xiàn)parcelable接口,然后傳值產(chǎn)生了爭議,相信也有不少同學(xué)也有這樣的困惑,所以有了這篇文章
(這里的Basebean和ChildBean是指父類和子類,正文的也是這個意思)
1.Java serialization algorithm
答:當(dāng)我們對一個對象實現(xiàn)Serializable 接口的時候,它會告訴序列化機(jī)制這個類是可以序列化的,java會通過文件流的形式,將object寫在一個文件file當(dāng)中,
public static void main(String args[]) throws IOException {FileOutputStream fos = new FileOutputStream("temp.out");ObjectOutputStream oos = new ObjectOutputStream(fos);TestSerial ts = new TestSerial();oos.writeObject(ts);oos.flush();oos.close(); } 復(fù)制代碼這里我們要注意ObjectOutputStream的構(gòu)造對象,會寫如流的header,在這里注意下code后面的注釋,因為在例子上面都要給對上的。
public ObjectOutputStream(OutputStream out) throws IOException {verifySubclass();......writeStreamHeader();.....} 復(fù)制代碼protected void writeStreamHeader() throws IOException {bout.writeShort(STREAM_MAGIC);//這里寫入序列化協(xié)議bout.writeShort(STREAM_VERSION);//這里寫入序列化的版本} 復(fù)制代碼protected void writeStreamHeader() throws IOException {bout.writeShort(STREAM_MAGIC);bout.writeShort(STREAM_VERSION);} 復(fù)制代碼具體的實現(xiàn)是在:
private void writeObject0(Object obj, boolean unshared) throws IOException{······else if (obj instanceof Serializable) {writeOrdinaryObject(obj, desc, unshared);} ···········} 復(fù)制代碼如上面代碼所示,剛開始的時候,對象是一個Serializable,所以會走writeOrdinaryObject(obj, desc, unshared);方法:
private void writeOrdinaryObject(Object obj,ObjectStreamClass desc,boolean unshared) throws IOException{······try {desc.checkSerialize();bout.writeByte(TC_OBJECT);//這里寫入TC_OBJECTwriteClassDesc(desc, false);//接著寫classDesc······} finally {if (extendedDebugInfo) {debugInfoStack.pop();}}} 復(fù)制代碼上面再寫入TC_OBJECT之后,就調(diào)用writeClassDesc方法,在這里我就不繼續(xù)分析了,因為文章的重點(diǎn)不應(yīng)該在Serializable的分析上,接下來都是些java代碼的調(diào)用,也有源碼,如果你自己感興趣,相信你們也可以隨便看看源代碼就能分析出來,在這里我就不浪費(fèi)大家的時間了,不過要提一下,寫的時候,是先寫自身類的描述,然后如果有父類就寫父類的描述,如果自身類包含的字段是一個對象,再寫該對象的描述,都寫完了,最后寫字段的數(shù)據(jù)。在這里對一個類獲取里面的字段,方法等是用到了反射機(jī)制
以下是一個對象寫入的例子,假設(shè)一個類是:
如上一個對象所示,在寫入磁盤的時候,保存的數(shù)據(jù)如下:
AC ED (序列化協(xié)議) 00 05 (序列化版本) 73 (TC_OBJECT. 新的對象) 72 (TC_CLASSDESC. 這是一個新類描述) 00 0A (類名的長度) 53 65 72 69 61 6C 54 65 73 74 (類的名稱) 05 52 81 5A AC 66 02 F6 (SerialVersionUID) 02 (Various flags,0x02代表這個對象支持序列化) 00 01 (類有幾個字段) 49 (代表是int類型) 00 07 (字段名稱的長度) 76 65 72 73 69 6F 6E (version, 字段的名稱) 78 (TC_ENDBLOCKDATA, 描述的結(jié)束符) 70 (TC_NULL) 00 00 00 64 (version的值) 復(fù)制代碼從上面可以看到serialiable的序列化和反序列化會創(chuàng)造大量的對象和寫入數(shù)據(jù)的時候,會寫入除去真實數(shù)據(jù)以外的其它數(shù)據(jù),比如序列化協(xié)議,版本等等。
2.Parcable 機(jī)制的原理?
首先我們在一個實體對象在實現(xiàn)parcelable的時候,這個時候,我們會重寫writeToParcel方法,其中執(zhí)行dest.writeInt(this.offLineBtn);writeLong等等類型的數(shù)據(jù),實際是執(zhí)行native方法,在這里我們就不分析各種數(shù)據(jù)類型的存取了,我們現(xiàn)在拿一個代表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);} } 復(fù)制代碼在這里我們要特別注意兩個參數(shù),一個是之前傳上去的指針以及需要保存的int數(shù)據(jù),這兩個值分別是: (jint nativePtr, jint val) 首先是根據(jù)這個指針,這里說一下,指針實際上就是一個整型地址值,所以這里使用強(qiáng)轉(zhuǎn)將int值轉(zhuǎn)化為parcel類型的指針是可行的,然后使用這個指針來操作native的parcel對象,即: const status_t err = parcel->writeInt32(val);
writeInt32是調(diào)用了parcel中的方法,parcel的實現(xiàn)類是在Framework/native/libs\binder\Parcel.cpp,我們看下writeInt32方法:
status_t Parcel::writeInt32(int32_t val) {return writeAligned(val); } 復(fù)制代碼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; } 復(fù)制代碼分析上面的之前,首先要知道m(xù)Data、mDataPos、mDataCapacity三個變量的意義,mData指向parcel緩存的首地址,mDataCapacity表示parcel緩存容量(大小),mDataPos指向parcel緩存中空閑區(qū)域的首地址,整個parcel緩存是一塊連續(xù)的內(nèi)存。
物理地址 = 有效地址+偏移地址,首先會判斷先寫入的int數(shù)據(jù)的字節(jié)數(shù)是否超過了data的容量,如果沒有超過,會執(zhí)行數(shù)據(jù)的寫入,reinterpret_cast是c++的一種再解釋,強(qiáng)制轉(zhuǎn)換,上面首先會將mData+mDataPos得到物理地址,轉(zhuǎn)成指向T類型的指針(T類型就是你傳進(jìn)來的變量的類型),然后將val賦值給指針指向的內(nèi)容。然后修改偏移地址,finishWrite(sizeof(val)):
上面主要是將修改偏移地址,將偏移地址加上新增加的數(shù)據(jù)的字節(jié)數(shù)。
如果增加的數(shù)據(jù)大于容量的話,那么首先擴(kuò)展parcel的緩存空間,growData(sizeof(val)):
擴(kuò)展成功,就繼續(xù)goto restart_write,在writeAligned方法中有restart_write,執(zhí)行restart_write后面code,寫入數(shù)據(jù)。
通過上面的解釋相信大家已經(jīng)明白int類型的數(shù)據(jù)寫入parcel緩存了,既然知道存數(shù)據(jù),那我們也要明白取數(shù)據(jù)了,在取數(shù)據(jù)的時候,我們會通過this.age = in.readInt();來取得int類型數(shù)據(jù)
static jint android_os_Parcel_readInt(jlong nativePtr) {Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);if (parcel != NULL) {return parcel->readInt32();}return 0; } 復(fù)制代碼調(diào)用的parcel的readInt32方法:
int32_t Parcel::readInt32() const {return readAligned<int32_t>(); } 復(fù)制代碼T Parcel::readAligned() const {T result;if (readAligned(&result) != NO_ERROR) {result = 0;}return result; } 復(fù)制代碼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;} } 復(fù)制代碼讀取數(shù)據(jù)的時候,首先我們會從parcel的起始地址+parcel偏移地址,得到讀取的數(shù)據(jù)的地址,然后取出數(shù)據(jù),然后將parcel的偏移地址+取出的數(shù)據(jù)的字節(jié)數(shù),這樣指針就可以指向下一個數(shù)據(jù),這樣說太抽象了,舉個例子: 比如我們現(xiàn)在有一個對象,里面是
stu{int age = 32;double score = 99; } 復(fù)制代碼我們在寫數(shù)據(jù)的時候,會在一塊parcel的內(nèi)存地址中,寫32,99,然后讀取的時候,會從起始地址+讀取的字節(jié)數(shù),來一一讀取,首先讀取parcel起始地址指向的數(shù)據(jù),取出32,然后將指針地址偏移int字節(jié)數(shù),指針指向99的地址,然后讀取99,然后取出數(shù)據(jù),這也就是parcelable在實現(xiàn)的時候為什么需要存和讀取的順序需要一致的原因。
3.在我們了解了,parcelable的實現(xiàn)原理的時候,我們就可以解答引言上面的問題了。
3.1 對BaseBean類實現(xiàn)了parceable接口,當(dāng)一個Activity中跳轉(zhuǎn)到另一個Activity的時候,intent.putExtra("key",childBean),另一個Activity能否用拿到數(shù)據(jù)?
答:因為在父類的BaseBean里面都有實現(xiàn)BaseBean中字段的讀寫,所以BaseBean中字段的數(shù)據(jù)是可以拿到的。
3.2 在用ChildBean=getIntent().getParcelableExtra()的時候出現(xiàn)了類型轉(zhuǎn)換錯誤,用BaseBean=getIntent().getParcelableExtra()確沒有問題?
答:其實這里是要看BaseBean中讀數(shù)據(jù),返回的對象是什么了?
public static final Parcelable.Creator<BaseBean> CREATOR = new Parcelable.Creator<BaseBean>() {@Overridepublic BaseBean createFromParcel(Parcel source) {return new BaseBean(source);}@Overridepublic BaseBean[] newArray(int size) {return new BaseBean[size];}}; 復(fù)制代碼很明顯,在這里返回的BaseBean的對象,當(dāng)你用ChildBean去接收的時候肯定會出現(xiàn)類型轉(zhuǎn)換錯誤啦,如果還覺得想用ChildBean來接收的話(前提是有強(qiáng)迫癥),可以重寫createFromParcel方法
@Overridepublic BaseBean createFromParcel(Parcel source) {ChildBean childBean = new ChildBean();childBean.setName(source.readString());childBean.setPrice(source.readDouble());return childBean;} 復(fù)制代碼這不返回ChildBean不就可以了,當(dāng)然不管你是哪種方式,如果childBean沒有實現(xiàn)parceable的話,對于childBean中的字段是無法傳遞的.
attention:這個和Serializable的實現(xiàn)是不同的,Serializable是父類實現(xiàn)了Serializable,子類不需要實現(xiàn)Serializable,子類的數(shù)據(jù)也能夠傳遞了,因為在寫入數(shù)據(jù)的判斷(obj instanceof Serializable),如果父類實現(xiàn)Serializable,子類肯定也是instanceof Serializable。
3.3 如果我們需要用到一個公共的界面,這個公共的界面可能是通過泛型T t =getIntent().getParcelableExtra()來獲取數(shù)據(jù)的解決方案?
答:這里我們的BaseBean不應(yīng)該是一個類,最合適的話,應(yīng)該是一個interface,比如我們公共界面是用到了t.getName()來得到顯示的數(shù)據(jù),這個時候
class ChildBean implements BaseBean,Parcelable{...@Overridepublic String getName(){return "WelliJohn";}... } 復(fù)制代碼當(dāng)用到了傳值的時候,ChildBean再自身實現(xiàn)了Parcelable接口,這樣代碼就完美了。這樣如果真的在公共界面有個特殊的類型的話,判斷下T的類型(ChildBean.class.isInstance(t)),強(qiáng)轉(zhuǎn)下也可以進(jìn)行某個特殊數(shù)據(jù)處理了。
4.總結(jié)
| 文件操作,且用到了反射 | 單獨(dú)的內(nèi)存空間,速度快 |
| 會創(chuàng)造大量的讀寫對象 | 直接操作內(nèi)存讀寫 |
| 實現(xiàn)簡單 | 實現(xiàn)復(fù)雜,而且讀和取的數(shù)據(jù)要一致 |
| 寫入的時候,會有字段名,長度等 | 只是寫入數(shù)據(jù),節(jié)省資源 |
| 因為寫在文件中,適合持久化數(shù)據(jù) | 不適合持久化數(shù)據(jù),可能會變化 |
如果你們有對3.3的解決方案感覺有更好的處理思路的話,歡迎提出來共同探討
如果你們覺得文章對你有啟示作用,希望你們幫忙點(diǎn)個贊或者關(guān)注下,謝謝
總結(jié)
以上是生活随笔為你收集整理的Parcelable最强解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JetBrains 授权服务器(Lice
- 下一篇: 他们是最懂数据的商家!智能品牌时代到来