Androidは、今日の世界で最も普及しているモバイルオペレーティングシステムであり、その心臓部にはAOSP (Android Open Source Project) が存在します。AOSPは、Googleが主導して開発・保守を行うオープンソースのプラットフォームであり、誰でも自由にAndroidのソースコードにアクセスし、改変し、独自のOSを構築することを可能にしています。このオープンな性質こそが、Androidエコシステムの多様性と革新を支える根源です。スマートフォンやタブレットだけでなく、スマートウォッチ、テレビ、自動車、IoTデバイスなど、あらゆる形態のハードウェアにAndroidが搭載されているのは、AOSPが提供する柔軟性の賜物と言えるでしょう。開発者はAOSPを基盤とすることで、特定のデバイスに最適化されたユーザーエクスペリエンスをゼロから創造する力を持つことができます。
この広大なAOSPの世界で、特に重要かつ強力な役割を担うのが「システムアプリ」です。システムアプリとは、OSのファームウェアイメージに直接組み込まれ、システムの根幹機能を提供するアプリケーション群を指します。通常のアプリのようにユーザーがGoogle Playストアからインストールしたり、任意にアンインストールしたりすることはできません。電話、メッセージ、設定、カメラ、システムUIといった、デバイスが「スマートフォン」として機能するために不可欠なアプリケーションは、すべてシステムアプリに分類されます。これらのアプリは、OSと密接に連携し、通常のアプリではアクセス不可能な特権的なAPIやハードウェア機能を利用することで、一貫性があり、安全で、効率的なユーザー体験を実現しています。この記事では、AOSPの深淵に分け入り、システムアプリ開発の根幹をなすビルドシステム(.mkファイルと.bpファイル)の理解から、実際の開発プロセス、そしてセキュリティや互換性といった高度な考慮事項に至るまで、その全貌を詳細に解説していきます。
Androidアーキテクチャとシステムアプリの位置付け
システムアプリの役割を深く理解するためには、まずAndroid OSの階層的なアーキテクチャを把握することが不可欠です。Androidは、下から順にLinuxカーネル、ハードウェア抽象化レイヤー(HAL)、ネイティブC/C++ライブラリとAndroidランタイム(ART)、Java APIフレームワーク、そして最上層のアプリケーション層という構造になっています。
- Linuxカーネル: OSの最下層に位置し、メモリ管理、プロセス管理、ネットワーク、デバイスドライバなど、基本的なシステム機能を提供します。
- ハードウェア抽象化レイヤー (HAL): 上位のJava APIフレームワークが、特定のハードウェアの実装に依存することなく、標準的なインターフェースを通じてデバイスのハードウェア(カメラ、Bluetooth、Wi-Fiなど)と通信できるようにするための中間層です。
- ネイティブC/C++ライブラリとAndroidランタイム(ART): HALの上には、OpenGL ESやSQLiteといったC/C++で書かれた多くのコアライブラリが存在します。これらと並行して、Androidアプリの実行環境であるARTが動作しています。
- Java APIフレームワーク: 開発者がアプリケーションを開発する際に利用する、豊富で拡張可能なAPI群です。アクティビティマネージャー、ウィンドウマネージャー、コンテンツプロバイダーなどが含まれます。
- アプリケーション層: この最上層に、システムアプリとユーザーがインストールするサードパーティアプリが存在します。
システムアプリは、このアーキテクチャの最上層に位置しながらも、Java APIフレームワークの公開されていないAPIや、ネイティブライブラリ、さらにはHAL層と直接連携する権限を持っています。これにより、通常のアプリでは実現不可能な、OSレベルでの深い統合が可能になるのです。
システムアプリ、特権アプリ、通常アプリの明確な違い
AOSP内では、アプリはインストールされる場所と署名によって、その権限レベルが厳密に区別されます。
- 通常アプリ (User Apps): ユーザーがGoogle Playストアなどを通じてインストールするアプリです。これらは
/data/appディレクトリにインストールされ、そのアプリ自身のデータ(/data/data/<package_name>)にしかアクセスできません。アンインストールは自由です。 - システムアプリ (System Apps): OSのビルド時に
/system/appディレクトリに配置されるアプリです。ユーザーによるアンインストールは通常できません。システムアプリは、signatureOrSystemレベルのパーミッションが付与されたAPIなど、通常アプリよりも広範な権限を持ちます。 - 特権アプリ (Privileged Apps): システムアプリの中でもさらに強力な権限を持つアプリで、
/system/priv-appディレクトリに配置されます。これらのアプリは、プラットフォームキー(OSをビルドする際に使用される秘密鍵)で署名されている必要があり、signatureレベルのパーミッションを要求するAPI(例: システム設定の書き込み、ユーザーデータの変更など)にアクセスできます。設定アプリやSystemUIなどがこれに該当します。
この階層構造は、Androidのセキュリティモデルの根幹をなしており、悪意のあるアプリがシステムの重要な部分を破壊したり、ユーザーのプライバシーを侵害したりすることを防いでいます。システムアプリ開発とは、まさにこの特権的な領域で、OSと一体となって動作するアプリケーションを創り出す行為なのです。
AOSPビルドシステムの進化:MakeからSoongへ
AOSPの巨大なソースコードツリーを管理し、最終的なOSイメージを生成するためには、高度で複雑なビルドシステムが不可欠です。Androidの歴史を通じて、ビルドシステムもまた大きな進化を遂げてきました。当初から長らく使われてきたのがGNU Makeをベースとしたシステム(.mkファイル)ですが、近年のAndroidバージョンでは、より高速で宣言的な新しいビルドシステムであるSoong(.bpファイル)への移行が進んでいます。
伝統的なビルドシステム:Make (.mkファイル)
Android.mkという名前で知られるMakeファイルは、長年にわたりAOSPのビルドを支えてきました。Makeは非常に柔軟で強力なツールであり、シェルスクリプトや複雑な条件分岐を記述できるため、開発者はビルドプロセスを細かく制御することができました。
しかし、AOSPの規模が拡大するにつれて、Makeベースのシステムにはいくつかの深刻な課題が浮上しました。
- パフォーマンスの低下: Makeファイルは本質的に手続き的であり、ビルドのたびにすべてのMakeファイルを解釈し直す必要がありました。ソースコードの規模が増大するにつれて、この解釈プロセスがビルド時間全体の大きなボトルネックとなっていきました。
- 複雑性とエラーの起こしやすさ: Makeの柔軟性は諸刃の剣でした。Turing完全であるため、Makeファイル内に複雑なロジックを埋め込むことができましたが、これが可読性を著しく低下させ、デバッグを困難にしました。変数名がグローバルスコープであるため、意図しない変数の上書きなども頻繁に発生しました。
- 保守性の問題: プロジェクト間の依存関係の記述が暗黙的になりがちで、ビルドシステム全体の変更が予期せぬ副作用を生むことがありました。
モダンなビルドシステム:SoongとBlueprint (.bpファイル)
これらの課題を解決するために、GoogleはAndroid 7.0 (Nougat) から新しいビルドシステム「Soong」を導入しました。Soongは、Makeのような手続き的な記述ではなく、JSONに似た宣言的な構文を持つ「Blueprint」ファイル(Android.bp)を入力として受け取ります。
Soongがもたらした主な改善点は以下の通りです。
- 圧倒的なパフォーマンス向上: SoongはBlueprintファイルを解析し、内部でNinjaビルドファイルを生成します。Ninjaは、依存関係の解決やコマンドの実行を高速に行うことに特化した低レベルビルドシステムです。一度Ninjaファイルが生成されると、次回のビルドでは変更があった部分だけを効率的に再計算するため、インクリメンタルビルドが劇的に高速化されました。
- 簡潔さと可読性: Blueprintの構文は、モジュール(ビルドの単位)とそのプロパティ(ソースファイル、ライブラリ依存など)を定義するという、非常にシンプルで宣言的なものです。これにより、ビルド設定が何をしているのかが一目で理解しやすくなりました。
- 厳密なエラーチェック: SoongはBlueprintファイルの解析時に、未知のプロパティや型の不一致などを厳しくチェックします。これにより、ビルドプロセスの早い段階で設定ミスを発見できるようになりました。
- 自動化と拡張性: Go言語で記述されたSoongは、ビルドロジックの自動生成や拡張が容易になるように設計されています。例えば、インターフェース定義言語(AIDL)ファイルからJavaのソースコードを自動生成する、といった処理がビルドシステムにきれいに統合されています。
現在、AOSPではMakeからSoongへの移行が積極的に進められていますが、依然として多くのレガシーモジュールや製品構成ファイルは.mk形式で記述されています。そのため、現代のAOSP開発者は、両方のシステムを理解し、適切に使い分ける能力が求められます。
詳細解説:Android.mkファイルによるビルド設定
Android.mkファイルは、AOSPの伝統的なMakeベースのビルドシステムの中核をなす設定ファイルです。各モジュール(ライブラリ、実行可能ファイル、アプリケーションなど)のディレクトリに配置され、そのモジュールをどのようにビルドするかを定義します。ここでは、システムアプリをビルドする際の典型的なAndroid.mkの構造と、主要な変数について深く掘り下げて解説します。
基本的な構造
すべてのAndroid.mkファイルは、定型的な構造を持っています。
# このファイルがあるディレクトリのパスを取得
LOCAL_PATH := $(call my-dir)
# LOCAL_変数群をクリアする
include $(CLEAR_VARS)
# --- ここからモジュールの定義 ---
# (ここに各種LOCAL_変数を設定する)
# --- モジュールの定義ここまで ---
# ビルド実行を指示する
include $(BUILD_PACKAGE)
LOCAL_PATH := $(call my-dir): ビルドシステムに関数を呼び出させ、現在のAndroid.mkファイルが存在するディレクトリのパスをLOCAL_PATH変数に設定します。これはファイルの冒頭で必ず宣言する必要があります。include $(CLEAR_VARS):Android.mkファイルは一つのビルドプロセスで複数回読み込まれる可能性があるため、このスクリプトをインクルードすることで、LOCAL_PATHを除くほとんどのLOCAL_XXX変数をリセットします。これにより、以前のモジュールの設定が現在のモジュールに影響を与えるのを防ぎます。include $(BUILD_PACKAGE): Đây là chỉ thị cuối cùng, cho hệ thống biết rằng tất cả các định nghĩa bạn đã thiết lập trong các biếnLOCAL_XXXcần được tập hợp lại để xây dựng một gói ứng dụng Android (APK). Các chỉ thị khác bao gồmBUILD_EXECUTABLE(cho tệp thực thi gốc),BUILD_JAVA_LIBRARY(cho thư viện JAR), vàBUILD_STATIC_LIBRARY(cho thư viện tĩnh .a).
システムアプリ開発で頻出する主要な変数
CLEAR_VARSとBUILD_PACKAGEの間で、モジュールの特性を定義する多数のLOCAL_XXX変数を設定します。
LOCAL_PACKAGE_NAME-
ビルドされるAPKのファイル名を決定する、最も重要な変数の一つです。例えば、
LOCAL_PACKAGE_NAME := MySystemAppと設定すると、MySystemApp.apkが生成されます。これはLOCAL_MODULEのエイリアスであり、アプリケーションのビルドではこちらを使うことが推奨されます。 LOCAL_SRC_FILES-
ビルドに含めるソースファイルのリストをスペース区切りで指定します。Javaファイルだけでなく、C/C++ソースファイル(JNI用)、AIDLファイルなども含めることができます。ビルドシステムはファイルの拡張子を基に、どのコンパイラを使用するかを自動的に判断します。
LOCAL_SRC_FILES := src/com/example/myapp/MainActivity.java \ src/com/example/myapp/Utils.java \ aidl/com/example/myapp/IMyService.aidl LOCAL_CERTIFICATE-
APKに署名するために使用する証明書を指定します。システムアプリ開発では、これが非常に重要になります。
platform: プラットフォームキーで署名します。システムのコア部分と同じ証明書であり、最も高い権限を持ちます。特権アプリ(privileged app)は通常これで署名されます。shared: ホーム/連絡先アプリと共有されるキー。media: メディア/ダウンロード関連のアプリと共有されるキー。PRESIGNED: 事前に署名済みのAPKを使用する場合に指定します。この場合、ビルドシステムは再署名を行いません。
testkeyですが、システム領域にインストールするアプリには、通常platformを指定します。 LOCAL_PRIVILEGED_MODULE-
このフラグを
trueに設定すると、このモジュールが特権アプリであることを示します。ビルドシステムは生成されたAPKを、通常の/system/appではなく/system/priv-appディレクトリにインストールします。これにより、アプリはsignatureレベルのパーミッションを利用できるようになります。LOCAL_PRIVILEGED_MODULE := true LOCAL_SDK_VERSION-
アプリがどのSDKバージョンをターゲットにしているかを指定します。
current: 最新の公式SDKに対してビルドします。通常のアプリ開発と同じです。system_current: OS内部の非公開API(@hideとマークされたAPIなど)に対してビルドします。システムアプリはOSと密接に連携するため、この設定が頻繁に用いられます。core_current: Javaのコアライブラリのみを使用します。
system_currentを指定して、フレームワークの内部機能にアクセスします。 LOCAL_STATIC_JAVA_LIBRARIESandLOCAL_JAVA_LIBRARIES-
依存するJavaライブラリ(.jar)を指定します。
LOCAL_STATIC_JAVA_LIBRARIES: 指定されたライブラリのクラスファイルを、生成されるAPKのDEXファイルに直接含めます。LOCAL_JAVA_LIBRARIES: デバイスに既にインストールされている共有ライブラリに依存します。例えば、frameworkやextなど、Androidフレームワーク自体が提供するライブラリはここで指定します。
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 LOCAL_JAVA_LIBRARIES := framework ext LOCAL_MODULE_TAGS-
ビルドの種類を制御するためのタグを指定します。
user: userビルドに含まれます(最終製品版)。eng: engビルドに含まれます(エンジニアリング用)。tests: testsビルドに含まれます。optional: デフォルトではどのビルドにも含まれませんが、製品構成ファイル(.mk)のPRODUCT_PACKAGESで明示的に指定することで含めることができます。これは最も一般的な設定です。
LOCAL_PROGUARD_ENABLED-
ProGuard(またはR8)によるコードの難読化と最適化を有効にするかどうかを指定します。
fullやcustomなどの値を設定できます。カスタムルールはLOCAL_PROGUARD_FLAG_FILESで指定します。
総合的なAndroid.mkの例
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# モジュールの基本情報
LOCAL_MODULE_TAGS := optional
LOCAL_PACKAGE_NAME := MyPrivilegedSystemApp
LOCAL_CERTIFICATE := platform
# 特権アプリとして/system/priv-appにインストール
LOCAL_PRIVILEGED_MODULE := true
# ソースファイル
LOCAL_SRC_FILES := $(call all-java-files-under, java)
LOCAL_SRC_FILES += $(call all-Iaidl-files-under, aidl)
# 静的ライブラリ(APKに同梱される)
LOCAL_STATIC_JAVA_LIBRARIES := \
androidx.core_core \
androidx.appcompat_appcompat
# フレームワークの非公開APIを使用
LOCAL_SDK_VERSION := system_current
# AndroidManifest.xmlの場所
LOCAL_MANIFEST_FILE := AndroidManifest.xml
# リソースディレクトリ
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
# Proguardを有効にする
LOCAL_PROGUARD_ENABLED := full
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
include $(BUILD_PACKAGE)
# ============================================================
# 他のモジュールを同じファイルで定義することも可能
# 例えば、JNIライブラリなど
# include $(CLEAR_VARS)
# LOCAL_MODULE := libmynative
# LOCAL_SRC_FILES := jni/native.c
# include $(BUILD_SHARED_LIBRARY)
詳細解説:Android.bpファイルによるビルド設定
Android.bpファイルは、Soongビルドシステムで使用される設定ファイルです。JSONに似た宣言的な構文を持ち、Makeファイルよりもシンプルで構造化されています。これにより、ビルド設定の可読性と保守性が大幅に向上しました。ここでは、システムアプリをビルドするためのandroid_appモジュールタイプを中心に、そのプロパティを詳細に解説します。
基本的な構文
Android.bpファイルは、モジュールタイプの後に波括弧{}が続き、その中にプロパティ: 値,の形式で設定を記述します。
// コメントはこのようにスラッシュ2つで記述します
android_app {
name: "MySystemApp",
// --- ここからプロパティの定義 ---
// (ここに各種プロパティを設定する)
// --- プロパティの定義ここまで ---
}
変数や条件分岐、ループといった複雑なロジックは意図的に排除されており、純粋に「何をビルドするか」を宣言することに特化しています。
システムアプリ開発で頻出するプロパティ
android_appモジュールタイプは、APKをビルドするために使用され、多くのプロパティを持っています。
name(必須)-
モジュールの一意な名前を指定します。この名前は、ビルドコマンド(例:
m MySystemApp)や他のモジュールからの参照に使用されます。生成されるAPKのファイル名も、この名前がベースになります。 srcs-
ソースファイルのリストを文字列の配列として指定します。ワイルドカード(glob)を使用して、特定のパターンのファイルを一括で指定することも可能です。
srcs: [ "java/com/example/myapp/MainActivity.java", "java/com/example/myapp/Utils.java", "aidl/com/example/myapp/IMyService.aidl", "srcs/**/*.kt", // Kotlinファイルも再帰的に含める ], certificate-
APKに署名する証明書を指定します。
Android.mkのLOCAL_CERTIFICATEに相当します。certificate: "platform", privileged-
このフラグを
trueに設定すると、特権アプリとして扱われ、/system/priv-appにインストールされます。Android.mkのLOCAL_PRIVILEGED_MODULEに相当します。privileged: true, sdk_version-
ターゲットSDKバージョンを指定します。
Android.mkのLOCAL_SDK_VERSIONと同様に、"current"、"system_current"などを指定します。システムアプリでは非公開APIへのアクセスが必須となることが多いため、"system_current"がよく使われます。sdk_version: "system_current", static_libsとlibs-
依存するJavaライブラリを指定します。
static_libs: 静的ライブラリを指定します。ライブラリのコードはAPKに直接含まれます。Android.mkのLOCAL_STATIC_JAVA_LIBRARIESに相当します。libs: 共有ライブラリを指定します。Android.mkのLOCAL_JAVA_LIBRARIESに相当しますが、Android.bpでは通常、フレームワークライブラリ(framework.jar,ext.jarなど)は自動的にリンクされるため、明示的に指定することは稀です。
static_libs: [ "androidx.core_core", "androidx.appcompat_appcompat", ], platform_apis-
sdk_versionが"system_current"などでない場合(つまり、公開SDKをターゲットにしている場合)でも、Soongは非公開APIへのアクセスを許可しません。このフラグをtrueに設定すると、公開SDKを使いつつも非公開APIへのアクセスが可能になります。これは、システムの進化に伴い、一部のAPIが非公開になったが、互換性のためにアクセスが必要な場合に役立ちます。 optimize-
ProGuard/R8による最適化設定を制御します。
optimize: { enabled: true, proguard_flags_files: ["proguard.flags"], }, aaptflags-
aapt2コンパイラに渡す追加のフラグを指定します。例えば、リソースのオーバーレイやバージョンコードの自動設定などに使用できます。
aaptflags: [ "--auto-add-overlay", "--version-code", "123", ], arch,target,multilib-
特定のCPUアーキテクチャ(arm, arm64, x86, x86_64)ごとに異なる設定を適用するためのプロパティです。例えば、アーキテクチャごとに異なるJNIライブラリを含めたり、ソースファイルを切り替えたりすることが可能です。
arch: { arm64: { // arm64用の設定 jni_libs: ["libmynative_arm64"], }, arm: { // arm用の設定 jni_libs: ["libmynative_arm"], }, },
総合的なAndroid.bpの例
android_app {
// モジュールの基本情報
name: "MyPrivilegedSystemAppBp",
// ソースファイル (KotlinとJavaの両方を含む)
srcs: [
"src/**/*.java",
"src/**/*.kt",
"aidl/**/*.aidl",
],
// 静的ライブラリ
static_libs: [
"androidx.core_core",
"androidx.appcompat_appcompat",
"com.google.android.material_material",
],
// リソース
resource_dirs: ["res"],
manifest: "AndroidManifest.xml",
// ビルド設定
sdk_version: "system_current",
certificate: "platform",
privileged: true,
// 最適化設定
optimize: {
enabled: true,
proguard_flags_files: ["proguard.flags"],
},
// 製品にデフォルトで含めるかどうか
// .mkのLOCAL_MODULE_TAGS := optional に相当
installable: true,
// JNIライブラリの依存関係
// ここで指定したモジュールがビルドされ、APKに同梱される
jni_libs: ["libmynative_jni"],
}
// JNIライブラリの定義
cc_library_shared {
name: "libmynative_jni",
srcs: ["jni/native_entry.cpp"],
sdk_version: "current",
}
実践:システムアプリ開発のワークフロー
理論を学んだところで、次はいよいよ実際の開発プロセスに移ります。AOSP環境でシステムアプリをゼロから開発し、ビルドして実機で動作させるまでの一連のワークフローを、具体的なコマンドとともにステップバイステップで解説します。
Step 1: AOSPビルド環境の構築
まず、AOSPのソースコードを取得し、ビルド可能な状態にする必要があります。これには数十GBのディスク容量と、高速なインターネット接続、そして強力なマシン(特に多くのCPUコアと大容量のRAM)が要求されます。
- Repoツールのインストール:
$ mkdir ~/bin $ PATH=~/bin:$PATH $ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo $ chmod a+x ~/bin/repo - AOSPソースコードのダウンロード:
$ mkdir aosp && cd aosp # 特定のブランチ(例: android-13.0.0_r1)を初期化 $ repo init -u https://android.googlesource.com/platform/manifest -b android-13.0.0_r1 # ソースコードの同期を開始(数時間以上かかることがあります) $ repo sync -c -j8 - ビルド環境のセットアップ:
このコマンドを実行すると、$ source build/envsetup.shlunch,m,crootなど、AOSPビルドに特化した便利なシェル関数が利用可能になります。 - ビルドターゲットの選択:
$ lunchlunchコマンドを実行すると、ビルド可能なデバイスとビルドタイプの組み合わせ(例:aosp_arm64-eng,aosp_oriole-userdebug)のリストが表示されます。目的のターゲットを番号で選択します。エミュレータ用ならaosp_x86_64-eng、Pixel 6ならaosp_oriole-userdebugなどを選びます。
Step 2: アプリケーションのソースコード配置
次に、開発するシステムアプリのソースコードをAOSPのソースツリー内に配置します。慣例として、新しいアプリケーションはpackages/apps/ディレクトリ以下に配置されます。
# AOSPのルートディレクトリに移動
$ croot
# 新しいアプリ用のディレクトリを作成
$ mkdir -p packages/apps/MySystemApp
# 作成したディレクトリに移動
$ cd packages/apps/MySystemApp
このMySystemAppディレクトリ内に、通常のAndroid Studioプロジェクトと同様のディレクトリ構造(src/, res/, AndroidManifest.xml)と、ビルド設定ファイル(Android.bpまたはAndroid.mk)を配置します。
Step 3: ビルドファイル (.bp or .mk) の作成
前のセクションで学んだ知識を活かして、packages/apps/MySystemApp/ディレクトリにAndroid.bpファイルを作成します。
// packages/apps/MySystemApp/Android.bp
android_app {
name: "MySystemApp",
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
manifest: "AndroidManifest.xml",
// システムアプリとしての設定
certificate: "platform",
privileged: true,
sdk_version: "system_current",
// デフォルトではビルドせず、製品構成で指定された場合のみビルdする
installable: true,
}
Step 4: 製品構成ファイルへの追加
ビルドファイルを作成しただけでは、そのアプリはシステムイメージに含まれません。どのアプリを最終的なイメージに含めるかは、デバイスごとの製品構成ファイル(Product Makefile)で決定されます。
例えば、Pixel 6 (oriole) の場合、関連する.mkファイルはdevice/google/oriole/以下にあります。メインの製品構成ファイル(例: device/google/oriole/aosp_oriole.mk)を探し、PRODUCT_PACKAGESという変数に、先ほどAndroid.bpで定義したモジュール名(nameプロパティの値)を追加します。
# device/google/oriole/aosp_oriole.mk
# ... 他のパッケージ定義 ...
PRODUCT_PACKAGES += \
MySystemApp \
AnotherSystemApp \
...
# ...
この一行を追加することで、次回のフルビルド時にMySystemAppがシステムイメージに含められるようになります。
Step 5: ビルドとフラッシュ
すべての設定が完了したら、いよいよビルドを実行します。
- 個別のモジュールビルド (開発中):
アプリの開発中は、システム全体をリビルドするのではなく、対象のモジュールだけをビルドする方がはるかに高速です。
ビルドが成功すると、生成されたAPKは$ m MySystemAppout/target/product/<device_name>/system/priv-app/MySystemApp/(privilegedな場合)に配置されます。このAPKをadb install -r -gやadb pushでデバイスにインストールして、素早くテストできます。(ただし、システムアプリとして正しく動作するには、システムパーティションに配置する必要があるため、再起動やSELinuxパーミッションで問題が起きることがあります。) - システムイメージのフルビルド:
製品構成ファイルへの変更を反映させ、完全なシステムイメージを生成するには、フルビルドが必要です。
このコマンドは、マシンのCPUコア数に応じて並列実行され、完了までに数十分から数時間かかります。$ make -j$(nproc) - デバイスへのフラッシュ:
ビルドが完了したら、生成されたイメージを実機に書き込みます。
# デバイスをbootloaderモードで再起動 $ adb reboot bootloader # AOSPのルートディレクトリにいることを確認 $ croot # 生成されたイメージをすべて書き込む(-wフラグはユーザーデータをワイプするので注意) $ fastboot flashall -w
デバイスが再起動すれば、あなたの作成したシステムアプリがプリインストールされた状態で起動するはずです。あとはlogcatやデバッガを駆使して、デバッグとテストを進めていきましょう。
高度なトピックとベストプラクティス
システムアプリ開発は、単にAPKをビルドしてシステムイメージに含めるだけで終わりではありません。OSの中核として動作するためには、セキュリティ、互換性、パフォーマンスといった、より高度な側面への深い配慮が不可欠です。
セキュリティ:SELinuxポリシーの壁を越える
現代のAndroidでは、従来のUNIX的なユーザー/グループベースのアクセス制御(Discretionary Access Control, DAC)に加えて、より強力な強制アクセス制御(Mandatory Access Control, MAC)であるSELinux (Security-Enhanced Linux)が全面的に導入されています。
たとえアプリがroot権限で実行されていたとしても、SELinuxのポリシーで許可されていなければ、特定のファイルへのアクセスや、システムサービスへのバインダーコールは拒否されます。システムアプリが、デバイスドライバ、特定のファイル、あるいは他のシステムプロセスと連携する必要がある場合、ほぼ間違いなくSELinuxポリシーの定義が必要になります。
SELinuxポリシーは、.teという拡張子を持つファイル(Type Enforcementファイル)に記述されます。
- ドメインの定義:
まず、新しいシステムアプリのためのセキュリティコンテキスト(ドメイン)を定義します。通常、
device/<vendor>/<device>/sepolicy/ディレクトリ以下にmysystemapp.teのようなファイルを作成します。# mysystemapp.te type mysystemapp, domain; type mysystemapp_exec, exec_type, file_type, system_file_type; # アプリのプロセスをmysystemappドメインで実行するよう指定 init_daemon_domain(mysystemapp) - ポリシー(allowルール)の記述:
次に、この
mysystemappドメインが、何に対してどのような操作を許可されるかをallowルールで記述します。# /dev/my_custom_driver にアクセスすることを許可 allow mysystemapp my_custom_device:chr_file { read write open ioctl }; # system_server が提供する my_service というサービスを見つけてバインドすることを許可 allow mysystemapp system_server:service_manager find; allow mysystemapp my_service:binder { call transfer }; - ファイルコンテキストの定義:
アプリの実行ファイル(APK)がどのドメインに属するかを
file_contextsファイルで定義します。# device/<vendor>/<device>/sepolicy/file_contexts /system/priv-app/MySystemApp(/.*)? u:object_r:mysystemapp_exec:s0
SELinuxポリシーのデバッグは非常に困難を伴います。最も効果的なアプローチは、まずSELinuxをPermissiveモード(警告は出すがブロックはしない)で動作させ、logcatやdmesgに出力されるavc: deniedログを収集し、そのログを基に必要なallowルールを監査ツール(audit2allow)を使って生成していく方法です。
$ adb shell setenforce 0 # Permissiveモードに設定
# ... アプリを操作してdeniedログを発生させる ...
$ adb logcat | audit2allow -p policy.conf
互換性とアップデート戦略
システムアプリはOSと密接に結合しているため、OSのバージョンアップが大きな課題となります。
- APIの変更追従: Androidの新しいバージョンがリリースされると、内部APIが変更・削除されたり、新しいセキュリティ要件(例: targetSdkVersionの引き上げ義務)が導入されたりします。システムアプリは、これらの変更に迅速に対応し、新しいOSバージョンでも正しく動作し続けるようにメンテナンスする必要があります。
- OTA (Over-the-Air) アップデート: システムアプリのバグ修正や機能追加は、OS全体のOTAアップデートの一部として配信するのが伝統的な方法です。しかし、これは時間とコストがかかります。近年では、Google Playストアを通じて一部のシステムアプリ(例: Google Dialer, Gboard)をアップデートする仕組みも利用されていますが、これはアプリが公開APIのみに依存するように慎重に設計されている場合に限られます。
- Project Mainline (APEX): Android 10以降で導入されたProject Mainlineは、OSの特定のコンポーネント(システムアプリを含む)をAPEX (Android Pony EXpress) という新しいパッケージ形式でモジュール化し、OS全体のアップデートとは独立して更新できるようにする仕組みです。これにより、セキュリティパッチなどをより迅速にユーザーに届けることが可能になりました。将来的にシステムコンポーネントを開発する際は、APEXモジュールとしてパッケージングすることも視野に入れるべきでしょう。
プロセス間通信 (IPC) とAIDL
システムアプリは、単独で動作することは稀で、多くは他のシステムサービスやアプリケーションと連携して機能します。Androidにおいて、異なるプロセス間で通信を行うための主要なメカニズムがBinder IPCです。そして、このBinder通信のインターフェースを定義するために使われるのがAIDL (Android Interface Definition Language)です。
AIDLを使うことで、Javaライクな構文でメソッドを定義した.aidlファイルを作成できます。AOSPビルドシステムは、この.aidlファイルを自動的に検出し、クライアント側(呼び出し側)のProxyとサーバー側(実装側)のStubクラスをJavaソースコードとして生成します。
// IMyService.aidl
package com.example.myservice;
// プリミティブ型やParcelableを実装したカスタムオブジェクトを渡せる
interface IMyService {
int getPid();
void showToast(String message);
}
システムサービスを実装するアプリは、この生成されたStubクラスを継承してインターフェースを実装し、ServiceManagerに登録します。一方、クライアントとなるアプリは、ServiceManagerからサービスを取得し、生成されたProxyオブジェクトを通じて、まるでローカルのメソッドを呼び出すかのように、別プロセスのメソッドを呼び出すことができます。システムレベルの機能を実装する際には、AIDLによる堅牢なIPCの設計が不可欠です。
結論
AOSPにおけるシステムアプリ開発は、通常のアプリ開発とは一線を画す、挑戦的で奥深い領域です。ビルドシステムの intricacies(.mkと.bp)、OSアーキテクチャへの深い理解、そしてSELinuxのような複雑なセキュリティ機構への対応が求められます。しかし、その困難を乗り越えた先には、Androidデバイスの核心的な機能を創り出し、何百万人ものユーザー体験を直接的に形成するという、他に代えがたい魅力があります。本稿が、その挑戦的な旅に踏み出す開発者にとって、信頼できる羅針盤となることを願っています。
Post a Comment