DUICUO

HarmonyOSオープンソースのサードパーティコンポーネント - Parceler_ohos、シリアル化およびデシリアル化カプセル化コンポーネント

[[422128]]

詳細については、以下をご覧ください。

51CTOとHuaweiが共同で構築したHarmonyOSテクノロジーコミュニティ。

https://harmonyos..com

序文

Androidプラットフォームをベースとしたシリアル化およびデシリアル化カプセル化コンポーネントであるParceler(https://github.com/johncarl81/parceler)が、HarmonyOS向けに実装およびリファクタリングされました。コードはオープンソース化(https://gitee.com/isrc_ohos/parceler_ohos)されており、既に多くのスターやフォークを獲得しています。皆様のダウンロード、ご利用、そして貴重なフィードバックをお待ちしております。

背景

シリアライゼーションとは、Javaオブジェクトをバイト列に変換するプロセスを指します。基本的には、エンティティオブジェクトの状態を特定の形式に従って順序付けられたバイトストリームに書き込むことです。一方、デシリアライゼーションとは、バイト列をJavaオブジェクトに戻すプロセスを指します。基本的には、順序付けられたバイトストリームからオブジェクトを再構築し、その状態を復元することです。2つのJavaプロセスが相互に通信する場合、プロセス間オブジェクト転送を実現するために、Javaのシリアライゼーションおよびデシリアライゼーションメソッドが必要です。HarmonyOSのParceler_ohosコンポーネントは、異なるタイプのデータをカプセル化およびシリアライズすることで、プロセス間オブジェクト転送の効果を実現します。

コンポーネント効果表示

Parceler_ohosコンポーネントは、基本データクラス、配列、マップ、セット、シリアル化可能なデータクラスなど、様々なデータ型のシリアル化とデシリアル化をサポートします。各データ型に含まれる具体的なデータクラスは以下のとおりです。

  • 基本データ型: int、float、String、boolean...;
  • 配列: PlainArray、PlainBooleanArray;
  • Map クラス: Map、HashMap、LinkedHashMap...;
  • Set クラス: Set、HashSet、SortedSet...;
  • シリアル化可能なデータ クラス: Sequensable、Serializable。

このコンポーネントは、int、float、String、plainArray、Sequenceable の 5 つのデータ型を例として使用して、シリアル化と逆シリアル化を示します。

コンポーネントアイコンをクリックすると、「Parceler Test」メインインターフェースが表示されます。このインターフェースには、「int Test」、「float Test」、「String Test」、「plainArray Test」、「Sequenceable Test」の5つのボタンがあります。これらのボタンをクリックすると、対応するデータ型のシリアル化およびデシリアル化テストインターフェースが表示されます。これらの5つのデータ型のテスト手順は同じであるため、以下のデモでは、コンポーネント内のintデータのテスト結果を例として使用します。

「intテスト」ボタンをクリックして「int形式テスト」インターフェースに入ります。「テスト開始」ボタンをクリックして、事前に設定された整数テストデータ「34258235」をシリアル化されたバイトストリームに変換し、「シリアル化:」というテキストの下にシリアル化結果を表示します。その後、バイトストリームをデシリアル化し、シリアル化前のオブジェクト「34258235」として出力します。「戻る」ボタンをクリックしてメインインターフェースに戻ります。テスト結果は図1に示されています。


図1. intデータのシリアル化とデシリアル化のテスト

サンプル分析

サンプルプロジェクトのMainMenuファイルは、コンポーネントアプリケーションのメインインターフェースを構築するために使用されます。その他のファイルは、コンポーネントの効果を示すために使用する5種類のデータに対するシリアル化およびデシリアル化のテストインターフェースを構築するために使用されます。これらの対応関係は図2に示されています。テスト手順は様々なデータ型で同じであるため、以下ではintデータのシリアル化およびデシリアル化テストを例に挙げて詳しく説明します。他のデータ型についてはこれ以上説明しません。


図 2 は、サンプル内のファイルとテスト インターフェイスのボタンの対応を示しています。

メインメニューファイル

メインインターフェースのレイアウトは比較的シンプルで、主に5つの異なるデータテストインターフェースにアクセスするためのボタンで構成されています。具体的な手順は以下のとおりです。

  • ステップ 1: 5 種類のデータのテスト ボタンとタイトルを宣言します。
  • ステップ 2: ページ レイアウトを作成します。
  • ステップ 3: int データ テスト ボタンのリスナーを設定します。
  • ステップ 4: int データ テスト ボタンを作成します。

1. 5 種類のデータのテスト ボタンとタイトルを宣言します。

「Parceler Test」というタイトルを表示するテキスト フィールドと、特定のテスト インターフェイスに移動するための 5 つのボタンを宣言し、タイプ別に名前を付けます。

  1. private Text title; // タイトル: "Parceler Test"
  2. private Button IntTestButton; // intデータ テスト ボタン
  3. private Button FloatTestButton; //フロートテストボタン
  4. private Button StringTestButton; //文字列テストボタン
  5. private Button PlainArrayTestButton; // PlainArrayデータテストボタン
  6. private Button SequenceableTestButton; // シーケンス可能なデータテストボタン

2. ページレイアウトを作成する

幅と高さが親コントロールに合わせて調整され、上、下、左、右の方向にそれぞれ 10/32/10/80 のパディング間隔で垂直に配置されたページ レイアウトを作成し、背景色を白に設定します。

  1. プライベート DirectionalLayout directionalLayout = new DirectionalLayout(this);
  2. ……
  3. 方向レイアウトの幅を設定します(ComponentContainer.LayoutConfig.MATCH_PARENT);
  4. 方向レイアウトの高さを設定します(ComponentContainer.LayoutConfig.MATCH_PARENT);
  5. 方向レイアウトの方向を設定します(Component.VERTICAL);
  6. directionalLayout.setPadding(10, 32, 10, 80); // パディング間隔
  7.  
  8. ShapeElement 要素 = 新しい ShapeElement();
  9. element.setShape(ShapeElement.RECTANGLE);
  10. element.setRgbColor(new RgbColor(255, 255, 255)); // 白背景
  11. 方向レイアウト.setBackground(要素);

3. int データ テスト ボタンのリスナーを設定します。

「int data test」ボタンにonClick()イベントリスナーを設定し、ボタンをクリックすると「int data test」インターフェースにリダイレクトされるようにします。具体的なコードは以下のとおりです。

  1. //ボタンリスナーを初期化する
  2. Component.ClickedListener intTestListener = 新しいComponent.ClickedListener() {
  3. @オーバーライド
  4. パブリックvoid onClick(コンポーネント コンポーネント) {
  5. AbilitySlice intSlice = 新しい IntTest();
  6. インテント intent = 新しい Intent();
  7. present(intSlice, intent); // intデータテストインターフェースにジャンプ
  8. }
  9. };

4. intデータテストボタンを作成する

サンプル内の5つのテストボタンは、表示されるテキストとボタンリスナーイベントを除き、すべての点で同一です。これらの属性には、ボタンの色、ベベル、表示されるテキストサイズ、配置、パディング距離が含まれます。

  1. プライベート DirectionalLayout.LayoutConfig layoutConfig = 新しい DirectionalLayout.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_CONTENT、ComponentContainer.LayoutConfig.MATCH_CONTENT);
  2. ……
  3. //ボタンのプロパティを設定する
  4. ShapeElement の背景 = 新しい ShapeElement();
  5. background.setRgbColor(new RgbColor(0xFF51A8DD)); // ボタンの色を作成する
  6. background.setCornerRadius(25); // ボタンの境界線の半径を作成する
  7. layoutConfig.alignment = LayoutAlignment.HORIZONTAL_CENTER; // 配置方法
  8. layoutConfig.setMargins(0,100,0,0); // ボタンの余白の位置
  9. ……
  10. //整数データを処理するボタンを作成する
  11. IntTestButton = new Button(this); // ボタンを作成する
  12. IntTestButton.setLayoutConfig(layoutConfig);
  13. IntTestButton.setText( "int Test" ); //ボタンのテキスト
  14. IntTestButton.setTextSize(80); // テキストサイズ
  15. IntTestButton.setBackground(background); // ボタンの背景
  16. IntTestButton.setPadding(10, 10, 10, 10); // ボタンのパディング間隔
  17. IntTestButton.setClickedListener(intTestListener); // ボタンリスナー
  18. directionalLayout.addComponent(IntTestButton); // レイアウトにボタンを追加する

IntTestファイル

上記でメインインターフェースのレイアウトを紹介しました。次に、intデータのシリアル化とデシリアル化を例に、Parceler_ohosコンポーネントの使い方を説明します。このコンポーネントは、intデータを以下の4つのステップで処理します。

  • ステップ 1: 関連するクラスをインポートします。
  • ステップ 2: レイアウトを作成します。
  • ステップ 3: 入力データと出力データを設定します。
  • ステップ 4: テスト ボタンを作成します。

1. 関連するクラスをインポートする

IntTestファイルでは、importキーワードを使用してParcelsクラスがインポートされています。このクラスは、データのシリアル化とデシリアル化のための特定のメソッドを提供します。

  1. org.parceler.Parcels をインポートします。

2.レイアウトを作成する

整数データを表示するための縦向きのテストページレイアウトを作成します。幅と高さは親コントロールに合わせて調整します。上、下、左、右のパディングはそれぞれ32/32/80/80にします。背景色は白に設定します。

  1. プライベート DirectionalLayout directionalLayout = new DirectionalLayout(this);
  2. ……
  3. //レイアウトプロパティ
  4. 方向レイアウトの幅を設定します(ComponentContainer.LayoutConfig.MATCH_PARENT); 方向レイアウトの高さを設定します(ComponentContainer.LayoutConfig.MATCH_PARENT);
  5. 方向レイアウトの方向を設定します(Component.VERTICAL);
  6. 方向レイアウト.setPadding(32, 32, 80, 80);
  7. //ShapeElementを使用して背景を設定する
  8. ShapeElement 要素 = 新しい ShapeElement();
  9. element.setShape(ShapeElement.RECTANGLE);
  10. 要素.setRgbColor(新しいRgbColor(255, 255, 255));
  11. 方向レイアウト.setBackground(要素);

3. 入力データと出力データを設定する

まず、入力データとして整数を設定します。テストデータ値 34258235 を整数オブジェクト `intIn` に割り当て、`setText()` メソッドを使用して、インターフェイス上に「Input: 34258235」という形式でテキストとして表示します。次に、2つの `Text` オブジェクトをインスタンス化します。1つは入力データのシリアル化された出力を「Serialization: xxx」という形式で表示するための `wrappedOutput` オブジェクト、もう1つはシリアル化結果のデシリアル化された出力を「Output: xxx」という形式で表示するための `output` オブジェクトです。

  1. //入力データを設定する
  2. intIn = 34258235;
  3. input.setText( "Enter: " + intIn);
  4. 方向レイアウト.addComponent(入力);
  5. // 初期化とシリアル化後の出力
  6. wrappedOutput = 新しいテキスト(これ);
  7. wrappedOutput.setText( "シリアル化:" );
  8. 方向レイアウトにコンポーネントを追加します(ラップされた出力)。
  9. // 初期化とデシリアライズ後の出力
  10. 出力= 新しいテキスト(これ);
  11. output.setText ( "出力:" );
  12. directionalLayout.addComponent(出力);

4. テストボタンを作成する

シリアル化とデシリアライズ処理をトリガーするインターフェース上のボタンは「テスト開始」ボタンです。このボタンをクリックすると、wrap()メソッドが呼び出され、入力データに対してシリアル化処理が実行され、結果のバイトストリーム情報が、上記の手順3で実装した「Serialization:」テキストの後に表示されます。unwrap()メソッドが呼び出され、シリアル化されたバイトストリームに対するデシリアライズ処理が完了し、結果の整数オブジェクトが、上記の手順3で実装した「Output:」テキストの後に出力されます。

  1. private DirectionalLayout.LayoutConfig layoutConfig = new DirectionalLayout.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_CONTENT,ComponentContainer.LayoutConfig.MATCH_CONTENT); //幅と高さは親コントロールに従います
  2. ……
  3. //「テスト開始」ボタンを作成する
  4. beginTestButton = 新しいボタン(this);
  5. layoutConfig.alignment = LayoutAlignment.HORIZONTAL_CENTER; //中央揃え
  6. layoutConfig.setMargins(0,100,0,0);
  7. beginTestButton.setLayoutConfig(layoutConfig);
  8. beginTestButton.setText( "テストを開始" ); //テキスト
  9. beginTestButton.setTextSize(80); // 文字サイズ
  10. ShapeElement の背景 = 新しい ShapeElement();
  11. 背景.setRgbColor(新しいRgbColor(0xFF51A8DD));
  12. background.setCornerRadius(25); // ベベルコーナー
  13. beginTestButton.setBackground(background); //背景
  14. beginTestButton.setPadding(10, 10, 10, 10); // パディング距離
  15. beginTestButton.setClickedListener(beginTestListener); //聞く
  16. directionalLayout.addComponent(beginTestButton); // レイアウトにボタンを追加する
  17. //ボタンリスナー
  18. パブリックvoid onClick(コンポーネント コンポーネント) {
  19. // シリアル化テスト
  20. intWrapped = Parcels.wrap(intIn);
  21. //デシリアライズテスト
  22. intOut = Parcels.unwrap(intWrapped);
  23. ……
  24. }

ライブラリ分析

Parceler_ohosコンポーネントは、様々なタイプのデータに対してシリアル化およびデシリアライズ処理を実行できます。Parceler_ohosを使用してシリアル化およびデシリアライズ処理を実装するには、Pacelsクラスのwrap()メソッドとunwrap()メソッドを呼び出し、処理対象のデータを唯一のパラメータとして渡すだけです。具体的な使用方法は上記のサンプル分析で詳しく説明しているため、ここでは繰り返しません。以下のセクションでは、Parceler_ohosコンポーネントのシリアル化およびデシリアライズ処理の原理と、コンバータの原理について説明します。

1. シリアル化とデシリアル化の方法の原理の分析

(1) wrap()メソッドはシリアル化を実装する

シリアル化操作の原理と関数呼び出しの関係は、以下の図 3 に示されています。


図3.シリアル化動作原理の模式図

Parcels クラスでは、メソッドオーバーロードによって 2 つの wrap() メソッドが実装されています。最初の wrap() メソッドは開発者が直接呼び出すメソッドで、入力データを 1 つだけパラメータとして受け取ります。入力データが空でないことを確認した後、入力データの特定のデータ型を取得し、2 番目の wrap() メソッドを呼び出します。

  1. //wrap() メソッド 1
  2. @SuppressWarnings( "チェックなし" )
  3. 公共 静的<T> シーケンシャルラップ(T 入力) {
  4. if(input == null ) { // null チェック、入力データが空かどうかを判断します。
  5. 戻る  null ; // 入力データが空の場合は空の値を返します。
  6. }
  7. return wrap(input.getClass(), input); // wrap()メソッド2を呼び出す
  8. }

2番目の`wrap()`メソッドは、入力データ型とデータ自体の2つの引数を取ります。入力データが空でないと判断されると、入力データ型`inputType`を`ParcelCodeRepository`クラスの`get()`メソッドの引数として渡し、`Parcelable`ファクトリオブジェクト`parcelableFactory`を返します。次に、`parcelableFactory`を介して`buildParcelable()`メソッドを呼び出し、実際のシリアル化操作を実行します。

  1. //wrap() メソッド 2
  2. @SuppressWarnings( "チェックなし" )
  3. 公共 静的<T> シーケンシャルラップ(Class<? extends T> inputType, T input) {
  4. if(input == null ) { // null チェック、入力データが空かどうかを判断します。
  5. 戻る  null ; // 入力データが空の場合は空の値を返します。
  6. }
  7. //ParcelCodeRepository クラスは値を取得し、それをファクトリ クラス オブジェクトに割り当てます。
  8. ParcelableFactory parcelableFactory = REPOSITORY.get(inputType);
  9. return parcelableFactory.buildParcelable(input); // ファクトリクラスオブジェクトは特定のシリアル化操作を実行します。
  10. }

`parcelableFactory.buildParcelable(input)` メソッドの具体的な実行プロセスは次のとおりです。

a. ジェネリッククラスインターフェースを実装する

ParcelableFactory ジェネリック クラス インターフェイスを実装し、入力データを引数として buildParcelable(input) メソッドに渡します。

  1. public interface ParcelableFactory<T> { //ParcelableFactoryジェネリッククラスインターフェース
  2. 文字列 BUILD_PARCELABLE = "buildParcelable" ;
  3. `Sequenceable buildParcelable(T input);` // Parcelable と入力データを入力パラメータとして渡します。
  4. }

b. パラメータ型が入力データ型と一致する `buildParcelable(input)` メソッドを呼び出します。

LibraのNonParcelRepositoryクラスでは、複数のクラスがParcelableFactoryインターフェースを実装しており、その結果、buildParcelable(input)メソッドのパラメータ形式は、boolean、char、List、Integer、setといった様々な入力データ型に対応しています。入力データがint型の場合、パラメータ型Integerを指定したbuildParcelable(input)メソッドを呼び出して、新たにインスタンス化されたIntegerParcelableオブジェクトを返す必要があります。

  1. プライベート静的クラスIntegerParcelableFactoryはParcels.ParcelableFactory <Integer>を実装します
  2. @オーバーライド
  3. public Sequenceable buildParcelable( Integer input) { // パラメータ型はInteger  
  4. return new IntegerParcelable(input); // 入力データをパラメーターとして、新しくインスタンス化された IntegerParcelable オブジェクトを返します。
  5. }
  6. }

c. IntegerParcelableクラスのオブジェクトをインスタンス化する

コンバータクラスオブジェクトCONVERTERをインスタンス化し、IntegerParcelableクラスのコンストラクタを介してインスタンス化操作を完了します。このステップでは、入力データとコンバータCONVERTERを入力パラメータとして、IntegerParcelableの親クラスのコンストラクタを呼び出す必要があります。

  1. 公共 静的最終クラス IntegerParcelable は ConverterParcelable< Integer > を拡張します {
  2. private static final NullableParcelConverter<Integer> CONVERTER = new NullableParcelConverter<Integer> ( ) {...//コンバータークラスオブジェクトをインスタンス化します
  3. @オーバーライド
  4. public void nullSafeToParcel(整数入力, Parcel parcel) {
  5. parcel.writeInt(入力);
  6. }
  7. };...
  8. public IntegerParcelable( Integer value) { // IntegerParcelableクラスのコンストラクタ
  9. super(value, CONVERTER); // 親クラスのコンストラクタを呼び出す
  10. }...
  11. }

d. 親クラスのコンストラクターを呼び出して、コンバータークラスのインターフェイスを実装します。

親クラスConverterParcelableのコンストラクタが呼び出されると、まず対応するコンバータクラスにTypeRangeParcelConverterクラスインターフェースのtoParcel()メソッドとfromParcel()メソッドが実装されます。次に、対応するコンバータクラスのtoParcel()メソッドが呼び出され、シリアル化操作が完了します。コンバータクラスの具体的な実装原理については後ほど詳しく説明するため、ここでは繰り返しません。

  1. //TypeRangeParcelConverterクラスインターフェース
  2. パブリックインターフェース TypeRangeParcelConverter<L, U extends L> {
  3. void toParcel(L 入力、ohos.utils.Parcel パーセル);
  4. U fromParcel(ohos.utils.Parcel パーセル);
  5. }
  6.  
  7. @Override // シリアル化操作を実行する
  8. パブリックブールマーシャリング(パーセルパーセル) {
  9. converter.toParcel(value, parcel); // コンバータークラスのtoParcel()メソッドを呼び出して、シリアル化操作を完了します。
  10. 戻る 間違い;
  11. }

(2) unwrap()メソッドはデシリアライゼーションを実装します。

デシリアライズを実装するには、入力データが空でないことを確認した後、入力データの値を ParcelWrapper インターフェースの汎用クラス オブジェクトに割り当て、クラス オブジェクトを通じて getParcel() メソッドを呼び出して、入力データのデシリアライズ結果を取得します。

  1. @SuppressWarnings( "チェックなし" )
  2. 公共 静的<T> T unwrap(シーケンス可能な入力) {
  3. if(input == null ) { // null チェック、入力データが空かどうかを判断します。
  4. 戻る  null ; // 入力データが空の場合、空の値を返します。
  5. }
  6. ParcelWrapper<T> ラッパー = (ParcelWrapper<T>) 入力;
  7. wrapper.getParcel()を返します
  8. }

wrapper.getParcel() メソッドの具体的な実行プロセスは次のとおりです: ParcelWrapper ジェネリック クラス インターフェイスを実装し、getParcel() メソッドを実装して具体的な逆シリアル化操作を実行し、最後に逆シリアル化された "@Parcel" インスタンスを返します。

  1. public interface ParcelWrapper<T> { // ParcelWrapperジェネリッククラスインターフェースを実装する
  2. 文字列 GET_PARCEL = "getParcel" ;
  3. `T getParcel();` // 実際のデシリアライズ操作を実行し、`@Parcel` インスタンスを返すには、`getParcel()` メソッドを実装する必要があります。
  4. }

2. コンバータ処理原理

前述の`wrap()`シリアル化メソッドの説明で述べたように、`Parceler_ohos`コンポーネントのコンバーター処理クラスは`Converter`クラスです。これらのクラスは、nullチェックやデータ収集の反復処理など、データ収集に関連する複雑で冗長なさまざまなタスクの処理を担い、対応するAPIパッケージ内でデータ収集変換に簡単にアクセスできるように、`converter`という名前のパッケージにパッケージ化されています。これらのコンバーター処理クラスは、基本データ型、Map型、Set型など、さまざまなデータ型を変換できます。具体的な変換原理はほぼ同じです。以下では、文字列配列コンバーターである`CharArrayParcelConverter`クラスを例に説明します。

CharArrayParcelConverter クラスには、toParcel() と fromParcel() という2つのメインメソッドが含まれており、これらはそれぞれ Parcel オブジェクトへの書き込みと読み取り操作を担います。toParcel() メソッドでは、まず文字列データが空かどうかを確認します。空の場合、配列データが Parcel オブジェクトに書き込まれ、書き込まれるデータは NULL です。空でない場合、書き込まれるデータは長さとデータ自体を含む文字列配列です。

  1. パブリッククラスCharArrayParcelConverterはParcelConverter< char []>を実装します{
  2. プライベート静的最終int   NULL = -1;
  3. @オーバーライド
  4. public void toParcel( char [] array, Parcel parcel) {
  5. if (array == null ) { // 文字列配列が空かどうかをチェックする
  6. parcel.writeInt( NULL ); // 空の場合はNULLデータを書き込みます。
  7. } else { // 空でない場合は文字列配列に書き込む
  8. parcel.writeInt(array.length); // 書き込むデータの長さ
  9. parcel.writeCharArray(array); // 配列データを書き込む
  10. }
  11. }
  12. ...
  13. }

fromParcel() メソッドでは、まず Parcel オブジェクトから配列の長さが読み取られます。長さが空の場合は空の値、そうでない場合は新しい文字列配列がインスタンス化されます。Parcel オブジェクトを介して readCharArray() メソッドが呼び出され、新しくインスタンス化された配列を入力パラメータとして配列からデータが読み取られます。これにより、読み取られたデータは新しくインスタンス化された配列に格納され、後で簡単に使用および処理できるようになります。その後、新しい配列が返され、読み取りプロセスが完了します。

  1. ...
  2. @オーバーライド
  3. 公共  char [] fromParcel(Parcel parcel) {
  4. char [] 配列;
  5. 整数  size = parcel.readInt(); // Parcel オブジェクトから配列の長さを読み取ります。
  6. if ( size == NULL ) { // 長さが空の場合
  7. array = null ; // 空の値を取得します。
  8. } else {//長さが空でない場合
  9. array = new char [ size ]; // 新しい配列をインスタンス化する
  10. parcel.readCharArray(array); // 配列からデータを読み取り、新しい配列に保存します。
  11. }
  12. return array; // 新しい配列を返す
  13. }
  14. }

詳細については、以下をご覧ください。

51CTOとHuaweiが共同で構築したHarmonyOSテクノロジーコミュニティ。

https://harmonyos..com