JDファイナンスのAndroid痩身の探求と実践

著者: JD Technology Feng Jianhua

1. 背景

ビジネスの継続的な反復更新により、アプリのサイズも急速に増加しています. 2019 年から 2022 年にかけて、一時は 1 億 1,700 万を超えました. この期間中に、図 1 の赤い部分に示すように、いくつかの最適化も行いました. ただし、 , 最適化しながら新たな課題に直面しています. インクリメンタル コード, パッケージ サイズは増加し続けています. パッケージ サイズは、ダウンロードのコンバージョン率、インストール時間、ディスク容量などの重要な指標に直接的または間接的に影響するため、インストール パッケージ サイズのより深い最適化を検討することに力を注ぐ必要があります。Google ストアの内部データによると、図 2 に示すように、APK サイズが 1,000 万減少するたびに、ダウンロード コンバージョン率が平均で最大 1.5% 増加する可能性があります。

図 1 JD ファイナンス Android バージョン 2019-2022 のボリューム変更プロセス (赤い部分は期間中に行われた最適化の一部ですが、すぐに元に戻りました)

図 2 Google ストア アプリのコンバージョン率の増加 / 1,000 万 [1]

そこで、2022年9月より金融アプリのスリム化に向けた特別修正を実施し、状況の増加を考慮せず、業務コードを削除することなく、117Mから74Mへのスリム化を実現しました。今回はインストール パッケージをダウンして、多くの落とし穴に遭遇しましたが、いくつかの経験も蓄積しました。ここで共有します。

2.APK分析

次に、Apk のさまざまなコンポーネントと、ZIP としての Apk の標準構造を簡単に分析して、パッケージのスリム化の目標設定とタスク分解のためのデータ サポートを提供します。

2.1 APK コンテンツ分析

図 3 APK の構造

•classes.dex APK には 1 つ以上の classes.dex ファイルが含まれる場合があり、アプリケーション内の Java/Kotlin ソース コードは、最終的にバイトコードの形式で classes.dex ファイルに存在します。

•resources.arsc  aapt ツールは、リソースをコンパイルするときに、一部のリソースまたはリソース インデックスを resources.arsc にパッケージ化します。

•res/ ソースコードプロジェクトの res ディレクトリ配下の値以外のリソースファイルで、これらのファイルのパスが同時に resources.arsc に記録されます。

• lib/nativeLibraries、つまり、ソース コード プロジェクトの jni ディレクトリの下の so ファイルであり、セカンダリ ディレクトリは NDK がサポートする ABI です。

•assets/ は res/ リソース ディレクトリとは異なります. assets/ の下のリソース ファイルは resources.arsc にクエリ エントリを生成しません. assets/ の下のリソース ディレクトリは完全にカスタマイズしてプログラムの AssetManager オブジェクトから取得できます.

•META-INF/ このフォルダには、主に CERT.SF および CERT.RSA 署名ファイル、および MANIFEST.MF マニフェスト ファイルが含まれています

•AndroidManifest.xml アプリケーション マニフェスト ファイル。主にアプリケーション パッケージ名、アプリケーション ID、アプリケーション コンポーネント、必要な権限、デバイスの互換性など、アプリケーションの基本情報を記述するために使用されます。

2.2 SDK サイズ分析

図 4 に示すように、自社開発のエネルギー効率改善プラットフォーム Pandora[7] を通じて、SDK のサイズを直感的に確認できます。

図 4 SDK サイズの並べ替え (バージョン番号を含む)

 

図5 SDKに含まれるSOライブラリの一覧とサイズ

SDKの分析と業務との組み合わせにより、どの業務がプラグインに適しているかを判断し、パッケージサイズを直感的に縮小します。

2.3 ZIP 構造解析

zipinfo コマンドを使用して、圧縮パッケージ内の各ファイルの詳細情報ログを出力できます。使用法: zipinfo -l --t --h test.apk > test.txt

図 6 に示すように、出力ログ ファイルが開きます。各ファイルには、ファイル名、元のサイズ、圧縮サイズ、およびその他の指標を含む圧縮情報が 1 行あります。

 

図 6 APK 内のファイル情報のサイズ

上記のログ情報を1行ずつ分析し、難読化を解除したファイル名のパスとファイルの種類で分類・カウントすると、各種ファイル数、合計サイズ、1ファイルのサイズ、その他の指標 ファイル サイズ インデックスを作成します。

3.痩身の練習

全体的な実装パスを図 7 に示します。これは主に次のように分けられます。

1. Gradleプラグイン(コードの侵入なし、自動化)を介した従来の技術的ソリューションにより、コンパイル中にAPPのスリム化を完了します。

2. 高度な技術ソリューション、プラグインまたは SO 動的ダウンロードにより、一部のビジネス ラインを異なる方法で変換します。

3. ビジネス ラインのデータ埋め込みポイントに基づくビジネス最適化計画は、ランキング用のアクセス UV を生成し、より低い UV のビジネス ラインをアーキテクチャ委員会にフィードバックして、オフライン化できるか、高度な技術ソリューションを通じて変換できるかを評価します。 (2) その後、パッケージのサイズを縮小します。

 

図 7 全体的な実装パス

3-1 従来の技術的解決策

3-1-1 画像処理

上記の APP の分析の後、イメージが最大のボリュームを占めることが結論付けられました.したがって、SDK を含む APP 内のすべてのイメージは、コンパイルおよびパッケージングプロセス中にスリミングタスクを通じて自動的に最適化されます.全体的な最適化スキームが示されています.図 8:

図 8 画像最適化スキーム

1. マルチ DPI 最適化:

異なる解像度やモードのデバイスに適応するために, Android は、同じリソースの複数の構成を持つ開発者向けのリソース パスを設計しています. アプリがリソースを介して画像リソースを取得すると、デバイスの構成に従って適応したリソースを自動的にロードしますが、これらの明らかな問題は、高解像度のデバイスに低解像度の役に立たないイメージが含まれているか、低解像度のデバイスに高解像度の役に立たないイメージが含まれていることです。

通常、国内のアプリケーション市場では、パッケージ サイズを小さくするために、アプリは市場シェアが最も高い dpi のセット (Google は xxhdpi を推奨) を選択して、すべてのデバイスと互換性を持たせます。海外アプリケーション市場向けのアプリの多くは、パッケージ化され、AppBundle を通じて Google Play にアップロードされ、dpi を動的に分散する機能を利用できます。解像度の異なる携帯電話は、異なる dpi の画像リソースをダウンロードできるため、複数のセットを提供する必要があります。すべてのデバイスを満たす dpi の。このプロジェクトでは、dpi のセットが 1 つしかない写真もあれば、dpi のセットが複数ある写真もあります。上記の 2 つのシナリオでは、パッケージ化時にリソースをマージしてリソースをコピーし、パッケージ サイズを縮小しました。

2. webp 形式に変換します。

WebPは Google が提供する非可逆圧縮と可逆圧縮をサポートする画像ファイル形式で、JPEG や PNG よりも優れた圧縮を提供できます。Android 4.0 (API レベル 14) の非可逆 WebP 画像、Android 4.3 (API レベル 18) 以降の可逆および透過 WebP 画像をサポート

したがって、プラグインを使用して、コンパイル期間中にGoogleが提供するシェルプログラムを介して画像の形式を変換し、変換により古い画像が正常に削除され、APKスリム化の効果が得られます

3.png圧縮

Pngquant は使いやすい png 圧縮ツールであり、非可逆画像圧縮を実行できるコマンドライン ツールであるため、1 と 2 の処理の後、Pngquantを使用して2 次圧縮を実行し、より優れた画像スリミングを実現できます。

3-1-2 Rファイルのインライン最適化

DEX は Java/Kotlin ソース コードからコンパイルされたバイトコード ファイルです. DEX の最適化は実際にはバイトコード ファイルを最適化する方法です. DEX には多数のリソース インデックス R ファイルが含まれています. ここでは主にリソース ID を介してインライン化する方法について説明します. APK スリム化の目的を達成するために R ファイルを削除します。

Rファイルスリム化の実現可能性分析

日常の開発段階では、R.xx.xx を通じてメイン プロジェクトのリソースを参照し、コンパイル後に、R クラス参照に対応する定数がクラスにコンパイルされます。

setContentView(2131427356);

この変更は、Java のメカニズムであるインライン化と呼ばれます (定数が static final としてマークされている場合、定数は Java コンパイル中にコードにインライン化され、変数のメモリ アドレス指定が 1 回削減されます)。非メイン プロジェクトでは、R クラスのリソース ID は参照によってクラスにコンパイルされ、インライン化は生成されません。

setContentView(R.layout.activity_main);

この現象の原因は、AGP パッケージ ツールが原因です。詳しくは、Rファイルのandroid gradleプラグインの処理過程を確認できます。結論: プログラムは R クラス ID がインライン化された後に実行できます, しかし、すべてのプロジェクトが自動的にインライン現象を生成するわけではありません. 適切なタイミングで R クラス ID をプログラムにインライン化する技術的手段を使用する必要があります. R ファイルに依存している場合ここでも、R ファイルを削除して、アプリケーションが正常に実行されている間にパッケージのサイズを縮小するという目的を達成できます. 図 9 に示すように、コンパイルが完了すると、多数の R ファイルが生成されます。

図9 プロジェクトRファイル生成の模式図

全体的なスキームを図 10 に示します。

図 10 R ファイルの最適化プロセス

: 置換フェーズでは、次に示すように、置換の完了後に実行時に ResourceNotFind 例外が表示されないように、2 次チェックを追加する必要があります。

try {
    int value = RManager.checkInt(type, name);
}catch (Exception e){
    String errorMsg = "resource is not found(I),className="+className+",fieldName="+owner+"."+name;
    throw new ResourceNotFoundException(errorMsg);
}
try {
    int[] value = RManager.checkIntArray(type, name);
}catch (Exception e){
    String errorMsg = "resource is not found(I[]),className="+className+",fieldName="+owner+"."+name;
    throw new ResourceNotFoundException(errorMsg);
}

 

3-1-3 リソース混乱のための AndResGuard

1. リソースの読み込みプロセスの分析

開発プロセスでは、aapt によって生成された R.java 内の定数を使用してリソースを使用し、コンパイル後に定数が使用されている場所は、次のように定数値に置き換えられます。

final View layout = inflater.inflate(2131165182, container, false);

つまり、int 値を使用して Resource を介してリソースを検索します。では、Resource は int 値を介して特定のリソースをどのように見つけるのでしょうか? apk を解凍すると、中に resources.arsc ファイルがあることがわかります. このファイルも aapt によって生成されます. リソース ID とリソース キーのマッピング関係がファイルに保存されています. リソースはこのマッピング関係に従ってリソースを検索します.

2.resources.arsc:

図 11 は、resources.arsc に格納されているマッピング関係を示しています。resources.arsc はリソース マッピング データベースとして理解でき、特定のパスと名前は ID に従ってマッピングされます。

 

図 11 resources.arsc 分析

APK を解凍した後、リソース ファイル名を短いチェーンに変換します。たとえば、res/layout/hello.xml を r/l/a.xml に変換し、resources.arsc に対応する値を変更して、全体的なスリム化効果を実現します。

AndResGuard[5] は WeChat が提供するリソース最適化ツールで、基本的な考え方は ProGuard の難読化に似ており、上記のソリューションを実現できます。

3-1-4 7zip圧縮

7zip コマンドの説明:

-t: 圧縮タイプを指定し、7z、xz、split、zip、gzip、bzip2、tar などをサポートします。

-m: 圧縮アルゴリズムを指定します。デフォルトは Deflate です。

具体的なプロセスは次のとおりです。

ステップ 1: 7z コマンドを使用して、署名されていないパッケージを指定されたディレクトリに解凍します: 7za x ${unsigned package} -o${7z decompressed directory}

ステップ 2: まず、7z コマンドを使用して、解凍されたすべてのディレクトリを圧縮します: 7za a -tzip -mx9 ${ターゲット 7z ファイル名} ${7z 解凍ディレクトリ}

ステップ 3: ストレージ タイプのファイルを取得し、Android SDK の aapt コマンドを使用して、圧縮モードが Stored であるファイルのリストを取得します: aapt l -v ${unsigned package}

ステップ 4: ストレージ タイプ ファイルを更新し、7z コマンドを使用してストレージ タイプ ファイルを 2 番目のステップで生成された 7zip インストール パッケージに更新します: 7za a -tzip -mx0 ${target 7z file name} ${storage type file directory }

3-1-5 CPUアーキテクチャの設定

さまざまな CPU アーキテクチャに応じてさまざまな種類のインストール パッケージをビルドします.現在、主流のデバイスは 64 ビット マシンであるため、Android 市場では主に arm64-v8a に基づいてコンパイルおよび構築されたインストール パッケージがリリースされます。

ndk {
    abiFilters arm64-v8a
}

3-1-6 arsc 圧縮

resources.arsc は圧縮によってボリュームが大幅に増加しますが、圧縮すると起動速度とメモリ メトリックに影響します。その理由は、システムがarscファイルをロードするとき、arscファイルが圧縮されていない場合はmmapを使用してメモリマッピングを行うことができます.arscファイルが圧縮されている場合は、圧縮解除してRAMバッファに読み込む必要があるため、メモリが増加します.メモリ使用量と起動速度が遅くなります。

同じ考慮事項として、targetSdkVersion>=30 の後、公式はこの方法を使用して resources.arsc を強制することはできません。そうしないと、インストールが直接失敗するため、この記事では詳しく説明しません。

3-1-7 国際化言語処理

JD Financial アプリは現在、国内市場でのみ動作していますが、接続されている多数の SDK に数十の言語が追加されているため、全体のサイズが大きくなっています. 評価後、構成することで無駄な言語リソースを削除できます. resConfigs.

defaultConfig {
    resConfigs "zh","en"
}

3-1-8 シュリンクリソース

ShrinkResources: コンパイル中に不要なリソース ファイル、つまり参照されていないリソースを検出して削除するために使用されます。

minifyEnabled: 参照されていないコードなどの無駄なコードの削除を有効にするために使用されるため、リソースが参照されているかどうかを知る必要がある場合は、minifyEnabled と組み合わせて使用​​する必要があります. 両方が true の場合にのみ、本当に無効なコードを削除しますコードと参照されていないリソースの目標。

その機能は、参照されていないリソース ファイルを小さい形式のファイルに置き換えることです(リソース エントリは保持されますが、まだフットプリントが存在するため、resources.arsc のボリュームは減りません)。このファイルには res/raw/ からアクセスできます。 .xml ファイルは、shrinkMode とホワイトリストを構成します。

buildTypes {
   release {
      // 不显示Log
      buildConfigField "boolean", "LOG_DEBUG", "false"
      //混淆
      minifyEnabled true
      // 移除无用的resource文件
      shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig sign.release
   }
}

3-1-9 コーディング制約

• 列挙型は、図 12 に示すように、バイトコードにコンパイルされた後にボリュームが大幅に増加するため、列挙型の使用はできるだけ少なくします (22 行のコードをコンパイルした後のバイトコードは 86 行です)。

図 12 列挙型のコンパイル済みバイトコードの比較

• 不要な LOG 出力を削除する

3-2 高度な技術ソリューション

SO ライブラリの動的ダウンロードとプラグイン技術は、本質的に動的ダウンロードのカテゴリです. 2 つのソリューションは、ビジネスで長期間継続して使用できます. 具体的な使用プロセスで選択する方法を図 13 に示します:

図 13 企業が高度なソリューションを選択する方法

3-2-1 SO ライブラリの動的ロード

APP 内の一部のビジネスはプラグイン変換に適していません.解体後、SO ライブラリが大きな割合を占めていることがわかりました.したがって、サイズを縮小するための変換には動的ダウンロードを検討できます.

SO ライブラリをロードする 2 つの方法

最初の方法では、SO ライブラリを直接ダウンロードして、指定したディレクトリに配置できます。

2つ目は、環境変数で設定したディレクトリにSOライブラリをロードする方法なので、SOライブラリを正常にロードするには、指定したディレクトリを環境変数に追加する必要があります

System.load("{安全路径}/libxxx.so") 
System.load("xxx") 

1. APP で SO ライブラリの環境変数の場所を設定する方法 (Tinker を参照):

final Field pathListField = ShareReflectUtil.findField(classLoader, "pathList");
final Object dexPathList = pathListField.get(classLoader);

final Field nativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "nativeLibraryDirectories");

List<File> origLibDirs = (List<File>) nativeLibraryDirectories.get(dexPathList);
if (origLibDirs == null) {
    origLibDirs = new ArrayList<>(2);
}
final Iterator<File> libDirIt = origLibDirs.iterator();
while (libDirIt.hasNext()) {
    final File libDir = libDirIt.next();
    if (folder.equals(libDir)) {
        libDirIt.remove();
        break;
    }
}
origLibDirs.add(0, folder);

final Field systemNativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "systemNativeLibraryDirectories");
List<File> origSystemLibDirs = (List<File>) systemNativeLibraryDirectories.get(dexPathList);
if (origSystemLibDirs == null) {
    origSystemLibDirs = new ArrayList<>(2);
}

final List<File> newLibDirs = new ArrayList<>(origLibDirs.size() + origSystemLibDirs.size() + 1);
newLibDirs.addAll(origLibDirs);
newLibDirs.addAll(origSystemLibDirs);

final Method makeElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class);

final Object[] elements = (Object[]) makeElements.invoke(dexPathList, newLibDirs);

final Field nativeLibraryPathElements = ShareReflectUtil.findField(dexPathList, "nativeLibraryPathElements");
nativeLibraryPathElements.set(dexPathList, elements);

2. 図 14 に示すように、指定した SO ライブラリとロード プロセス全体を削除する方法:

図 14 SO ライブラリの削除とロードのプロセス

3-2-2 プラグイン

プラグインとは:

プラグインは、Apk をビジネス機能に応じて異なるサブ Apk (つまり、異なるプラグイン) に分割することであり、各サブ Apk は個別にコンパイルおよびパッケージ化でき、統合された Apk が最終的にリリースおよび起動されます。Apk を使用すると、各プラグインが動的にロードされ、プラグインのホットフィックスおよびホット アップデートも可能になります。

•ホスト: メイン アプリは、プラグインをロードするために使用でき、ホストにもなります。

• プラグイン: プラグイン アプリ (ホストによって読み込まれるアプリ) は、通常のアプリと同じ Apk ファイルにすることができます

プラグイン変換に適したビジネス形態は次のとおりです。

• ビジネスは比較的独立しており、ホスト アプリから完全に切り離されています。

• 改修費用が低く、収入が比較的高い

• 大きな体積を占める

一連の評価の後、ビデオ ビジネスは上記の点を満たしています。変換後の効果は図 15 に示されています。

 

図 15 ビデオ ビジネス ホールのプラグイン変換の効果

3-3 業務最適化計画

ビジネスの増加に伴い、一部の古いビジネス UV はどんどん低くなってきているため、図 16 に示すように、一連のビジネス オフライン最適化プロセスが策定されています。

図 16 業務最適化ソリューションのプロセス

4. コントロール

痩身計画の実施は非常に重要であり、その後の制御がリバウンドしないことがより重要です. 痩身ガバナンスを行いながら、一方では正常化された制御メカニズムを模索しており、最終的に一連の制御規範を沈殿させました.および制御メカニズム。管理と制御の目的は、ビジネスの反復や新しいコードを制限することではなく、限られたコードでその機能をどのように実現し、日々のコーディングでエンジニアのスリム化に対する意識を向上させるかです。

4.1 SDK アクセス仕様

SDKの無秩序な拡張を防ぐため、SDKのアクセス仕様を策定し、機能確保を前提に、SDKのサイズを厳密に管理し、APPボリュームのリバウンドを極限まで抑えています。

4.2 制御プロセス

 

図 17 制御プロセス

コンテンツの追加、コンテンツの削除、コンテンツの増加、コンテンツの削減、重複ファイル、コード管理などのリソースファイルの変更に応じて、ガバナンス制御仕様などと組み合わせて、パッケージ化と構築を過去のバージョンと比較します。変更されたコンテンツを取得する 最適化の余地があるかどうかを評価し、最適化の目標を与え、最適化後にパッケージ統合を再構築します。

5. 成果とフォローアップ計画

5.1 成果

以上の施策により、JD Finance の Android 版は 2 四半期で 117M から現在の 74M まで 5 バージョンが繰り返され(図 18)、全体としては制御可能な範囲内に収まっています。同時に、次のバージョンの反復では、APK のスリム化を正規化し、パッケージ サイズを常に制御可能な範囲内に維持します。

 

図 18 金融 APP の痩身効果

5.2 その後の計画

技術的手段の継続的な最適化:

ビジネスの継続的な蓄積と反復は、常にいくつかの役に立たないリソースを生成するため、これらの役に立たないファイルとコードを定期的にクリーンアップして、インストール パッケージをスリム化する必要があります。

各バージョンを適切に監視し、バージョン間の違いを比較して、ビジネスに影響を与えずに技術的な手段を使用して最適化できることを確認してください。

オンライン管理および制御プラットフォームの構築:

初期段階では、オフラインの管理と制御が使用されていたため、実装に少し時間がかかりました. 今後は、オンラインの管理と制御プラットフォームの構築を改善し、アプリのリリースと構築プラットフォーム全体と統合し、組み立てラインのメカニズムを形成し、管理と制御で良い仕事をします。

概要: インストール パッケージのスリム化を探求するには、まだ長い道のりがあります. この記事では、一般的に使用されるスリム化ソリューションの一部のみをリストします. 巨大なプロジェクトの最適化に加えて、プロジェクト間のガバナンスにも優れた仕事をする必要があります.ユーザーエクスペリエンスを向上させるために、 APP のサイズを引き続き最適化します。

【参照】

[1] パッケージサイズとインストール変換率
https://medium.com/googleplaydev/shrinking-apks-growing-installs-5d3fcba23ce2

[2] プロガード https://www.guardsquare.com/proguard

[3] R8 https://r8.googlesource.com/r8

[4] ProGuard と R8 の比較
https://www.guardsquare.com/blog/proguard-and-r8

[5] AndResGuard https://github.com/shwenzhang/AndResGuard

[6] AGP https://developer.android.com/studio/releases/gradle-plugin

[7] Pandora: 分散型技術に基づく研究開発およびテスト段階のエネルギー効率を改善するためのツール

{{o.name}}
{{m.name}}

おすすめ

転載: my.oschina.net/u/4090830/blog/8590988
おすすめ