鴻蒙開源第三方組件—序列化與反序列化封裝組件Parceler_ohos
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
前言
基于安卓平臺的序列化與反序列化封裝組件Parceler (https://github.com/johncarl81/parceler),實現(xiàn)了鴻蒙化遷移和重構(gòu),代碼已經(jīng)開源到(https://gitee.com/isrc_ohos/parceler_ohos),目前已經(jīng)獲得了很多人的Star和Fork ,歡迎各位下載使用并提出寶貴意見!
背景
序列化是指將Java對象轉(zhuǎn)換為字節(jié)序列的過程,本質(zhì)上是把實體對象狀態(tài)按照一定的格式寫入到有序字節(jié)流;而反序列化則是將字節(jié)序列轉(zhuǎn)換為Java對象的過程,本質(zhì)上是從有序字節(jié)流重建對象,恢復(fù)對象狀態(tài)。當(dāng)兩個Java進程相互通信時,就需要使用Java序列化與反序列化方法來實現(xiàn)進程間的對象傳送。鴻蒙中Parceler_ohos組件可將不同種類型的數(shù)據(jù)進行序列化和反序列化封裝,從而達到進程間對象傳送的效果。
組件效果展示
Parceler_ohos組件支持多種數(shù)據(jù)的序列化和反序列化處理,包括基礎(chǔ)數(shù)據(jù)類、數(shù)組、Map類、Set類、以及序列化數(shù)據(jù)類,各類型中包含的具體數(shù)據(jù)類如下:
- 基礎(chǔ)數(shù)據(jù)類:int、float、String、boolean......;
- 數(shù)組:PlainArray、PlainBooleanArray;
- Map類:Map、HashMap、LinkedHashMap......;
- Set類:Set、HashSet、SortedSet......;
- 序列化數(shù)據(jù)類:Sequensable、Serializable。
組件以上述數(shù)據(jù)中的五種為例進行序列化和反序列化演示,分別是:int、float、String、plainArray、Sequenceable。
用戶點擊組件圖標后進入“Parceler測試”主界面,該界 面包含“int測試”、“float測試”、“String測試”、“plainArray測試”、“Sequenceable測試”五個按鈕。點擊上述各按鈕可跳轉(zhuǎn)至相應(yīng)類型數(shù)據(jù)的序列化和反序列化測試界面。由于這五種數(shù)據(jù)的測試步驟相同,接下來就以組件中int數(shù)據(jù)的測試效果為例進行展示。
點擊“int測試”按鈕進入“int格式測試”界面;再點擊“開始測試”按鈕,即可將事先設(shè)定好的整型測試數(shù)據(jù)“34258235”轉(zhuǎn)換成序列化字節(jié)流,并將序列化結(jié)果顯示在文字“序列化:”的下方;然后將字節(jié)流反序列化并輸出,輸出內(nèi)容為序列化之前的對象“34258235”;點擊“返回”按鈕后則可跳轉(zhuǎn)回主界面,上述測試效果如圖1所示。

圖1 int數(shù)據(jù)序列化與反序列化測試
Sample解析
Sample工程文件中的MainMenu文件用于構(gòu)建組件應(yīng)用的主界面,其他文件用于構(gòu)建上述組件效果展示的五種數(shù)據(jù)的序列化和反序列化測試界面,其對應(yīng)關(guān)系如圖2所示。由于各種數(shù)據(jù)的測試步驟相同,下文將以int數(shù)據(jù)的序列化和反序列化測試為例進行詳細講解,其他數(shù)據(jù)類型不再贅述。

圖 2 Sample中各文件與測試界面中按鈕的對應(yīng)關(guān)系
MainMenu文件
主界面的布局比較簡單,主要是設(shè)置進入五種數(shù)據(jù)測試界面的按鈕,具體步驟如下:
- 第1步:聲明五種數(shù)據(jù)的測試按鈕和標題。
- 第2步:創(chuàng)建頁面布局。
- 第3步:為int數(shù)據(jù)測試按鈕設(shè)置監(jiān)聽。
- 第4步:創(chuàng)建int數(shù)據(jù)測試按鈕。
1、聲明五種數(shù)據(jù)的測試按鈕和標題
聲明用于顯示標題“Parceler測試”的Text文本,以及用于跳轉(zhuǎn)到具體測試界面的5個Button按鈕,并將它們分別按類型命名。
- private Text title;//標題“Parceler測試”
- private Button IntTestButton;//int數(shù)據(jù)測試按鈕
- private Button FloatTestButton;//float數(shù)據(jù)測試按鈕
- private Button StringTestButton;//String數(shù)據(jù)測試按鈕
- private Button PlainArrayTestButton;//PlainArray數(shù)據(jù)測試按鈕
- private Button SequenceableTestButton;//Sequenceable數(shù)據(jù)測試按鈕
2、創(chuàng)建頁面布局
創(chuàng)建一個縱向顯示的整體頁面布局,其寬度和高度都跟隨父控件變化而調(diào)整,上下左右四個方向的填充間距依次是10/32/10/80,并設(shè)置背景顏色為白色。
- private DirectionalLayout directionalLayout = new DirectionalLayout(this);
- ......
- directionalLayout.setWidth(ComponentContainer.LayoutConfig.MATCH_PARENT);
- directionalLayout.setHeight(ComponentContainer.LayoutConfig.MATCH_PARENT);
- directionalLayout.setOrientation(Component.VERTICAL);
- directionalLayout.setPadding(10, 32, 10, 80);//填充間距
- ShapeElement element = new ShapeElement();
- element.setShape(ShapeElement.RECTANGLE);
- element.setRgbColor(new RgbColor(255, 255, 255)); //白色背景
- directionalLayout.setBackground(element);
3、為int數(shù)據(jù)測試按鈕設(shè)置監(jiān)聽
對int數(shù)據(jù)測試按鈕設(shè)置onClick()點擊監(jiān)聽事件,實現(xiàn)點擊按鈕可跳轉(zhuǎn)至int數(shù)據(jù)測試界面的效果。具體代碼如下。
- //初始化int按鈕監(jiān)聽
- Component.ClickedListener intTestListener = new Component.ClickedListener() {
- @Override
- public void onClick(Component component) {
- AbilitySlice intSlice = new IntTest();
- Intent intent = new Intent();
- present(intSlice,intent); // 跳轉(zhuǎn)至int數(shù)據(jù)測試界面
- }
- };
4、創(chuàng)建int數(shù)據(jù)測試按鈕
Sample中包含的5個測試按鈕除了顯示的文本信息和按鈕監(jiān)聽事件不同以外,其他屬性完全一致,這些屬性包括:按鈕顏色、倒角、顯示文本大小、對齊方式、按鈕內(nèi)填充距離等。
- private DirectionalLayout.LayoutConfig layoutConfig = new DirectionalLayout.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_CONTENT,ComponentContainer.LayoutConfig.MATCH_CONTENT);
- ......
- //設(shè)置按鈕屬性
- ShapeElement background = new ShapeElement();
- background.setRgbColor(new RgbColor(0xFF51A8DD));//創(chuàng)建按鈕顏色
- background.setCornerRadius(25);//創(chuàng)建按鈕邊框弧度
- layoutConfig.alignment = LayoutAlignment.HORIZONTAL_CENTER; //對齊方式
- layoutConfig.setMargins(0,100,0,0);//按鈕的邊界位置
- ......
- //創(chuàng)建處理int數(shù)據(jù)的按鈕
- IntTestButton = new Button(this); //創(chuàng)建按鈕
- IntTestButton.setLayoutConfig(layoutConfig);
- IntTestButton.setText("int測試"); //按鈕文本
- IntTestButton.setTextSize(80); //文本大小
- IntTestButton.setBackground(background); //按鈕背景
- IntTestButton.setPadding(10, 10, 10, 10); //按鈕填充間距
- IntTestButton.setClickedListener(intTestListener); //按鈕的監(jiān)聽
- directionalLayout.addComponent(IntTestButton); //按鈕添加到布局
IntTest文件
上文中我們已經(jīng)對主界面布局的實現(xiàn)進行了介紹,接下來就具體以int型數(shù)據(jù)序列化和反序列化測試為例,為大家講解如何使用Parceler_ohos組件。該組件處理int類型的數(shù)據(jù)共分為4個步驟:
- 第1步:導(dǎo)入相關(guān)類。
- 第2步:創(chuàng)建布局。
- 第3步:設(shè)置輸入數(shù)據(jù)和輸出數(shù)據(jù)。
- 第4步:創(chuàng)建測試按鈕。
1、導(dǎo)入相關(guān)類
在IntTest文件中,通過import關(guān)鍵字導(dǎo)入Parcels類,該類提供了數(shù)據(jù)序列化和反序列化實現(xiàn)的具體方法。
- import org.parceler.Parcels;
2、創(chuàng)建布局
創(chuàng)建一個縱向顯示的int數(shù)據(jù)測試頁面布局,寬度和高度都跟隨父控件變化而調(diào)整,上下左右四個方向的填充間距依次是32/32/80/80,并設(shè)置背景顏色為白色。
- private DirectionalLayout directionalLayout = new DirectionalLayout(this);
- ......
- //布局屬性
- directionalLayout.setWidth(ComponentContainer.LayoutConfig.MATCH_PARENT); directionalLayout.setHeight(ComponentContainer.LayoutConfig.MATCH_PARENT);
- directionalLayout.setOrientation(Component.VERTICAL);
- directionalLayout.setPadding(32, 32, 80, 80);
- //ShapeElement設(shè)置背景
- ShapeElement element = new ShapeElement();
- element.setShape(ShapeElement.RECTANGLE);
- element.setRgbColor(new RgbColor(255, 255, 255));
- directionalLayout.setBackground(element);
3、設(shè)置輸入數(shù)據(jù)和輸出數(shù)據(jù)
首先設(shè)置一個int數(shù)據(jù)作為輸入數(shù)據(jù),將測試數(shù)據(jù)的值34258235賦給int類對象intIn,并使用setText()方法將其以文本的形式顯示在界面上,格式為:“輸入:34258235”。然后分別實例化兩個Text類對象,一個是wrappedOutput,用來顯示輸入數(shù)據(jù)的序列化輸出,格式為"序列化:xxx",另一個是output,用來顯示上述序列化結(jié)果的反序列化輸出,格式為"輸出:xxx"。
- //設(shè)定輸入數(shù)據(jù)
- intIn = 34258235;
- input.setText("輸入:" + intIn);
- directionalLayout.addComponent(input);
- //初始化序列化后輸出
- wrappedOutput = new Text(this);
- wrappedOutput.setText("序列化:");
- directionalLayout.addComponent(wrappedOutput);
- //初始化反序列化后輸出
- output = new Text(this);
- output.setText("輸出:");
- directionalLayout.addComponent(output);
4、創(chuàng)建測試按鈕
界面上觸發(fā)序列化和反序列化操作的按鈕為“開始測試”按鈕。當(dāng)該按鈕被點擊后,會調(diào)用wrap()方法對輸入數(shù)據(jù)執(zhí)行序列化操作,并將得到的字節(jié)流信息顯示在上述第3步實現(xiàn)的“序列化:”文本后方;調(diào)用unwrap()方法將序列化后的字節(jié)流完成反序列化操作,并將得的整型對象輸出在上述第3步實現(xiàn)的的“輸出:”文本后方。
- private DirectionalLayout.LayoutConfig layoutConfig = new DirectionalLayout.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_CONTENT,ComponentContainer.LayoutConfig.MATCH_CONTENT); //寬和高跟隨父控件
- ......
- //創(chuàng)建“開始測試”按鈕
- beginTestButton = new Button(this);
- layoutConfig.alignment = LayoutAlignment.HORIZONTAL_CENTER; //居中
- layoutConfig.setMargins(0,100,0,0);
- beginTestButton.setLayoutConfig(layoutConfig);
- beginTestButton.setText("開始測試"); //文本
- beginTestButton.setTextSize(80); //文本大小
- ShapeElement background = new ShapeElement();
- background.setRgbColor(new RgbColor(0xFF51A8DD));
- background.setCornerRadius(25); //倒角
- beginTestButton.setBackground(background); //背景
- beginTestButton.setPadding(10, 10, 10, 10); //填充距離
- beginTestButton.setClickedListener(beginTestListener); //監(jiān)聽
- directionalLayout.addComponent(beginTestButton); //按鈕添加到布局
- //按鈕監(jiān)聽
- public void onClick(Component Component) {
- //序列化測試
- intWrapped = Parcels.wrap(intIn);
- //反序列化測試
- intOut = Parcels.unwrap(intWrapped);
- ......
- }
Library解析
Parceler_ohos組件能夠針對不同類型的數(shù)據(jù),進行序列化和反序列化的操作。在使用Parceler_ohos組件實現(xiàn)序列化和反序列化操作時,只需分別調(diào)用Pacels類中的wrap()和unWrap()方法,并將待操作數(shù)據(jù)作為唯一參數(shù)傳入即可,具體使用方法在上文Sample解析中已有詳細講解,此處不再進行贅述。接下來分別從Parceler_ohos組件的序列化和反序列化方法原理解析、以及轉(zhuǎn)換器原理這兩個方面進行講解。
1、序列化和反序列化方法原理解析
(1)wrap()方法實現(xiàn)序列化
實現(xiàn)序列化操作的原理和函數(shù)調(diào)用關(guān)系可以參考下圖3。

圖3 序列化操作原理示意圖
在Parcels類中,通過方法重載分別實現(xiàn)了兩種wrap()方法。第一種wrap()方法是直接被開發(fā)者調(diào)用的方法,僅有一個輸入數(shù)據(jù)作為參數(shù),在判斷輸入數(shù)據(jù)不為空后,獲取輸入數(shù)據(jù)的具體數(shù)據(jù)類型并調(diào)用第二種wrap()方法。
- //wrap()方法一
- @SuppressWarnings("unchecked")
- public static <T> Sequenceable wrap(T input) {
- if(input == null){//空判斷,判斷輸入數(shù)據(jù)是否為空
- return null;//若輸入數(shù)據(jù)為空,則返回空值
- }
- return wrap(input.getClass(), input);//調(diào)用wrap()方法二
- }
第二種wrap()方法有兩個參數(shù):輸入數(shù)據(jù)類型和數(shù)據(jù)本身。在判斷輸入數(shù)據(jù)不為空后,將輸入數(shù)據(jù)類型inputType作為ParcelCodeRepository類get()方法的參數(shù),返回一個Parcelable工廠類對象parcelableFactory;再通過parcelableFactory調(diào)用buildParcelable()方法,執(zhí)行具體的序列化操作。
- //wrap()方法二
- @SuppressWarnings("unchecked")
- public static <T> Sequenceable wrap(Class<? extends T> inputType, T input) {
- if(input == null){//空判斷,判斷輸入數(shù)據(jù)是否為空
- return null;//若輸入數(shù)據(jù)為空,則返回空值
- }
- //ParcelCodeRepository類獲取值賦給工廠類對象
- ParcelableFactory parcelableFactory = REPOSITORY.get(inputType);
- return parcelableFactory.buildParcelable(input);//工廠類對象執(zhí)行具體序列化操作
- }
其中,parcelableFactory.buildParcelable(input)方法具體執(zhí)行過程是:
a.實現(xiàn)泛型類接口
實現(xiàn)ParcelableFactory泛型類接口;再將輸入數(shù)據(jù)作為入?yún)魅隻uildParcelable(input)方法。
- public interface ParcelableFactory<T> {//ParcelableFactory泛型類接口
- String BUILD_PARCELABLE = "buildParcelable";
- Sequenceable buildParcelable(T input);//將Parcelable和輸入數(shù)據(jù)一起作為入?yún)?nbsp;
- }
b.調(diào)用參數(shù)類型與輸入數(shù)據(jù)類型匹配的buildParcelable(input)方法
在Libray的NonParcelRepository類中,有多個類實現(xiàn)了ParcelableFactory的接口,所以有多種參數(shù)形式的buildParcelable(input)方法,分別對應(yīng)多種輸入的數(shù)據(jù)類型,如boolean、char、List、Integer、set等。當(dāng)輸入數(shù)據(jù)為int類型時,就需要調(diào)用參數(shù)類型為Integer的buildParcelable(input)方法,并返回新實例化的IntegerParcelable類對象。
- private static class IntegerParcelableFactory implements Parcels.ParcelableFactory<Integer>{
- @Override
- public Sequenceable buildParcelable(Integer input) {//參數(shù)類型為Integer
- return new IntegerParcelable(input);//返回新實例化的類對象,輸入數(shù)據(jù)作為入?yún)?nbsp;
- }
- }
c.實例化IntegerParcelable類對象
實例化轉(zhuǎn)換器類對象CONVERTER;再通過IntegerParcelable類的構(gòu)造方法完成實例化操作,此步驟需要調(diào)用IntegerParcelable類父類的構(gòu)造函數(shù),以輸入數(shù)據(jù)和轉(zhuǎn)換器CONVERTER作為入?yún)ⅰ?/p>
- public static final class IntegerParcelable extends ConverterParcelable<Integer> {
- private static final NullableParcelConverter<Integer> CONVERTER = new NullableParcelConverter<Integer>() {...//實例化轉(zhuǎn)換器類對象
- @Override
- public void nullSafeToParcel(Integer input, Parcel parcel) {
- parcel.writeInt(input);
- }
- };...
- public IntegerParcelable(Integer value) {//IntegerParcelable類構(gòu)造方法
- super(value, CONVERTER);//調(diào)用父類構(gòu)造函數(shù)
- }...
- }
d.調(diào)用父類構(gòu)造函數(shù),實現(xiàn)轉(zhuǎn)換器類接口
在調(diào)用父類ConverterParcelable的構(gòu)造函數(shù)時,先在相應(yīng)轉(zhuǎn)換器類中實現(xiàn)TypeRangeParcelConverter類接口中的toParcel()和fromParcel()方法,然后調(diào)用相應(yīng)轉(zhuǎn)換器類的toParcel()方法完成序列化操作,其中具體轉(zhuǎn)換器類實現(xiàn)原理會在下文進行詳細講解,此處不進行贅述。
- //轉(zhuǎn)換器TypeRangeParcelConverter類接口
- public interface TypeRangeParcelConverter<L, U extends L> {
- void toParcel(L input, ohos.utils.Parcel parcel);
- U fromParcel(ohos.utils.Parcel parcel);
- }
- @Override//執(zhí)行序列化操作
- public boolean marshalling(Parcel parcel) {
- converter.toParcel(value, parcel);//調(diào)用轉(zhuǎn)換器類的toParcel()方法完成序列化操作
- return false;
- }
(2) unwrap()方法實現(xiàn)反序列化
實現(xiàn)反序列化操作,在判斷輸入數(shù)據(jù)不為空后,將輸入數(shù)據(jù)的值賦給接口ParcelWrapper的泛型類對象,再通過類對象調(diào)用getParcel()方法,獲取輸入數(shù)據(jù)的反序列化結(jié)果。
- @SuppressWarnings("unchecked")
- public static <T> T unwrap(Sequenceable input) {
- if(input == null){//空判斷,判斷輸入數(shù)據(jù)是否為空
- return null;//若輸入數(shù)據(jù)為空,則返回空值
- }
- ParcelWrapper<T> wrapper = (ParcelWrapper<T>) input;
- return wrapper.getParcel();
- }
其中,wrapper.getParcel()方法具體執(zhí)行過程是:實現(xiàn)ParcelWrapper泛型類接口,并實現(xiàn)getParcel()方法來執(zhí)行具體的反序列化操作;最后返回反序列化后的“@Parcel”實例。
- public interface ParcelWrapper<T> {//實現(xiàn)ParcelWrapper泛型類接口
- String GET_PARCEL = "getParcel";
- T getParcel();//需實現(xiàn)getParcel()方法執(zhí)行具體反序列化操作,并返回@Parcel實例
- }
2、轉(zhuǎn)換器處理類原理
在上文講解序列化方法wrap()時提到Parceler_ohos組件的轉(zhuǎn)換器處理類是Converter類。這些類負責(zé)處理數(shù)據(jù)集合的各種復(fù)雜或冗長繁復(fù)的工作,如判空檢查和數(shù)據(jù)集合迭代,并被打包在名為converter的包中,以便在API的相應(yīng)包下更容易地找到數(shù)據(jù)集合的轉(zhuǎn)換。這些轉(zhuǎn)換器處理類能夠?qū)Χ喾N數(shù)據(jù)類型進行轉(zhuǎn)換,如基礎(chǔ)數(shù)據(jù)類、Map類和Set類等,其具體轉(zhuǎn)換原理大同小異,接下來就以字符串?dāng)?shù)組轉(zhuǎn)換器CharArrayParcelConverter類為例進行詳細講解。
CharArrayParcelConverter類中包含兩個主要方法,即toParcel()和fromParcel(),其實質(zhì)是分別負責(zé)對Parcel類對象進行寫和讀操作。在toParcel()方法中,先判斷字符串?dāng)?shù)據(jù)是否為空,若為空則將數(shù)組數(shù)據(jù)寫入到Parcel類對象中,寫入數(shù)據(jù)為NULL;否則寫入數(shù)據(jù)為字符串?dāng)?shù)組,包括其長度和數(shù)據(jù)本身。
- public class CharArrayParcelConverter implements ParcelConverter<char[]> {
- private static final int NULL = -1;
- @Override
- public void toParcel(char[] array, Parcel parcel) {
- if (array == null) {//判斷字符串?dāng)?shù)組是否為空
- parcel.writeInt(NULL);//為空則寫入NULL空數(shù)據(jù)
- } else {//不為空則寫入字符串?dāng)?shù)組
- parcel.writeInt(array.length);//寫入數(shù)據(jù)長度
- parcel.writeCharArray(array);//寫入數(shù)組數(shù)據(jù)
- }
- }
- ...
- }
在fromParcel()方法中,先從Parcel類對象中讀取數(shù)組的長度,判斷長度是否為空,若為空則獲取到空值;否則實例化一個新的字符串?dāng)?shù)組,通過Parcel類對象調(diào)用readCharArray()方法讀取數(shù)組中的數(shù)據(jù),以剛才新實例化的數(shù)組作為入?yún)?,這樣就可以將讀取到的數(shù)據(jù)存入新實例化的數(shù)組中,方便后續(xù)對讀取到的數(shù)據(jù)的使用和處理,再返回新數(shù)組即可完成讀取。
- ...
- @Override
- public char[] fromParcel(Parcel parcel) {
- char[] array;
- int size = parcel.readInt();//讀取Parcel類對象中的數(shù)組長度
- if (size == NULL) {//若長度為空
- array = null;//則獲取到空值
- } else {//若長度不為空
- array = new char[size];//實例化新數(shù)組
- parcel.readCharArray(array);//讀取數(shù)組數(shù)據(jù),并存入新數(shù)組中
- }
- return array;//返回新數(shù)組
- }
- }
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)