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

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

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

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