プラグインエンジニアリング R ファイル細化技術ソリューション | JD Cloud 技術チーム

ビジネスの発展とバージョンの反復に伴い、クライアント プロジェクトには新しいビジネス ロジックや新しいリソースが継続的に追加され、インストール パッケージのサイズが大きくなるという問題が発生します。初期段階では、さまざまなビジネス モジュールが無駄なリソースや大きな画像を圧縮したりクラウドに転送したり、オフラインで実験的にビジネスロジックを導入したりするなど、パッケージサイズの削減に一定の成果を上げています。

スリム化の過程で、R ファイルのスリム化という概念に注目しました。現在、JD APP はビジネス プラグイン プロジェクトとホスト プロジェクトを含むプラグインをサポートしています。ビジネス プラグイン パッケージ ファイルを分析した結果、次のことがわかりました。従来のリソースとコードに加えて、R クラス ファイルがパッケージのボリュームの約 3% ~ 5% を占めており、ホスト プロジェクトのパッケージ ファイルを分析したところ、R ファイルも約 3% を占めています。業界における R ファイル スリミングとオープン ソース プロジェクトの実現可能性を調査した後、私たちはプラグイン プロジェクトに適した一連の R ファイル スリミング テクノロジ ソリューションを検討してきました。

理論的根拠 - R ファイル

R ファイルは私たちが日常業務で扱うことが多い R.java ファイルですが、Android の開発仕様では、アプリケーションで使用するリソースを特別な名前のリソース ディレクトリに配置し、アプリケーションのリソースを外部化する必要があります。別途メンテナンス可能です。

アプリケーション リソースを外部化した後、R クラス ID を使用してプロジェクト内のこれらのリソースにアクセスできます。R クラス ID は一意です。

public class MainActivity  extends BaseActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

Android APK のパッケージ化プロセスでは、aapt (Android Asset Packaging Tool) によって R クラス ファイルがパッケージ化および生成されます。R クラス ファイルの生成時に、リソース ファイルも同時にコンパイルされて resource.arsc ファイルが生成されます。resource.arsc ファイルは、ファイル インデックス テーブルと同等であるため、アプリケーション層コードは R クラス ID を介して対応するリソースにアクセスできます。

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

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

 setContentView(2131427356);

この変更は、Java のメカニズムであるインライン化と呼ばれます (定数が static Final としてマークされている場合、その定数は Java のコンパイル中にコードにインライン化され、変数のメモリ アドレス指定が 1 回削減されます)。

非メイン プロジェクトでは、R クラスのリソース ID は参照によってクラスにコンパイルされ、インライン化は生成されません。

 setContentView(R.layout.activity_main);

この現象は、AGP パッケージング ツールが原因で発生します。詳細については、Android gradle プラグインの処理プロセスを R ファイルで確認できます。

結論: R クラス ID がインライン化された後、プログラムは実行できますが、すべてのプロジェクトでインライン現象が自動的に生成されるわけではありません。適切なタイミングで R クラス ID をプログラムにインライン化するには、技術的手段を使用する必要があります。R ファイルに依存する場合は、繰り返しますが、アプリケーションが通常に実行されている間に、パッケージのサイズを削減する目的を達成するために R ファイルを削除できます。

プラグインプロジェクト R ファイルスリム化の実践

技術的ソリューションの策定

現在、JD Android クライアントはプラグインをサポートしており、プラグイン プロジェクト全体にはパブリック ライブラリ (コンポーネントとホストによって共有されるクラスとリソースを保存するために使用される aar プロジェクト)、ビジネス プラグイン (プラグイン プロジェクトは独立したプロジェクトです。コンパイル済み製品 (ホスト環境で実行可能)、ホスト (動作環境を提供するメイン プロジェクト)。プラグインのプロセス中にホストとプラグインの間でリソースの競合を防ぐために、プラグインの packageId を変更することでリソースの一意性が保証されます。パブリック リソース ライブラリとホストは多くのビジネスによって依存されているため、これら 2 つのプロジェクトへの変更の影響の評価には多くのことが含まれます。プラグインは通常、ビジネス モジュール自体によって保守されており、問題はありません。そこで、業務用プラグインモジュールは、まずR式痩身実践に使用します。

ビジネス プラグイン プロジェクトで作成されたパッケージを逆コンパイルした後、R クラス ID にはインライン現象がなく、R クラス ファイルには一定のサイズがあることがわかり、パッケージ内の R ファイルを分析したところ、 R ファイルにはビジネス自体のリソースのみが含まれ、ビジネスの依存関係を含まないパブリック リソースの R クラスが含まれます。

public View onCreateView(LayoutInflater paramLayoutInflater, ViewGroup paramViewGroup, Bundle paramBundle) {
    this.b = paramLayoutInflater.inflate(R.layout.lib_pd_main_page, paramViewGroup, false);
    this.h = (PDBuyStatusView)this.b.findViewById(R.id.pd_buy_status_view);
    this.f = (PageRecyclerView)this.b.findViewById(R.id.lib_pd_recycle_view);}

業界のオープンソース プロジェクトの調査と分析と組み合わせて、JD.com に準拠する技術的ソリューションの策定を試み、ビジネス プラグインでの R タイプ ID インライン化の完了と、対応する R ファイルの削除を優先してください。

1.変換APIを通じて処理されるクラス ファイルを収集します。

Transform は、Android Gradle によって提供されるバイトコードを操作する方法であり、クラスが dex にコンパイルされる前に、一連の Transform プロセスを通じて .class ファイルを変更します。

@Override
public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation);
//  通过TransformInvocation.getInputs()获取输入文件,有两种
//  DirectoryInpu以源码方式参与编译的目录结构及目录下的文件
//  JarInput以jar包方式参与编译的所有jar包
    allDirs = new ArrayList<>(invocation.getInputs().size());
    allJars = new ArrayList<>(invocation.getInputs().size());
    Collection<TransformInput> inputs = invocation.getInputs();
    for (TransformInput input : inputs) {
        Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs();
         for (DirectoryInput directoryInput : directoryInputs) {
               allDirs.add(directoryInput.getFile());
             }
            Collection<JarInput> jarInputs = input.getJarInputs();
         for (JarInput jarInput : jarInputs) {
                allJars.add(jarInput.getFile());
             }
     }
}

2. 収集された .class ファイルを ASM フレームワークと組み合わせて分析および処理します

ASM は Java バイトコードを操作するためのクラス ライブラリであり、ASM を介して .class ファイルを簡単に変更できます。

R クラス ファイルの識別を優先し、ClassVisitor を通じて R.class ファイルにアクセスし、ファイル内の静的定数を読み取り、一時変数を保存します。

@Overridepublic FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {    //R类中收集 public static final int 对应的变量  if (JDASMUtil.isPublic(access) && JDASMUtil.isStatic(access) &&JDASMUtil.isFinal(access) &&JDASMUtil.isInt(desc)) {       jdRstore.addInlineRField(className, name, value);      }      return super.visitField(access, name, desc, signature, value);}

非 R ファイルの場合は、MethodVisitor を通じてコード内の R 参照を識別し、その参照に対応する値を取得して、ID 値を置き換えます。

@Override
    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
        if (opcode == Opcodes.GETSTATIC) {
            //owner:包名;name:具体变量名;value:R类变量对应的具体id值
            Object value = jdRstore.getRFieldValue(owner, name);
            if (value != null) {
              //调用该api实现值替换
                mv.visitLdcInsn(value);
                return;
            }
        }
        super.visitFieldInsn(opcode, owner, name, desc);
    }

*注意: 上記のコードは回路図コードの一部であり、非公式プラグイン コードです。

R タイプの Thin プラグインをビジネス モジュールに導入した後、ビジネス モジュールの機能は正常に動作するようになり、プラグイン パッケージのサイズは程度の差はありますが 3 ~ 5% 削減されました。

パブリック リソース R クラス ID インライン

JD.com Android クライアント コードでは、より多くのリソース ファイルがパブリック リソース ライブラリに集中しており、パブリック ライブラリによって生成される相対的な R ファイルも大きく、コンパイルされた APK パッケージの内容を分析した後、パブリック リソースの R ファイルがlibrary Class ファイルは最大 3% を占めます。

パブリック ライブラリはホストと一緒にパッケージ化され、ホストのパッケージ化プロセス中に R タイプのスリム化プラグインが導入され、パッケージ化された APK が大幅に削減されます。異常なクラッシュ現象、クラッシュ タイプは Rx リソースが見つかりません。クラッシュの理由は次のように分析されます: ビジネス プラグイン コードはパブリック ライブラリの R リソースを使用し、プラグインのパッケージ化プロセスはホストのパッケージ化から独立しています。プラグインのパッケージ化プロセス中、プラグインのインライン化のみが行われます。ビジネスモジュール R クラスが完了し、パブリック リソース R クラスのインライン化が完了すると、上記の理由に基づいて、ホスト パッケージ プロセスが R クラス ファイルの削除と間引きを完了すると、当然のことながら、パブリック リソース R クラスがインライン化される問題が報告されます。ビジネス プラグインの実行プロセス中に見つからないため、クラッシュが発生します。

この問題を解決するために、当初の計画では、ビジネス モジュールが使用するすべてのパブリック リソースを保持するホワイトリストのメカニズムを追加する予定でしたが、この考えはすぐに覆され、パブリック リソースの存在自体は、各ビジネス モジュールが直接参照できるようにすることを期待しています。これらのリソースを自分で定義せずに残しておくと、削除できないリソースが多くなり、減量効果が大幅に低下します。

予約されたソリューションは適切ではないため、パブリック リソースの R クラス ID もコードにインライン化されます。前述したように、JD.com はプラグインをサポートしており、プラグイン ソリューション全体は aura プラットフォームに基づいて実装されているため、aura チームと相談し、ソリューションの新しいエントリ ポイントを取得しました。

aura プラットフォームには、プラグインのプロセスで aapt2 を介してパブリック リソース ID を修正する機能が導入されました。この機能では、定義されたパブリック リソース ID は常に固定されます (各ビジネス プラグインで参照されるパブリック リソース ID は、パブリック リソース ライブラリ内の既存のリソースは、他のモジュールで繰り返し定義することはできません。そうしないと、以前に定義されたリソースが上書きされます。 **上記の結果とルールに基づいて、以前の R ファイル スリム化の機能を改善しました。 gralde プラグイン、およびパブリック リソース R クラス ID はプロジェクトにインライン化されます。

appt2 の 2 つのパラメーター -stable-ids と -emit-ids を使用して、リソース ID を修復する機能を実現し、修復された ID ファイルにshared_res_public.xml という名前を付けて、パブリック リソース ライブラリに保存します。パブリックリソースライブラリ パッケージ化およびコンパイルのプロセスにおいて、aura は、shared_res_public.xml をビジネスプロジェクトの中間の一時コンパイルフォルダーの下の指定された場所にコピーし、ビジネスモジュールのパッケージ化プロセスに参加します ファイルコンテンツの形式以下のとおりであります:

R ファイルを変更して Gradle プラグイン コードをスリム化し、指定された場所からパブリック リソースのこの部分を読み取って識別し、変数を <name, id> の形式で保存し、ビジネス モジュール内のパブリック リソースの ID を置き換えます。その後のプロセス。

public Map<String, String> parse() throws Exception {
        if (in == null) {
            return null;
        }
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document doc = builder.parse(in);
        Element rootElement = doc.getDocumentElement();
        NodeList list = rootElement.getChildNodes();
        ......
        return resNode;
    }
}

R リソース ID コードのインライン部分は次のとおりです。

public void visitFieldInsn(int opcode, String owner, String name, String desc) {
        if (opcode == Opcodes.GETSTATIC) {
            //优先从业务模块R类资源中查找
            Object value = jdRstore.getRFieldValue(owner, name);
            if (value != null) {
                mv.visitLdcInsn(value);
                return;
            }
           //从公共R类资源中查找
            value = getPublicRFileValue(name);
            if (value != null) {
                mv.visitLdcInsn(value);
                return;
            }
        }
        super.visitFieldInsn(opcode, owner, name, desc);
    }

ソリューションが完成した後、Shangqing ビジネス プラグインと組み合わせて検証され、ビジネス詳細とホストが R ファイルのインライン間引きを完了した後、Shangxiang モジュールのビジネス機能は異常現象なく正常に使用できます。 。

R ファイル インライン スリミング gradle プラグインがパッケージ化およびコンパイルの段階で導入されることを考慮して、プラグインの導入後のパッケージ化時間への影響もカウントしました。

データによると、R ファイルスリム化プラグインの導入は、全体のパッケージ化時間に大きな影響を与えていません。

これまでのところ、Jingdong Mall が検討したプラグイン エンジニアリング R ファイル痩身 Gradle プラグインに基づいて、開発は完了しました。現在、いくつかのビジネス プラグイン モジュールはオンラインで検証されています。機能の開始後、私たちはまた、クラッシュの観察とユーザーからのフィードバックをタイムリーにフォローアップします。例外はありません。もちろん、開発者は、R ファイルのサイズを縮小し、パッケージのサイズを縮小することを目的として、さまざまな技術的ソリューションを用意しています。関連するツールは、作業のさまざまな段階に関与しており、パッケージのボリュームの増大を効率的かつ効果的に制御できます。減量に関して関連する提案やアイデアがある場合は、一緒に話し合うことを歓迎します。

参考記事:

グラドルプラグイン:

https://docs.gradle.org/current/userguide/custom_plugins.html

グラドル変換:

https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/transform/Transform

APKビルドプロセス:

https://developer.android.com/studio/build/index.html?hl=zh-cn#build-process

著者:耿雷天イノベーション

出典: JD Cloud 開発者コミュニティ

おすすめ

転載: blog.csdn.net/JDDTechTalk/article/details/131184424