この原則をAndroidに適用します。 APKゴルフをプレイし、Android 8.0 Oreoにインストールできる最小のアプリケーションを作成します。
基本レベル
Android Studioが生成するデフォルトのアプリケーションから始めましょう。 キーストアを作成し 、アプリケーションに署名し、コマンド
stat -f%z $filename
を使用してファイルサイズをバイト単位で測定します。
次に、Oreoの下のNexus 5xスマートフォンにAPKをインストールして、すべてが機能することを確認します。
素晴らしい。 APKの重量は約1.5メガバイトです。
APKアナライザー
アプリケーションの動作を考慮すると、1.5メガバイトは大きすぎるようです(そして何もしません)。プロジェクトを調べて、ボリュームをすばやく保存する場所を探しましょう。 Android Studioが生成したものは次のとおりです。
-
MainActivity
、AppCompatActivity
を拡張します。 - メインウィンドウの
ConstraintLayout
を含むレイアウトファイル。 - 3つの色、1つの文字列リソース、およびテーマのリソースファイル。
-
AppCompat
およびConstraintLayout
ライブラリをサポートします。 - 1つの
AndroidManifest.xml
- 正方形、円形、および背景アイコンのPNGファイル。
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
パッケージに属しているため、サポートライブラリを削除します。 これを行うには、次を実行します。
-
build.gradle
から依存関係ブロックを完全に削除します。
dependencies { implementation 'com.android.support:appcompat-v7:26.1.0' implementation 'com.android.support.constraint:constraint-layout:1.0.2' }
- MainActivityを更新して、クラス
android.app.Activity
を拡張します。
public class MainActivity extends Activity
- テンプレートを更新して、単一の
TextView
を使用します。
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="Hello World!" />
-
AndroidManifest
<application>
要素からstyles.xml
とandroid:theme
属性を削除します。 -
colors.xml
削除しcolors.xml
。 - Gradleの同期中に50回の腕立て伏せを行います。
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で引き続き言及されています。
- 1つのテンプレートファイル
- 1つの文字列リソース
- 1つのランチャーアイコン
上から下に行きましょう。
テンプレートファイル(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。)