2024.12.19
システムの穴を運用でカバーしようとしてミス多発… バグが大量発生、決算が合わない状態から業務効率化を実現するまで
APKファイルはいかにして作られるのか(全1記事)
提供:LINE株式会社
リンクをコピー
記事をブックマーク
高島友里氏:AndroidのLINE開発に携わっているtakasyこと高島です。よろしくお願いします。今回の発表では「APKファイルはどのように作られているのか」について、流れを見ていきたいと思います。かなり初心者向けの内容になっていますので、気軽に聞いていただければと思います。
この発表での目標は、どういう流れで何がなんのために動いているのかをざっくり理解することです。公式資料のこの図をベースにしながら順を追って見ていくことにしますが、まずはその前にAndroidの実行環境の話に触れておきます。この青枠で囲ったDEX Fileがなぜ必要になるかを把握したほうが流れが追いやすいと思うからです。
Androidの特徴はいくつかあると思うんですが、1つとしてさまざまな端末があるということが挙げられると思います。端末によって使われているCPUも違いますし、ARM64やx86のようにCPUアーキテクチャも種類があって、それに全部対応するようなコードをコンパイルするのは現実的ではありません。
そこで登場するのがJVM、Java仮想マシンになります。Javaコードはjavacコンパイラによって.classファイル、バイトコードにコンパイルされてJVM上で実行されます。
JVMを使うことでCPUアーキテクチャへの対応の問題はなくなったんですが、そもそもJVMがほぼ無制限のストレージやバッテリーを備えた端末向けに設計されているので、メモリもバッテリー容量も少ないAndroidだと……最近はそうでもないかもしれないんですけど、ちょっと無理があります。そのためGoogleではDalvikと呼ばれるAndroid JVMを採用していました。
今はDalvikではなくAndroidランタイムというARTに移行しています。この実行環境では.classバイトコードではなくて.dex、Dalvikバイトコードを実行します。dexはDalvik Excutableの意味です。
Androidのバイトコード実行環境はアプリ起動やインストール、ひいてはOSの更新時のアプリの再インストールなどにも関わってくるかなり大事な部分になっていて、けっこう変遷があっておもしろいので、興味がある方は、右下のリンクに後ほど資料が共有されると思うのでご覧いただければと思います。
では、なぜDEX Fileが登場するかもわかったので、ベースの図に戻って順を追って見ていきます。コンパイラはコンパイルするものによって使うものが変わっていきますが、まず挙げられている対象物をざっくり見ていこうと思います。けっこうみなさんご存知で「当たり前だろ」という気持ちになるかもしれないんですけど復習がてらに聞いてみてください。
ソースコードとリソースファイルに関しては言わずもがな。モジュールのsrcフォルダに入っている.javaファイルと.ktファイル。リソースファイルはresフォルダに入っているものが対象になります。AIDLはクライアントが別のアプリからマルチスレッド対応のサービスにアクセスしてプロセス間通信をするときに必要になるファイルで、Java言語で書かれています。これはあまり使うことがないかもしれません。
ライブラリモジュールは……すみません。何を指してライブラリモジュールなのかいまいちピンと来ないので、これは一旦置いておいて、AARを見ていきます。AARはAndroidライブラリで、モジュールと構造は同じになります。コンパイルするとAPKではなくてAARが作られる点が違います。JARはJavaのライブラリで、AARと違ってAndroidのリソースとマニフェストを含められません。
ざっくり紹介したんですけど、この中でリソースファイルおよびAAR内にあるリソースファイルは、aapt2によってコンパイルされて、それ以外のソースコードの.javaファイル、.aidlとか.jarや.aar内の.javaファイルはJavaコンパイラであるjavac。.ktファイルや.aar内の.ktファイルはkotlincというKotlinコンパイラでコンパイルされます。
このコンパイルされることによって.classファイルに変換されます。一旦ソースコードは置いて、リソース側のコンパイラを見ていきましょう。
aapt2は、AndroidManifestとリソースファイルをコンパイルして、1つのAPKにパッケージ化します。このときにコンパイルとリンクの2つのステップに分かれていて、1つずつコンパイルして最後にリンクしてコンパイルしたものをまとめるようになります。そのため、変更が1つのファイルだったとしたら、再コンパイルが必要なのはそのファイルだけで済むといったインクリメンタルコンパイルができるようになっています。
まずコンパイルのフェーズで1つずつファイルをコンパイルして中間ファイルの.flat、拡張子のバイナリXMLファイルを出力します。リンクフェーズではコンパイルフェーズで生成された中間ファイルをすべてマージして1つの.apkファイルを出力します。このときR.javaやproguard-rulesも生成できます。
出力された.apkファイルはもちろんDEXファイルが入ってないのでDEXファイルは含まないですし、署名もしていないので実行できないAPKになっています。このAPKには、AndroidManifestとバイナリXMLファイルたちとresouces.arscが入っています。
このresouces.arscというのは、リソースに関するすべてのmeta情報が含まれていて、パッケージ内のすべてのリソースのインデックスなどを持っています。バイナリファイルになっていて、実際の実行できるAPK、みなさんがよくビルドして実行しているAPKには非圧縮で保存されていてメモリ上に展開されるだけで使えるようになります。
ここに関してもう少し詳細が知りたい方は、また右下のSpeaker DeckのURLを参照してみてください。
APKと一緒に出力されると言ったR.javaは、一意のIDが割り当てられているため、コンパイル中にJavaコードからリソースを使用できるようになります。arscはアプリ実行時に使われるリソースのインデックスなので注意してください。
proguardのルールも出力されると言ったんですが、出力されるルールはproguardが後述するR8によってレイアウト内でのみ参照されるなど、使われていないと判断されて削除されないようにしてくれます。これでリソースファイルはコンパイルできたので、次に進みます。
次に、ソースコードのコンパイルを、javacやkotlincで.classファイルが生成されたあとから見ていきます。
コードのコンパイルにはR8が使われます。Jakeさんのブログで「R8とは最適化もするD8のバージョン」と言われていました。D8はクラスファイルをDEXファイルに変換するdexerの役割と、Java8の機能をAndroidでも実行可能なバイトコードに変換するdesugarの役割を担っています。
なので、R8に.classファイルを持たせてコンパイルをすると、圧縮しつつ難読化や最適化、desugar、DEX変換とDEXのマージをしてくれて、最終的にすべてのDEXファイルがまとめられたclasses.dexが出力されます。
R8ではproguard-rulesファイルを通じてアプリのコードへのエントリーポイントとして機能するクラスなど、そのアプリの構造を把握できるようになります。
リソースの圧縮の項目がこのコンパイラ中にあると思うんですが、この項目のときに先に話したaapt2で出力されたproguardのルールが使用されて、圧縮時に必要以上に削除されないようになっています。
コンパイルの中でやっていることを1つずつ見ていくと、まずこのツリーシェイキング、圧縮のところは、静的解析で到達できなかったりインスタンス化されないオブジェクトなど未使用のコードと構造を削除します。
難読化は、クラス、メソッド、フィールドの名前を短くしてアプリのサイズを小さくできます。難読化なので他にも利点があるんですが、目的はサイズを削減することにあります。
最適化は、不要なところを書き換えたりインライン化していくことでDEXファイルサイズを小さくするアプローチです。
最後にdesugarですが、これはD8の担当で、Desugaringすることでさっきも言ったようにJava8の便利な言語機能が使えるようになります。
言葉だけで見てもわかりづらいかと思うので、去年のAndroid Dev Summitの動画でわかりやすくシュリンクしている例があったので拝借してきました。
ここではJavaHellowWorldというクラスをコンパイルしています。まずはトレースして、何を削減していいかを把握するところから始まります。
proguardのルールであるkeepルールから、既知のエントリーポイントであるmainは消したくないことがわかります。
エントリーポイントをたどってトレースしていきます。mainから入ってgreetingが呼ばれて終わり。ここでunusedが使われていないということがわかりました。
トレースが終わったので、ツリーシェイキングで使っていなかったunusedメソッドがまずは削除されます。次に、難読化でメソッド名がgreetingからaという名前に変わって短くリネームされます。最後に、最適化としてgreetingメソッドをmainの中にインライン化して完成します。
これでかなり短くなっているのがわかると思います。
これらのことをR8がしてくれて、classes.dexという1つのDEXファイルが出力されます。Multidexを使用している場合はその限りではなく、複数出てきてしまうんですけど、とりあえずclasses.dexが作られます。
できたついでに、DEXファイルのフォーマットも確認しておきましょう。ファイル自体の情報は1番上のヘッダにあって、その下の緑で囲っているところはIDの集まりになります。すべてのファイル内の他の場所をこのIDが指しています。実際のバイトコードとデータは後ろにまとめられていて、このdataセクションのdataと書いてあるところですね。バイトコードの中ではクラス名や定数の名前はすべて上のIDで参照されます。
例えば、string_idsセクションでは、ファイルの先頭からこのアイテムの文字列までのオフセットであるstring_data_offを持つstring_id_itemが並んでいます。そのオフセットを元に、例えば左側の228バイトだとしたら、ファイルの先頭から228バイトのところにstring_id_itemがあって、文字列の長さが10だとわかっているので、その長さの文字列を取得してTestClass0が取得できます。
DEXファイルのフォーマットがざっくり理解できたとして、DEXファイルの課題があります。アプリが大きくなってくると起こる64K参照制限に引っ掛かったことがある人もいると思います。参照するライブラリも含めてアプリのメソッドが65,536個、64×1,024個を超えると、右のようにビルドエラーが起こります
なぜこれが起こるのか。IDのセクションは範囲が決まっています。メソッドIDの範囲は0から0xFFFFまで。つまり65,536個、通し番号でいうと0から65,535までしか参照できません。これが64Kを超えると起こるビルドエラーの原因でした。これを回避するためには、アプリの依存関係の見直しやR8を使用して未使用のコードを削除するなどが有用です。やむを得ない場合は、Multidexを使いましょう。
コンパイラが生成するものがわかったので、次は署名回りを見てみます。APKをデバイスにインストールしたり更新したりする前に、すべてのAPKで必要なのがデジタル署名です。といっても、このすべてのAPKということはデバッグも含まれているんですけど、デバッグのときに署名をした覚えがある人はあまりいないと思います。
署名の設定をしていなくてもデバッグができているのは、プロジェクトを実行したときにAndroid StudioがAndroid SDKツールで生成されたデバッグ証明書を使用して自動的にアプリに署名してくれている。デバッグキーストアとデバッグ証明書を$HOME/.android/debug.keystoreを自動的に作成してキーストアと鍵のパスワードを設定しています。
これも画面を見たほうが思い出せると思うので、左に画面を載せてみました。左がアップロード鍵とキーストアを作るときの画面です。作ったあとは右画面のようにアップロード鍵でAPKをビルドして署名すれば署名済みAPKができあがります。
最後に、キーストアも整ったところでAPKを仕上げるために実行されるのがapkbuilderとzipalignです。apkbuilderで今までの出力されたものをまとめます。そしてzipalignでAPKファイルを最適化するために4バイトアラインメントに揃えます。これをしておくと、アプリ実行時にリソースをメモリに展開するmmap()というシステムコールですべての部分にアクセスできるようになります。
また、アプリ実行時に無駄にメモリの確保がされなくなるためメモリ消費量が減ります。そのためAPKファイルを作成する前にはzipalignを実行する必要があります。
早いですけどまとめです。Androidに合うJVMにするために、まずDEXファイルを使用する必要があった。だからDEXファイルを内部では使っている。リソースファイルはaapt2がコンパイルして、APKファイルとR.java、proguardのルールを出力します。コードはクラスファイルにjavacとkotlincでコンパイルしてからR8がコンパイルして最適化した上でclasses.dexを出力します。
署名済みアプリをビルドするには、キーストアとアップロード鍵が必要です。apkbuilderでAPKファイルにしてzipalignで最後に最適化します。
aab(Android App Bundle)まで本当は調査をしたかったんですけど、ぜんぜん間に合わない上に、概要に書いている内容と違ってかなりフワッとした内容となってしまったことは、自分としてはすごく心残りなんですけど、自分のようにビルド回りに苦手意識を持っている方がもしいるなら、苦手意識を払拭できるきっかけになれば幸いです。
お聞きくださりありがとうございました。
LINE株式会社
2024.12.20
日本の約10倍がん患者が殺到し、病院はキャパオーバー ジャパンハートが描く医療の未来と、カンボジアに新病院を作る理由
2024.12.19
12万通りの「資格の組み合わせ」の中で厳選された60の項目 532の資格を持つ林雄次氏の新刊『資格のかけ算』の見所
2024.12.16
32歳で成績最下位から1年でトップ営業になれた理由 売るテクニックよりも大事な「あり方」
2023.03.21
民間宇宙開発で高まる「飛行機とロケットの衝突」の危機...どうやって回避する?
PR | 2024.12.20
モンスター化したExcelが、ある日突然崩壊 昭和のガス工事会社を生まれ変わらせた、起死回生のノーコード活用術
2024.12.12
会議で発言しやすくなる「心理的安全性」を高めるには ファシリテーションがうまい人の3つの条件
2024.12.18
「社長以外みんな儲かる給与設計」にした理由 経営者たちが語る、優秀な人材集め・会社を発展させるためのヒント
2024.12.17
面接で「後輩を指導できなさそう」と思われる人の伝え方 歳を重ねるほど重視される経験の「ノウハウ化」
2024.12.13
ファシリテーターは「しゃべらないほうがいい」理由 入山章栄氏が語る、心理的安全性の高い場を作るポイント
2024.12.10
メールのラリー回数でわかる「評価されない人」の特徴 職場での評価を下げる行動5選
Climbers Startup JAPAN EXPO 2024 - 秋 -
2024.11.20 - 2024.11.21
『主体的なキャリア形成』を考える~資格のかけ算について〜
2024.12.07 - 2024.12.07
Startup CTO of the year 2024
2024.11.19 - 2024.11.19
社員の力を引き出す経営戦略〜ひとり一人が自ら成長する組織づくり〜
2024.11.20 - 2024.11.20
「確率思考」で未来を見通す 事業を成功に導く意思決定 ~エビデンス・ベースド・マーケティング思考の調査分析で事業に有効な予測手法とは~
2024.11.05 - 2024.11.05