當我們在調(diào)用遠程方法時,需要在進程間傳遞參數(shù)以及返回結(jié)果。這種類似的處理方式,需要把數(shù)據(jù)與進程相關(guān)性去除,變成一種中間形式,然后按統(tǒng)一的接口進行讀寫操作。這樣的機制,一般在高級編程語言里都被稱為序列化。
在Android世界里處理數(shù)據(jù)的序列化操作的,使用了一種Parcel類,而能夠處理數(shù)據(jù)序列能力,則是實現(xiàn)Parcelable接口來實現(xiàn)。于是,當我們需要在進程間傳輸一個對象,則實現(xiàn)這一對象的類必須實現(xiàn)Parcelable接口里定義的相應(yīng)屬性或方法,而在使用這一對象時,則可以使用一個Parcel引用來處理傳輸時的基本操作。
Parcel和Serialize很類似,只是它是在內(nèi)存中完成的序列化和反序列化,利用的是連續(xù)的內(nèi)存空間,因此會更加高效。
1. Parcelable接口
Interface for classes whose instances can be written to and restored from a Parcel。 Classes implementing the Parcelable interface must also have a static field called CREATOR, which is an object implementing the Parcelable.Creator
interface。
2.實現(xiàn)Parcelable就是為了進行序列化,那么,為什么要序列化?
1)永久性保存對象,保存對象的字節(jié)序列到本地文件中;
2)通過序列化對象在網(wǎng)絡(luò)中傳遞對象;
3)通過序列化在進程間傳遞對象。
3.實現(xiàn)序列化的方法
Android中實現(xiàn)序列化有兩個選擇:一是實現(xiàn)Serializable接口(是JavaSE本身就支持的),一是實現(xiàn)Parcelable接口(是Android特有功能,效率比實現(xiàn)Serializable接口高效,可用于Intent數(shù)據(jù)傳遞,也可以用于進程間通信(IPC))。實現(xiàn)Serializable接口非常簡單,聲明一下就可以了,而實現(xiàn)Parcelable接口稍微復(fù)雜一些,但效率更高,推薦用這種方法提高性能。
注:Android中Intent傳遞對象有兩種方法:一是Bundle.putSerializable(Key,Object),另一種是Bundle.putParcelable(Key,Object)。當然這些Object是有一定的條件的,前者是實現(xiàn)了Serializable接口,而后者是實現(xiàn)了Parcelable接口。
4.選擇序列化方法的原則
1)在使用內(nèi)存的時候,Parcelable比Serializable性能高,所以推薦使用Parcelable。
2)Serializable在序列化的時候會產(chǎn)生大量的臨時變量,從而引起頻繁的GC。
3)Parcelable不能使用在要將數(shù)據(jù)存儲在磁盤上的情況,因為Parcelable不能很好的保證數(shù)據(jù)的持續(xù)性在外界有變化的情況下。盡管Serializable效率低點,但此時還是建議使用Serializable 。
5.應(yīng)用場景
需要在多個部件(Activity或Service)之間通過Intent傳遞一些數(shù)據(jù),簡單類型(如:數(shù)字、字符串)的可以直接放入Intent。復(fù)雜類型必須實現(xiàn)Parcelable接口。
6.Serializable實現(xiàn)與Parcelabel實現(xiàn)的區(qū)別
1)Serializable的實現(xiàn),只需要implements Serializable 即可。這只是給對象打了一個標記,系統(tǒng)會自動將其序列化。
2)Parcelabel的實現(xiàn),不僅需要implements Parcelabel,還需要在類中添加一個靜態(tài)成員變量CREATOR,這個變量需要實現(xiàn) Parcelable.Creator 接口。
1)創(chuàng)建Person類,實現(xiàn)Serializable:
public class Person implements Serializable private static final long serialVersionUID = -7060210544600464481L; public void setName(String name) public void setAge(int age)
2)創(chuàng)建Book類,實現(xiàn)Parcelable:
public class Book implements Parcelable public String getBookName() public void setBookName(String bookName) this.bookName = bookName; public String getAuthor() public void setAuthor(String author) public int getPublishDate() public void setPublishDate(int publishDate) this.publishDate = publishDate; public int describeContents() public void writeToParcel(Parcel out, int flags) out.writeString(bookName); out.writeInt(publishDate); public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>() public Book[] newArray(int size) public Book createFromParcel(Parcel in) bookName = in.readString(); author = in.readString(); publishDate = in.readInt();
1、describeContents(),Parcelabl所需要的接口方法之一,必須實現(xiàn)。這一方法作用很簡單,就是通過返回的整形來描述這一Parcel是起什么作用的,通過這一整形每個bit來描述其類型,一般會返回0。
2、writeToParcel(),Parcelabl所需要的接口方法之二,必須實現(xiàn)。writeToParcel()方法的作用是發(fā)送,就是將類所需要傳輸?shù)膶傩詫懙絇arcel里,被用來提供發(fā)送功能的Parcel,會作為第一個參數(shù)傳入,于是在這個方法里都是使用writeInt()、writeLong()寫入到Parcel里。這一方法的第二參數(shù)是一個flag值,可以用來指定這樣的發(fā)送是單向還是雙向的,可以與aidl的in、out、inout三種限定符匹配。
3、CREATOR對象,Parcelable接口所需要的第三項,必須提供實現(xiàn),但這是一個是接口對象。正如我們看到的,這一CREATOR對象,是使用模板類Parcelable.Creator,套用到TaskInfo來得到的,Parcelable.Creator<TaskInfo>。這個CREATOR對象在很大程度上是一個工廠(Factory)類,用于遠程對象在接收端的創(chuàng)建。從某種意義上來說,writeToParcel()與CREATOR是一一對應(yīng)的,發(fā)送端進程通過writeToParcel(),使用一個Parcel對象將中間結(jié)果保存起來,而接收端進程則會使用CREATOR對象把作為Parcel對象的中間對象再恢復(fù)出來,通過類的初始化方法以這個Parcel對象為基礎(chǔ)來創(chuàng)建新對象。后續(xù)的4-6,則是完成這個CREATOR對象的實現(xiàn)。
4、createFromParcel(),這是ParcelableCreator<T>模板類所必須實現(xiàn)的接口方法,提供從Parcel轉(zhuǎn)義出新的對象的能力。接收端來接收傳輸過來的Parcel對象時,便會以這一個接口方法來取得對象。我們這里直接調(diào)用基于Parcel 的類的初始化方法,然后將創(chuàng)建的對象返回。
5、newArray(),這是ParcelableCreator<T>模板類所必須實現(xiàn)的另一個接口方法,但這一方法用于創(chuàng)建多個這種實現(xiàn)了Parcelable接口的類。通過這一方法,CREATOR對象不光能創(chuàng)建單個對象,也能返回多個創(chuàng)建好的空對象,但多個對象不能以某個Parcel對象為基礎(chǔ)創(chuàng)建,于是會使用默認的類創(chuàng)始化方法。
6、實現(xiàn)具體的以Parcel為參照的初始化方法,這并非必須,我們也可以在createFromParcel()里直接根據(jù)Parcel的值賦值到對象來實現(xiàn),但這樣實現(xiàn)則更清晰。這一方法,基本上與writeToParcel()是成對的,以什么順序?qū)ο髮傩詫懭隤arcel,則在createFromParcel()會就會以同樣的順序?qū)ο髮傩詮腜arcel里讀出來,使用Parcel的readInt()、readLong()等方法來完成。
7.應(yīng)用實例
就應(yīng)用程序而言,最常見使用Parcel類的場景就是在Activity間傳遞數(shù)據(jù)。沒錯,在Activity間使用Intent傳遞數(shù)據(jù)的時候,可以通過Parcelable機制傳遞復(fù)雜的對象。
在下面的程序中,MyColor用于保存一個顏色值,MainActivity在用戶點擊屏幕時將MyColor對象設(shè)成紅色,傳遞到SubActivity中,此時SubActivity的TextView顯示為紅色的背景;當點擊SubActivity時,將顏色值改為綠色,返回MainActivity,期望的是MainActivity的TextView顯示綠色背景。
MyColor類的實現(xiàn)代碼:
import android.graphics.Color; import android.os.Parcel; import android.os.Parcelable; public class MyColor implements Parcelable { private int color=Color.BLACK; public void setColor(int color){ public int describeContents() { public void writeToParcel(Parcel dest, int flags) { public static final Parcelable.Creator<MyColor> CREATOR = new Parcelable.Creator<MyColor>() { public MyColor createFromParcel(Parcel in) { public MyColor[] newArray(int size) { return new MyColor[size];
MainActivity的代碼:
import android.app.Activity; import android.content.Intent; import android.graphics.Color; import android.os.Bundle; import android.view.MotionEvent; public class MainActivity extends Activity { private final int SUB_ACTIVITY=0; private MyColor color=new MyColor(); public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode==SUB_ACTIVITY){ if (resultCode==RESULT_OK){ if (data.hasExtra("MyColor")){ color=data.getParcelableExtra("MyColor"); //<span style="font-size: 14px; white-space: pre;">反序列化后是一個新的MyColor對象</span> findViewById(R.id.text).setBackgroundColor(color.getColor()); public boolean onTouchEvent(MotionEvent event){ if (event.getAction()==MotionEvent.ACTION_UP){ Intent intent=new Intent(); intent.setClass(this, SubActivity.class); color.setColor(Color.RED); intent.putExtra("MyColor", color); startActivityForResult(intent,SUB_ACTIVITY); return super.onTouchEvent(event);
SubActivity的代碼:
import android.app.Activity; import android.content.Intent; import android.graphics.Color; import android.os.Bundle; import android.view.MotionEvent; import android.widget.TextView; public class SubActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); ((TextView)findViewById(R.id.text)).setText("SubActivity"); Intent intent=getIntent(); if (intent.hasExtra("MyColor")){ color=intent.getParcelableExtra("MyColor"); findViewById(R.id.text).setBackgroundColor(color.getColor()); public boolean onTouchEvent(MotionEvent event){ if (event.getAction()==MotionEvent.ACTION_UP){ Intent intent=new Intent(); color.setColor(Color.GREEN); intent.putExtra("MyColor", color); setResult(RESULT_OK,intent); return super.onTouchEvent(event);
在MainActivity的onActivityResult()中,有一句color=data.getParcelableExtra("MyColor"),這說明的是反序列化后是一個新的MyColor對象,因此要想使用這個對象,我們做了這個賦值語句。
如果數(shù)據(jù)本身是IBinder類型,那么反序列化的結(jié)果就是原對象,而不是新建的對象,很顯然,如果是這樣的話,在反序列化后在MainActivity中就不再需要color=data.getParcelableExtra("MyColor")這句了。因此,換一種MyColor的實現(xiàn)方法,令其中的int color成員變量使用IBinder類型的成員變量來表示。
新建一個BinderData類繼承自Binder,代碼如下:
import android.os.Binder; public class BinderData extends Binder {
修改MyColor的代碼如下:
import android.graphics.Color; import android.os.Parcel; import android.os.Parcelable; public class MyColor implements Parcelable { private BinderData data=new BinderData(); data=(BinderData) in.readValue(BinderData.class.getClassLoader()); public void setColor(int color){ public int describeContents() { public void writeToParcel(Parcel dest, int flags) { public static final Parcelable.Creator<MyColor> CREATOR = new Parcelable.Creator<MyColor>() { public MyColor createFromParcel(Parcel in) { public MyColor[] newArray(int size) { return new MyColor[size];
去掉MainActivity的onActivityResult()中的color=data.getParcelableExtra("MyColor")一句,變成:
protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode==SUB_ACTIVITY){ if (resultCode==RESULT_OK){ if (data.hasExtra("MyColor")){ findViewById(R.id.text).setBackgroundColor(color.getColor());
|