Android - ACTION_BOOT_COMPLETEDイベント受信

一般的に、起動されると、ほとんどのアプリは実行されていない状態です。

Androidは起動が完了したら、 ACTION_BOOT_COMPLETEDインテントを渡して、アプリが実行されるようにします。 アプリは、このインテントを受けて、いくつかのタスクを処理することができます。

この記事では、 ACTION_BOOT_COMPLETEDインテントを受信する方法をご紹介します。

この記事で紹介されたSample AppはGitHub - StartForegroundServiceで確認することができます。 この記事の例では、Kotlinで作成されました。

権限の設定

Appが ACTION_BOOT_COMPLETEDインテントを受信するには、次のようにRECEIVE_BOOT_COMPLETED権限をAndroidManifestに宣言する必要があります。

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

<application ...>
    ...
</application>

BroadcastReceiverを実装

イベントを半分ウリョミョンReceiverクラスを実装し、そのクラスをAndroidManifestに定義する必要があります。 起動すると、アプリが実行されていないとき、 Context.registerReceiver() APIでレシーバを登録することができないからです。

次のようにReceiverクラスを生成します。

class MyReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        Log.d("Test", "Receive : ${intent.action}")

        // Do something
    }
}

そして、次のようにAndroidManifestにレシーバを定義します。 インテントを受信するために受信機のインテントフィルタに BOOT_COMPLETED Actionを追加する必要があります。

<application ... >
    ...

    <receiver android:name=".MyReceiver">
        <intent-filter >
            <action android:name="android.intent.action.BOOT_COMPLETED"/>
        </intent-filter>
    </receiver>
</application>

今アプリをインストールして、デバイスを再起動すると、 BOOT_COMPLETEDがアプリに渡されることを確認することができます。

12-24 11:35:00.574 15143 15143 D Test    : Receive : android.intent.action.BOOT_COMPLETED

サービスの実行

BOOT_COMPLETEDインテントを受けて、私のアプリのサービスを実行してどのような操作を処理することができます。

サービスの実装

まず、次のようにServiceを実装します。

class MyService : Service() {
    companion object {
        const val NOTIFICATION_ID = 10
        const val CHANNEL_ID = "my_notification_channel"
    }

    override fun onCreate() {
        super.onCreate()
        Log.d("Test", "MyService is started")

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            createNotificationChannel()
            val notification = NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("MyService is running")
                .setContentText("MyService is running")
                .build()
            startForeground(NOTIFICATION_ID, notification)
        }
    }

    private fun createNotificationChannel() {
        val notificationChannel = NotificationChannel(
            CHANNEL_ID,
            "MyApp notification",
            NotificationManager.IMPORTANCE_HIGH
        )
        notificationChannel.enableLights(true)
        notificationChannel.lightColor = Color.RED
        notificationChannel.enableVibration(true)
        notificationChannel.description = "AppApp Tests"

        val notificationManager = applicationContext.getSystemService(
                Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(
                notificationChannel)
    }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }
}

Android O(API 26)からBackground service実行が制限されてForegroundで実行する必要があります。 startForegroundService()でサービスをForegroundに実行させることができます。しかし、サービスは5秒以内に startForeground()を呼び出してNotificationにサービスがForegroundに実行中であることをユーザーに通知します。そうでなければ、システムは、サービスを終了させます。

そのため、上記のコードでNotificaiton ChannelとNotificaitonを作成しました。

注意すべき点は、上記のコードでは、次のように startForeground()を呼び出すし、Channel IDを引数として渡したが、Channel IDは0以外の数値が渡されます。 0を渡すと、動作がされていません。

startForeground(NOTIFICATION_ID, notification)

Manifestにパーミッションとサービスの登録

次のようにAndroidManifestにサービスを定義して FOREGROUND_SERVICEパーミッションを追加する必要があります。 FOREGROUND_SERVICEはForegroundでサービスを実行させるために必要な権限です。

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

<application>
    ...

    <service android:name=".MyService" />

</application>

レシーバでサービスを実行

次に、Receiverでインテントを受けた時のサービスを実行するように実装します。

class MyReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        Log.d("Test", "Receive : ${intent.action}")

        val intent = Intent(context, MyService::class.java)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context.startForegroundService(intent)
        } else {
            context.startService(intent)
        }
    }
}

上記のようにAndroid O(API 26)からBackground Serviceの実行を提案しています。 API 26以上では startForegroundService()でサービスをForegroundで実行する必要があります。

テスト

上記のように実装したら、アプリをインストールして、デバイスを再起動してみてください。

起動後少し待てばReceiverで BOOT_COMPLETEDが伝達され、Foregroundでサービスが実行されることを確認することができます。

12-24 13:04:58.044  3793  3793 D Test    : Receive : android.intent.action.BOOT_COMPLETED
12-24 13:04:58.054  3793  3793 D Test    : MyService is started

foreground service

Troubleshooting

FOREGROUND_SERVICE権限をアプリに追加しない場合は、次のようなエラーでアプリが終了します。

12-24 12:58:32.783  3688  3688 E AndroidRuntime: FATAL EXCEPTION: main
12-24 12:58:32.783  3688  3688 E AndroidRuntime: Process: com.example.app, PID: 3688
12-24 12:58:32.783  3688  3688 E AndroidRuntime: java.lang.RuntimeException: Unable to create service com.example.example.MyService: java.lang.SecurityException: Permission Denial: startForeground from pid=3688, uid=10153 requires android.permission.FOREGROUND_SERVICE

startForegroundService()の呼び出し後、実行されたサービスで、5秒以内に startForeground()を呼び出していない場合、システムによってサービスが終了します。

12-24 11:53:19.793   504   530 W ActivityManager: Bringing down service while still waiting for start foreground: ServiceRecord{21733d5 u0 com.example.app/.MyService}

12-24 11:53:19.912   504  3971 I ActivityManager: Crashing app skipping ANR: ProcessRecord{1bdf9c0 3660:com.example.app/u0a153} Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{21733d5 u0 com.example.app/.MyService}

参考

codechachaCopyright ©2019 codechacha