夏のような日差しの日があったり、大雨の日があったり、忙しい天候ですね。子どもたちと昆虫採集に行く機会が増えてきたのですが、飛んでいる虫はなかなか捕まえられず、思い付きで虫取り網をDIYしました。アルミワイヤーで枠を作り、網部分は洗濯ネットをぬい付けました。作ってみるとわかるのですが、市販の網は枠と持ち手の接合部分にかなりの工夫がありました。自作の方はまだまだ改良の余地ありです。
こんにちは。STORES 決済 でAndroidアプリエンジニアをしている Yamaton です。今回は、とあるプロジェクトの過程で調査した「ホームアプリの作り方」をまとめます。調査しているとホームアプリの仕組みがわかっておもしろかったです。
AndroidManifest.xml の設定
ホームアプリ開発のすべての始まりは、これから作るアプリが「ホームアプリである」とAndroid OSに宣言することです。この設定は AndroidManifest.xml に特定のインテントフィルタ (intent-filter) を追加するだけで完了します。
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTask" android:stateNotNeeded="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.HOME" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>
設定項目の解説
<action android:name="android.intent.action.MAIN" />
- 毎度おなじみの。このActivityがアプリケーションのエントリーポイントであることを示します。
<category android:name="android.intent.category.HOME" />
- ホームアプリの心臓部です。このカテゴリを宣言することで、Android OSはこのActivityをホーム画面の選択肢の一つとして認識します。
<category android:name="android.intent.category.DEFAULT" />
- HOMEカテゴリと一緒に設定し、デフォルトのホームアプリを設定する一覧に、このアプリを候補として表示するために必要です。
android:launchMode="singleTask"
- ホームアプリは常に単一のインスタンスで動作することが望ましいです。この設定により、ホームボタンを繰り返し押しても新しいActivityが生成されるのを防ぎ、安定した動作を実現します。
この設定を追加したアプリをデバイスにインストールし、ホームボタンを押してみます。OSが使用するホームアプリを尋ねるダイアログにアプリが表示されれば成功です!
設定 | アプリ | デフォルトのアプリ | ホームアプリ |
---|---|---|---|
![]() |
![]() |
![]() |
![]() |
疑問: MAIN
は必要だけど、 LAUNCHER
は不要なのか?
毎度おなじみの <category android:name="android.intent.category.LAUNCHER" />
は要らないの?と疑問に思うかもしれません。
結論から言うと、不要です。
LAUNCHER
- アプリ一覧(ドロワー)に表示されることを宣言する。
今回作っているのは「ホームアプリ」そのもの、つまり「起動する側」です。 LAUNCHER
が必要なのは「起動される側」なので、今回は不要です。
ただし、「アプリ一覧から起動してもらって、エントリーポイントの画面ではホームアプリ自身の設定をしたい」などの要件があれば、 LAUNCHER
を設定して、アプリ一覧に表示してもいいかもしれません。
メイン機能の実装
アプリ一覧の取得と表示
ホームアプリとして認識されたら、次はメイン機能である「アプリ一覧の表示」を実装します。ここでは、PackageManager でアプリ情報を取得し、Jetpack Composeで画面に描画するまでの流れを解説します。
1. アプリ情報を取得する
デバイスにインストールされているアプリの情報は PackageManager を使って取得できます。
まず、取得した情報を格納するためのシンプルなデータクラスを定義しましょう。
// AppInfo.kt import android.graphics.drawable.Drawable data class AppInfo( val label: String, val packageName: String, val icon: Drawable )
次に、 PackageManager を使い、起動可能なアプリの一覧を List
fun getInstalledApps(packageManager: PackageManager): List<AppInfo> { val intent = Intent(Intent.ACTION_MAIN, null).apply { addCategory(Intent.CATEGORY_LAUNCHER) } val resolveInfoList = packageManager.queryIntentActivities(intent, 0) return resolveInfoList.map { resolveInfo -> AppInfo( label = resolveInfo.loadLabel(packageManager).toString(), packageName = resolveInfo.activityInfo.packageName, icon = resolveInfo.loadIcon(packageManager) ) } }
2. アプリ一覧を表示・起動する
データが準備できたら、いよいよUIを構築します。
まず、パッケージ名を渡すとアプリを起動してくれるヘルパー関数を定義します。
fun launchApp(context: Context, packageName: String) { val intent = context.packageManager.getLaunchIntentForPackage(packageName) if (intent != null) { context.startActivity(intent) } else { // 起動できない場合 Toast.makeText(context, "アプリを起動できません", Toast.LENGTH_SHORT).show() } }
次に、AppInfo のリストを受け取って画面に表示するComposable関数を作成します。各アイテムがクリックされたときに、先ほどの launchApp 関数が呼ばれるようにイベントを渡せるようにしておくと便利です。
@Composable fun AppList(apps: List<AppInfo>, onAppClick: (String) -> Unit) { LazyVerticalGrid( columns = GridCells.Fixed(4) ) { items(apps) { app -> Column( modifier = Modifier .padding(8.dp) .clickable { onAppClick(app.packageName) }, horizontalAlignment = Alignment.CenterHorizontally ) { Image( painter = rememberDrawablePainter(drawable = app.icon), contentDescription = app.label, ) Text( text = app.label, ) } } } }
注: rememberDrawablePainterはDrawableを描画するシンプルな方法ですが、この関数が含まれるAccompanistライブラリは将来的に非推奨になる可能性があります。実際の開発では、キャッシュ機能も備えた画像読み込みライブラリの利用をおすすめします。
これらのコンポーネントをActivityで組み合わせれば、ホームアプリの基本的なUIが完成です。
おわりに
今回は、ホームアプリの作り方として、AndroidManifest.xml の設定から、Jetpack Composeを使ったアプリ一覧の表示・起動までをご紹介しました。
AOSP(Android Open Source Project)の Launcher3 は基本的な機能が網羅されているので、この記事をきっかけにホームアプリを作りたくなった方は、ぜひ参考にしてみてください。