マニフェストの呪い 〜Manifest.xmlで泣いた日々〜

※ChatGPTを使用して記事を作成しています。

Androidアプリ開発において、意外と油断しがちなのが AndroidManifest.xml です。

普段は IDE が自動で生成してくれる部分も多く、「とりあえずビルドできて動けばいいや」と思っていた私。

しかし実際には、マニフェストの設定ミスが原因で アプリがストアでリジェクトされたり、通知が届かなかったり、起動すらしない という大惨事を招きました。

今回は、私が実際に経験した「マニフェストの呪い」をテーマに、典型的な失敗例と修正版を紹介します。

ケース1: 権限忘れで機能が動かない

❌ 失敗例

アプリにインターネット接続が必要だったのに、権限を宣言し忘れていました。

開発機では Wi-Fi に繋がっていたため動いているように見えたのですが、ユーザーから「ネットワークエラーで何もできません」と報告が殺到。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.app">
    <!-- 権限の記載がない -->
    <application ...>
        ...
    </application>
</manifest>

Kotlinコード側では普通にリクエストを書いているのに、エラーが返ってきます。

val url = URL("https://example.com/api")
val connection = url.openConnection() as HttpURLConnection
connection.connect() // → java.net.UnknownHostException

✅ 修正版

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.app">
    <uses-permission android:name="android.permission.INTERNET" />
    <application ...>
        ...
    </application>
</manifest>

Androidは「宣言していない権限は拒否する」仕様。

開発序盤のうっかりミスが、ユーザー体験を大きく損なった例でした。

ケース2: Notificationが届かない

❌ 失敗例

Firebase Cloud Messaging を使ってプッシュ通知を実装したときのこと。

なぜか一部端末で通知が届かない。

調べてみたら サービスをマニフェストに登録していなかった のが原因でした。

<application ...>
    <!-- FirebaseMessagingService を宣言し忘れ -->
</application>

そのため、バックグラウンドで通知を受け取ることができず、ユーザーは「通知が来ないアプリ」と判断してアンインストール…。

✅ 修正版

<application ...>
    <service
        android:name=".MyFirebaseMessagingService"
        android:exported="false">
        <intent-filter>
            <action android:name="com.google.firebase.MESSAGING_EVENT"/>
        </intent-filter>
    </service>
</application>

バックグラウンド処理はシステムがマニフェストを参照して呼び出すため、記述漏れは致命的です。

ケース3: Activity宣言漏れでクラッシュ

❌ 失敗例

新しい画面を作り、Activityを追加したのに、マニフェストに登録し忘れていました。

val intent = Intent(this, DetailActivity::class.java)
startActivity(intent) // → ActivityNotFoundException

開発中は Fragment で動かしていたので気づかず、リリース後にユーザーから「ボタンを押すとアプリが落ちる」と報告が殺到しました。

✅ 修正版

<application ...>
    <activity android:name=".DetailActivity" />
</application>

Androidはマニフェストで宣言されていないコンポーネントを呼び出すことができません。

基本的なことですが、焦って開発すると見落としがちです。

ケース4: Intent Filterの地獄

❌ 失敗例

外部からのディープリンクに対応させるために Intent Filter を設定したのですが、指定を誤ったせいで 全てのURLをハンドリングしてしまう バグが発生。

<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="http" />
</intent-filter>

結果、ユーザーがChromeでWebページを開こうとすると 「アプリで開きますか?」と毎回聞かれる 状況になり、レビュー欄が炎上しました。

✅ 修正版

<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data
        android:scheme="https"
        android:host="example.com"
        android:pathPrefix="/articles/" />
</intent-filter>

対象を https://example.com/articles/ に限定することで、不要な動作を回避できました。

ケース5: exportedフラグの罠(Android 12以降)

❌ 失敗例

Android 12 から、明示的に android:exported を指定しないとビルドエラーになります。

しかし私はルールを理解せず、とりあえず全部 true にしてリリースしてしまいました。

結果、外部アプリから不要に呼び出せる状態になり、セキュリティ上のリスクを抱えることに…。

✅ 修正版

<activity
    android:name=".MainActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<service
    android:name=".MyFirebaseMessagingService"
    android:exported="false" />

intent-filter を持つコンポーネントは true、そうでなければ false と明確に使い分けることが重要です。

技術的背景

マニフェストはアプリの 定義書 のようなものです。

  • どんな機能(権限)を使うか
  • どんなコンポーネント(Activity, Service, Receiver)があるか
  • 外部アプリからどう呼び出せるか

これらを全てシステムが読み込んで動作を制御します。

そのため、ほんの少しの記述ミスや漏れが 「アプリが動かない」 という最悪の結果を招きます。

教訓

  1. 権限は最小限かつ正しく宣言する
  2. Activity・Service・Receiverは必ず登録する
  3. Intent Filterは最小範囲に絞る
  4. Android 12以降はexportedを意識する
  5. ストアリリース前にマニフェストをレビューする習慣を持つ

まとめ

マニフェストは普段あまり触れないファイルですが、実はアプリの生死を左右する最重要ファイルです。

私自身、権限忘れや宣言漏れで大きなトラブルを経験し、ユーザーからの低評価に繋がったこともありました。

「マニフェストはアプリの定義書」だと思い、常に正しく、最小限で、漏れなく書くことが大切です。

タイトルとURLをコピーしました