APK-golfをプレイします。 Android APKファイルサイズを99.9%削減

ゴルフでは、ポイントが最も少ない方が勝ちます。



この原則をAndroidに適用します。 APKゴルフをプレイし、Android 8.0 Oreoにインストールできる最小のアプリケーションを作成します。



基本レベル



Android Studioが生成するデフォルトのアプリケーションから始めましょう。 キーストアを作成し 、アプリケーションに署名し、コマンドstat -f%z $filename



を使用してファイルサイズをバイト単位で測定します。



次に、Oreoの下のNexus 5xスマートフォンにAPKをインストールして、すべてが機能することを確認します。











素晴らしい。 APKの重量は約1.5メガバイトです。



APKアナライザー



アプリケーションの動作を考慮すると、1.5メガバイトは大きすぎるようです(そして何もしません)。プロジェクトを調べて、ボリュームをすばやく保存する場所を探しましょう。 Android Studioが生成したものは次のとおりです。





mipmap-anydpi-v26



下に合計15の画像と2つのXMLファイルがある場合、おそらくアイコンを処理する最も簡単な方法mipmap-anydpi-v26



。 Android StudioのAPKアナライザーでこれをすべてカウントしましょう。







最初の仮定に反して、最大のファイルはDexであり、リソースはAPKのサイズの20%しか占めていないようです。



ファイル 大きさ
classes.dex



74%
res



20%
resources.arsc



4%
META-INF



2%
AndroidManifest.xml



<1%


各ファイルの動作を個別に調べます。



dexファイル



classes.dex



は、肥大化したAPKの主な原因であり、総ボリュームの73%を占めているため、最適化の最初の目標になります。 このファイルには、Dex形式でコンパイルされたすべてのコードと、Androidフレームワークの外部メソッドのリストおよびサポートライブラリが含まれています。



android.support



パッケージには13,000を超えるメソッドがリストされていますが、これは「Hello World」などのアプリケーションにとって冗長なようです。



資源



resディレクトリには、Android Studioインターフェースですぐには表示されない多数のテンプレートファイル、図面(描画可能ファイル)、およびアニメーションが含まれています。 繰り返しますが、これらはサポートライブラリから取得され、APKのサイズの約20%を占めます。







resources.arsc



ファイルには、これらすべてのリソースのリストも含まれています。



署名



META-INF



フォルダーCERT.SF



、v1 APK署名するために必要なCERT.SF



MANIFEST.MF



およびCERT.RSA



CERT.SF



ます。 攻撃者がAPK内のコードを変更すると、署名が一致しなくなり、ユーザーが無関係なマルウェアを開始するのを防ぎます。



MANIFEST.MF



はAPKのファイルがリストされ、 CERT.SF



にはマニフェストと各ファイルのチェックサムが含まれています。 CERT.RSA



は公開キーを保存し、 CERT.RSA



の整合性を検証しますCERT.SF











最適化の明確な目標はありません。



AndroidManifest



AndroidManifestは元のファイルと非常によく似ています。 唯一の違いは、文字列やドロウアブルなどのリソースの代わりに、 0x7F



で始まる整数識別子がここに示されることです。



縮小をオンにする



私たちは、アプリケーションのbuild.gradle



ファイルでリソースの縮小化と圧縮のオプションを有効にしようとしていません。 やってみましょう。



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





 -keep class com.fractalwrench.** { *; }
      
      





minifyEnabled



true



に設定すると、 Proguardがアクティブになり、アプリケーションから不要なコードがクリアされます。 また、キャラクター名を難読化し、アプリケーションのリバースエンジニアリングを困難にします。



shrinkResources



は、APKから直接参照されないリソースを削除します。 リソースに直接アクセスしないと問題が発生する場合がありますが、これはアプリケーションには適用されません。



786 KB(50%減少)



プログラムに目に見える変更を加えることなく、APKのサイズを半分にしました。











アプリケーションにminifyEnabled



shrinkResources



まだ含めていない場合、これはこの記事で取り上げるべき最も重要なことです。 構成とテストに数時間費やすことで、数メガバイトを簡単に節約できます。



さようならAppCompat、私たちはあなたをほとんど認識しませんでした



classes.dex



APK全体の57%を占めるようになりました。 Dexファイルのメソッドのリストの大部分はandroid.support



パッケージに属しているため、サポートライブラリを削除します。 これを行うには、次を実行します。





108 KB(87%減少)



神の母、ファイルはほぼ10倍に減少しました:786 KBから108 KBに。 唯一の目立った変更は、ツールバーの色の変更のみで、これはデフォルトのOSテーマになりました。











これらのすべてのランチャーアイコンにより、resディレクトリはAPKのサイズの95%を占めるようになりました。 これらのアイコンがデザイナーによって作成された場合、API 15以降でサポートされているより効率的な形式であるWebP変換しようとします。



幸いなことに、Googleは既にドロアブルを最適化しましたが、そうでなければ、それらを最適化してImageOptimを使用してPNGから不要なメタデータを削除することもできます。



res/drawable



ましょう-そして、すべての起動アイコンをres/drawable



単一の単一ピクセルの黒いドットに置き換えres/drawable



。 この画像の重量は67バイトです。



6808バイト(94%減少)



ほとんどすべてのリソースを削除したので、APKのサイズが約95%減少したことは驚くことではありません。 次のリソースは、resources.arscで引き続き言及されています。





上から下に行きましょう。



テンプレートファイル(6262バイト、9%の削減)



AndroidフレームワークはXMLファイルcontentView



し、 Activity



contentView



として使用されるTextView



オブジェクトを自動的に作成します。



XMLファイルを削除し、プログラムでcontentViewを設定することにより、この仲介なしでやってみましょう。 XMLファイルがなくなるためリソースの量は減少しますが、追加のTextView



メソッドに言及しているため、Dexファイルのサイズは増加します。



 TextView textView = new TextView(this); textView.setText("Hello World!"); setContentView(textView);
      
      





良い交換のようです。



アプリケーション名(6034バイト、4%削減)



strings.xml



を削除して、AndroidManifestマニフェストのandroid:label



を文字「A」に置き換えます。 これは小さな変更のように見えますが、 resources.arsc



からエントリを削除すると、マニフェストの文字数が減り、resディレクトリからファイルが削除されます。 どんな小さなことでも良いことです-228バイトを節約しました。



ランチャーアイコン(5300バイト、13%削減)



Androidプラットフォームリポジトリのresources.arscのドキュメントには、各APKリソースがresources.arsc



で整数の識別子とともに記述されてresources.arsc



ことが説明されています。 これらのIDには2つの名前空間があります。



0x01:システムリソース(framework-res.apkで事前定義)



0x7f:アプリケーションリソース(アプリケーションの.apkファイル内)


0x01名前空間のリソースへのリンクを配置すると、APKはどうなりますか? 理論的には、より美しいアイコンを取得すると同時に、ファイルのサイズを小さくします。



 android:icon="@android:drawable/btn_star"
      
      









もちろん、実際に動作するアプリケーションのアイコンなどのシステムリソースを信頼することはできません 。 この方法はGoogle Playでの検証に失敗し、一部のメーカーは独自の方法白を決定するため、注意してください。



マニフェスト(5252バイト、1%の削減)



マニフェストにはまだ触れていません。



 android:allowBackup="true" android:supportsRtl="true"
      
      





これらの属性を削除すると、48バイト節約されます。



プロガードハック(4984バイト、5%削減)



BuildConfig



クラスとR



クラスは、Dexファイルに残っているようです。



 -keep class com.fractalwrench.MainActivity { *; }
      
      





Proguardルールを改良すると、不要なクラスが削除されます。



難読化(4936バイト、1%削減)



Activityクラスの名前を難読化します。 通常のクラスの場合、Proguardはこれを自動的に行いますが、Activityクラスの名前はIntentsを介して呼び出されるため、デフォルトでは難読化されていません。



MainActivity-> c.java



com.fractalwrench.apkgolf-> cc


META-INF(3,307バイト、33%削減)



現時点では、v1とv2の両方の署名でアプリケーションに署名しています。 v2はAPK全体をハッシュすることで優れた保護とパフォーマンスを提供するため、これはリソースの無駄のようです。



署名v2はAPKファイル自体のバイナリブロックに含まれているため、APKアナライザーからは見えません。 署名v1は、 CERT.RSA



およびCERT.SF



として表示されCERT.SF







Android Studioインターフェースでv1署名をオフにして、署名付きAPKを生成しましょう。 その逆も試してみましょう。



署名 大きさ
v1 3511
v2 3307


これでv2を使用するように見えます。



どこへ行く-IDEは必要ありません



APKを手動で編集します。 次のコマンドを使用します。



 # 1.   apk ./gradlew assembleRelease # 2.   unzip app-release-unsigned.apk -d app #    # 3.   zip -r app app.zip # 4.  zipalign zipalign -v -p 4 app-release-unsigned.apk app-release-aligned.apk # 5.  apksigner   v2 apksigner sign --v1-signing-enabled false --ks $HOME/fake.jks --out signed-release.apk app-release-unsigned.apk # 6.   apksigner verify signed-release.apk
      
      





APK署名プロセスの詳細な概要については、 こちらをご覧ください 。 一般に、Gradleは無署名のアーカイブを生成し、zipalignは非圧縮リソースのバイトアライメントを行ってAPKのロード後のRAM消費を最適化し、最後にAPK暗号署名プロセスが開始されます。



署名されていない整列されていないAPKの重量は1902バイトです。これは、プロシージャが約1キロバイトを追加することを意味します。



ファイルサイズの不一致(2608バイト、21%圧縮)



変だ! 位置合わせされていないAPKを解凍して手動で署名すると、 META-INF/MANIFEST.MF



ファイルが消え、543バイトが節約されます。 誰かがこれが起こる理由を知っているなら、私に知らせてください!



これで、署名済みAPKに3つのファイルが残っています。 ただし、 resources.arsc



をインストールしないため、 resources.arsc



ファイルは削除できます。



その後、マニフェストとclasses.dex



ファイルのみがあり、どちらもほぼ同じサイズです。



圧縮ハッキング(2599バイト、0.5%削減)



残りのすべての行を「c」に変更し、バージョンを26に更新してから、署名済みAPKを生成します。



 compileSdkVersion 26 buildToolsVersion "26.0.1" defaultConfig { applicationId "cc" minSdkVersion 26 targetSdkVersion 26 versionCode 26 versionName "26" }
      
      





 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cc"> <application android:icon="@android:drawable/btn_star" android:label="c" > <activity android:name="ccc">
      
      





これにより、サイズがさらに9バイト小さくなります。



ファイル内の文字数は変更されていませんが、実際には、文字「c」の頻度が増加しています。 その結果、圧縮アルゴリズムはより効率的に機能しました。



こんにちは、ADB(2462バイト、5%削減)



ActivityクラスのLaunch intentフィルターを削除することにより、マニフェストをさらに最適化できます。 この瞬間から、次のコマンドでアプリケーションを起動します。



adb shell am start -a android.intent.action.MAIN -n cc/.c







新しいマニフェストは次のとおりです。



 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cc"> <application> <activity android:name="c" android:exported="true" /> </application> </manifest>
      
      





ランチャーアイコンも削除しました。



メソッド参照の消去(2179バイト、12%削減)



初期条件に従って、デバイスにインストールできるAPKを準備する必要があります。



このアプリケーションでは、 TextView



Bundle



およびActivity



クラスのメソッドをリストします。 これらのリンクを削除して、新しいApplication



クラスに置き換えることにより、Dexファイルのサイズを小さくできます。 したがって、Dexファイルは単一のメソッドApplication



クラスのコンストラクターを参照するようになります。



ソースファイルは次のようになります。



 package cc; import android.app.Application; public class c extends Application {}
      
      





 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cc"> <application android:name=".c" /> </manifest>
      
      





adbを使用してAPKが正常にインストールされたことを確認します。これは「設定」でも確認できます。











最適化(1961バイト、10%削減)



チェックサムやオフセットなどのさまざまなメカニズムによって手動での編集が困難になるため、この最適化のためにDexファイル形式を数時間かけて調査しました。



つまり、APKをインストールするための唯一の要件は、 classes.dex



ファイルclasses.dex



という事実であることがclasses.dex



。 したがって、元のファイルを削除し、コンソールでtouch classes.dex



を実行し、空のファイルを使用してサイズの10%を保存します。



場合によっては、最も愚かな解決策が最善であることがあります。



マニフェストを理解する(1961バイト、0%削減)



署名されていないAPKマニフェストは、正式に文書化されていないように見えるバイナリXMLファイルです。 HexFiendエディターを使用して、ファイルの内容を変更できます。



ファイルヘッダーにはいくつかの興味深い要素が推測されます。最初の4バイトは、Dexファイルのバージョン番号と一致する38



エンコードします。 次の2バイトは、ファイルサイズに一致する660



エンコードします。



targetSdkVersionを1



に設定し、ヘッダー内のファイルのサイズを659



変更して、1バイトを削除してみましょう。 残念ながら、Androidシステムは新しいファイルを無効なAPKとして拒否します。 すべてが何らかの形でより複雑に配置されているようです...



マニフェストの誤解(1777バイト、9%の削減)



そして、ファイル全体でランダムな文字を書き留めてから、指定されたファイルサイズを変更せずにAPKをインストールします。 これにより、チェックサムがチェックされているかどうか、および変更がファイルヘッダーのオフセットにどのように影響するかがチェックされます。



驚くべきことに、そのようなマニフェストは、Neo 5XでOreoの下で有効なAPKとして認識されます。







BinaryXMLParser.java



をサポートするAndroidフレームワークの開発者が、大声で枕にBinaryXMLParser.java



でいるのを聞いたばかりだと思います。



最大の利点を得るには、これらのすべての愚かな文字をヌルバイトで置き換える必要があります。 これは、HexFiendでファイルの重要な部分を認識するのに役立ち、上記の圧縮ハックのおかげで数バイトも削減されます。



UTF-8マニフェスト



以下は、APKがインストールされない重要なマニフェストコンポーネントです。











マニフェストやパッケージタグなど、いくつかのことは明らかです。 文字列プールでは、versionCodeとパッケージ名が表示されます。



16進マニフェスト











ファイルを16進数で表示すると、文字列プールやその他の値(ファイルサイズ0x9402



など)を記述するファイルヘッダーの値が表示されます。 文字列も興味深い方法でエンコードされます-8バイトを超える場合、全体の長さは前の2バイトで示されます。



しかし、ここで最適化のための他のオプションを見つけることはほとんど不可能です。



終わった? (1757バイト、1%削減)



最終的なAPKをご覧ください。











この名前全体を通して、APKはv2署名に私の名前を示しました。 圧縮にハックを使用する新しいキーストアを作成します。











20バイト節約しました。



ステップ5:認識



1757



バイトは非常に小さいです。 そして私の知る限り、これは既存の最小のAPKです。



ただし、Androidコミュニティの誰かがさらなる最適化を実行し、結果をさらに改善できると合理的に信じています。 ファイルを現在の1757



バイトから減らすことができた場合は、最小のAPKがホストされているリポジトリにプルリクエストを送信する、Twitterで報告してください(記事の公開以降、ファイルは既に820バイトに削減されています-約Per。)



All Articles