Articles

実用的なProGuardルールの例

Wojtek Kaliciński
Wojtek Kaliciński

Follow
Feb20,前回の記事では、ProGuardをAndroidアプリに使用する理由、ProGuardを有効にする方法、およびその際に発生する可能性のあるエラーの種類について説明しました。 潜在的な問題に対処する準備をするためには、基礎となる原則を理解することが重要だと思うので、多くの理論が関与していました。また、別の記事で、インスタントアプリビルド用にProGuardを構成するという非常に具体的な問題についても説明しました。この部分では、中規模のサンプルアプリでのProGuardルールの実用的な例についてお話したいと思います:Nick ButcherによるPlaid。

Plaidは、注釈処理やコード生成、リフレクション、java resource loading、native code(JNI)などを使用するサードパーティのライブラリが混在しているため、ProGuardの問題を研究するのに素晴ら 私は一般的に他のアプリに適用されるべきいくつかの実用的なアドバイスを抽出し、書き留めました:

データクラス

おそら コンテキストとアプリのアーキテクチャ内のどこにあるかに応じて)。 データオブジェクトに関することは、通常、ある時点で、ネットワーク(HTTPリクエスト)、データベース(ORMを介して)、ディスク上のJSONファイル、またはFirebaseデータストアなど、他の

これらのフィールドのシリアル化と逆シリアル化を簡素化するツールの多くは、リフレクションに依存しています。 GSON、Retrofit、Firebase—それらはすべてデータクラスのフィールド名を検査し、トランスポートまたはストレージのいずれかの別の表現(例:{"name”: "Sue”, "age”: 28}"name”:”John”String name フィールドを検索してJavaオブジェクトに適用しようとします。結論:これらのデータクラスのフィールドはシリアル化された形式と一致する必要があるため、ProGuardに名前を変更または削除させることはできません。 クラス全体に注釈を追加するか、すべてのモデルにワイルドカードルールを追加するのは安全です。

-keep class io.plaidapp.data.api.dribbble.model.** { *; }

: アプリがこの問題の影響を受けやすいかどうかをテストするときに間違いを犯す可能性があります。 たとえば、オブジェクトをJSONにシリアル化し、適切なキープルールなしでアプリのバージョンNのディスクに保存すると、保存されたデータは次のようにな ProGuardはフィールドの名前をabに変更したため、すべてが機能しているように見え、データは正しく保存され、ロードされます。ただし、アプリを再度ビルドしてアプリのバージョンN+1をリリースすると、ProGuardはフィールドの名前をcd その結果、以前に保存されたデータの読み込みに失敗します。最初に適切なkeepルールがあることを確認する必要があります。

AndroidのデフォルトのProGuardファイル(常に含める必要がありますが、本当に便利なルールがあります)には、ネイティブ側で実装されているメソッドのルールが 残念ながら、JNIからJavaへの逆の方向で呼び出されたコードを維持するためのすべての方法はありません。JNIを使用すると、JVMオブジェクトを構築したり、C/C++コードからJVMハンドル上のメソッドを検索して呼び出すことができますが、実際には、Plaidで使用されているライブラリの1つがそれを実行します。結論:ProGuardはJavaクラスのみを検査できるため、ネイティブコードで発生する使用法については知りません。 このようなクラスとメンバーの使用法は、@Keep-keepルールを介して明示的に保持する必要があります。

-keep, includedescriptorclasses 
class in.uncod.android.bypass.Document { *; }
-keep, includedescriptorclasses
class in.uncod.android.bypass.Element { *; }

jar/APKからリソースを開く

Androidには、ProGuardにとって通常は問題にならない独自のリソースと資産システムがあります。 しかし、普通のJavaでは、JARファイルから直接リソースをロードするための別のメカニズムがあり、一部のサードパーティのライブラリは、Androidアプリでコンパイルされていてもそれを使用している可能性があります(その場合、APKからロードしようとします)。問題は、通常、これらのクラスが独自のパッケージ名(JARまたはAPKのファイルパスに変換されます)でリソースを検索することです。 ProGuardは難読化時にパッケージ名の名前を変更できるため、コンパイル後、クラスとそのリソースファイルが最終APKの同じパッケージにないことがあります。この方法でロードリソースを識別するには、コード内および依存するサードパーティのライブラリ内でClass.getResourceAsStream / getResourceClassLoader.getResourceAsStream / getResourceへの呼結論:このメカニズムを使用してAPKからリソースをロードするクラスの名前を保持する必要があります。

Plaidでは、実際にはOkHttpライブラリとJsoupに二つのものがあります。

-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
-keepnames class org.jsoup.nodes.Entities

サードパーティのライブラリのルールを考え出す方法

理想的な世界では、使用するすべての依存関係は、AARで必要なProGuardルールを提供します。 時々、彼らはこれを行うことを忘れたり、ProGuardルールを提供する標準化された方法を持っていないJarのみを公開することを忘れたりすることがあります。

その場合、アプリのデバッグを開始し、ルールを考え出す前に、ドキュメントを確認することを忘れないでください。 いくつかのライブラリの著者は、あなたに多くの時間と欲求不満を節約することができます推奨ProGuardルール(格子縞で使用される改装など)を提供します。 残念ながら、多くのライブラリはそうではありません(この記事で言及されているJsoupとBypassの場合など)。 また、ライブラリに付属の設定は最適化が無効になっている場合にのみ機能するため、それらをオンにすると、未知の領域にいる可能性があります。では、ライブラリがそれらを提供しないときにルールをどのように思いつくのですか?
私はあなたにいくつかのポインタを与えることができます:

  1. ビルド出力とlogcatを読んでください!
  2. ビルド警告は、どの-dontwarn追加するルールを教えてくれます
  3. ClassNotFoundExceptionMethodNotFoundExceptionFieldNotFoundException-keep

追加するルール

proguardを有効にしてアプリがクラッシュしたときに喜んでいるはずです—調査を開始する場所があります:)

デバッグす

そこでは、この記事で説明したシナリオのいくつかを検討し、サードパーティのコードに飛び込んで、reflection、introspection、JNIを使用する場合など、失敗する理由を理解す

デバッグとスタックトレース

ProGuardは、デフォルトで、プログラムの実行に必要ない多くのコード属性と隠しメタデータを削除します。 たとえば、デバッグを容易にするために、スタックトレースのソースファイル名と行番号を保持したい場合があります。

-keepattributes SourceFile, LineNumberTable

リリースバージョンをビルドするときに生成されたProGuardマッピングファイルを保存し、それらをPlayにアップロードして、ユーザーが発生したクラッシュから難読化されたスタックトレースを取得することを忘れないでください。

デバッガをアタッチして、アプリのProGuardedビルドでメソッドコードをステップスルーする場合は、ローカル変数に関するデバッグ情報を保持するために、次の属性も保持する必要があります(この行はdebugビルドタイプにのみ必要です)。

-keepattributes LocalVariableTable, LocalVariableTypeTable

最小化されたデバッグビルドタイプ

デフォルトのビルドタイプは、debugがproguardを実行しないように構成されています。 開発時に高速に反復してコンパイルしたいが、リリースビルドでProGuardを使用してできるだけ小さく最適化したいので、これは理にかなっています。

しかし、ProGuardの問題を完全にテストしてデバッグするには、次のように個別に縮小されたデバッグビルドを設定することをお勧めします。

buildTypes {
debugMini {
initWith debug
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
matchingFallbacks =
}
}

このビルドタイプを使用すると、デバッガを接続したり、UIテストを実行したり、リリースビルドにできるだけ近いビルドでアプリをテストしたりすることができます。

結論: ProGuardを使用するときは、エンドツーエンドのテストを行うか、アプリ内のすべての画面を手動で調べて、何かが欠落しているかクラッシュしているかを

ランタイムアノテーション、型イントロスペクション

ProGuardはデフォルトですべての注釈と余分な型情報をコードから削除します。 注釈を処理してコンパイル時にコードを生成するライブラリ(Dagger2やGlideなど)では、プログラムの実行時に後でこれらの注釈を必要としない場合があ

実行時に実際に注釈を検査したり、パラメータと例外の型情報を調べたりする別のクラスのツールがあります。 たとえば、Retrofitは、Proxyオブジェクトを使用してメソッド呼び出しをインターセプトし、注釈と型情報を調べて、HTTP要求から何を入れたり読んだりするかを決定することによってこれを行います。結論:コンパイル時とは対照的に、実行時に読み取られる型情報と注釈を保持する必要がある場合があります。 ProGuardマニュアルの属性リストを確認できます。デフォルトのAndroid ProGuard設定ファイル(getDefaultProguardFile('proguard-android.txt'))を使用している場合、最初の2つのオプション(注釈と署名)が指定されています。 デフォルトを使用していない場合は、自分で追加する必要があります(アプリの要件であることがわかっている場合は、それらを複製するだけでも損

すべてをデフォルトパッケージに移動する

-repackageclassesオプションは、ProGuard設定でデフォルトでは追加されていません。 既にコードを難読化していて、適切なkeepルールの問題を修正している場合は、このオプションを追加してDEXサイズをさらに小さくすることができます。 これは、すべてのクラスをデフォルト(ルート)パッケージに移動し、基本的に”com”のような文字列によって占有されるスペースを解放することで機能します。例。myapp.いくつかのパッケージ”。前に述べたように、ProGuardは3つのことを行うことができます。

  1. 未使用のコードを取り除き、
  2. 識別子の名前を変更してコードを小さくし、
  3. プログラム全体の最適化を実行します。私はそれを見る方法、誰もが試してみて、1を得るために彼らのビルドを設定する必要があります。 と2. 働いてる

    ロックを解除するには3。 (追加の最適化)、別のデフォルトのProGuard設定ファイルを使用する必要があります。 proguard-android.txtproguard-android-optimize.txtbuild.gradle:

    release {
    minifyEnabled true
    proguardFiles
    getDefaultProguardFile('proguard-android-optimize.txt'),
    'proguard-rules.pro'
    }

    これにより、リリースビルドが遅くなりますが、メソッドインライニングなどの最適化により、アプリの実行が速くなり、コードサイズがさらに小さくなる可能性があります。、クラスのマージとより積極的なコードの削除。 ただし、バグを診断するのが新しく困難になる可能性があるため、注意して使用し、何かが機能していない場合は、特定の最適化を無効にするか、最適化

    Plaidの場合、ProGuardの最適化は、Retrofitが具体的な実装なしでプロキシオブジェクトを使用する方法を妨害し、実際に必要とされたいくつかのメソッドパラメータ 私はこの行を私の設定に追加しなければなりませんでした:可能な最適化のリストとそれらを無効にする方法は、ProGuardマニュアルで見つけることができます。

    @Keepと-keepを使用する場合

    @Keepサポートは、実際にはデフォルトのAndroid ProGuardルールファイルの-keep-keep-keepnames-keepclasseswithmembersなど)。

    単純な”このクラスを保持する”または”このメソッドを保持する”ルールが必要な場合はいつでも、実際には、クラスまたはメンバーに@Keepアノテ

    私の後に来る他の開発者がコードをリファクタリングしたい場合、@Keepでマークされたクラス/メンバーがProGuard設定を参照することを忘れずに、何かを壊す危険を冒すことなく、特別な処理が必要であることをすぐに知るでしょう。 また、IDEのほとんどのコードリファクタリングは、クラスとともに@Keep注釈を自動的に保持する必要があります。ここでは、Proguardを使用して削除できたコードの量を示す、Plaidの統計情報をいくつか示します。 より多くの依存関係とより大きなDEXを持つより複雑なアプリでは、節約はさらに大幅になる可能性があります。>