※ChatGPTを使用して記事を作成しています。
Android 開発において、バックグラウンド処理は避けて通れません。
API通信、データベース同期、通知スケジュール、ファイルアップロードなど、多くの機能が裏で動いています。
しかし「制約」を軽視すると、思わぬトラブルにつながります。
今回は実際の失敗例と修正版コードを交えて、バックグラウンド処理の怖さと対処法をまとめます。
ケーススタディ1:Service暴走事件
失敗例
「アプリが終了しても動いてほしい」と考えて Service
を常駐させた実装。
class MyBackgroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Thread {
while (true) {
// 定期的にAPI呼び出し
fetchDataFromApi()
Thread.sleep(60_000)
}
}.start()
return START_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
}
🔴 問題点
- 無限ループ+Threadでバッテリーを消耗
- Android 8.0以降ではバックグラウンド制限により強制終了される
START_STICKY
を乱用して再起動を繰り返す
修正版
WorkManager
を利用し、制約に従った形に修正。
class SyncWorker(appContext: Context, workerParams: WorkerParameters) :
CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result {
fetchDataFromApi()
return Result.success()
}
}
// 定期実行の登録
val request = PeriodicWorkRequestBuilder<SyncWorker>(15, TimeUnit.MINUTES)
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"SyncWork",
ExistingPeriodicWorkPolicy.KEEP,
request
)
✅ WorkManagerは
- バックグラウンド制約に対応済み
- OSに合わせて最適化された方法で実行
- 再起動後もスケジュール維持
ケーススタディ2:AlarmManagerを信じすぎた話
失敗例
「午前9時に必ず通知を出す」として AlarmManager
を利用。
val intent = Intent(context, MyReceiver::class.java) val pendingIntent = PendingIntent.getBroadcast( context, 0, intent, PendingIntent.FLAG_IMMUTABLE ) val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager alarmManager.setExact( AlarmManager.RTC_WAKEUP, Calendar.getInstance().apply { set(Calendar.HOUR_OF_DAY, 9) set(Calendar.MINUTE, 0) }.timeInMillis, pendingIntent )
🔴 問題点
- Dozeモード中は正確に動かない
- バッテリー最適化の影響で端末によって挙動が違う
- 「通知が来ない」とユーザーからクレーム
修正版
WorkManager
を使って「9時頃に通知する」設計に変更。
class NotificationWorker(appContext: Context, params: WorkerParameters) :
CoroutineWorker(appContext, params) {
override suspend fun doWork(): Result {
showNotification("お知らせ", "朝の時間です!")
return Result.success()
}
}
val request = PeriodicWorkRequestBuilder<NotificationWorker>(24, TimeUnit.HOURS)
.setInitialDelay(calculateDelayUntil9AM(), TimeUnit.MILLISECONDS)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"DailyNotification",
ExistingPeriodicWorkPolicy.REPLACE,
request
)
✅ 「正確な9時」は保証されないが、制約下でも確実に動作する。
ケーススタディ3:ForegroundServiceを忘れた事件
失敗例
大きなファイルをダウンロードする処理をバックグラウンドで開始。
fun downloadFile(url: String) { GlobalScope.launch { val data = api.download(url) saveToFile(data) } }
🔴 問題点
- OSによっては途中でプロセスが落とされる
- ユーザーに進行状況が見えない
- ダウンロード失敗の報告が多数
修正版
ForegroundService
+ Notification
で修正。
class DownloadService : LifecycleService() { override fun onCreate() { super.onCreate() val notification = NotificationCompat.Builder(this, "download_channel") .setContentTitle("ダウンロード中") .setSmallIcon(R.drawable.ic_download) .build() startForeground(1, notification) } fun startDownload(url: String) { lifecycleScope.launch { val data = api.download(url) saveToFile(data) stopForeground(STOP_FOREGROUND_REMOVE) } } }
✅ 長時間処理はForegroundServiceに委ねるべき。
学びのまとめ
- 無限ループや独自ThreadはNG → OSにKILLされる
- AlarmManagerは信じすぎない → Dozeで止まる
- 長時間処理はForegroundService必須
- 最終的にはWorkManagerに寄せるのが安定
おわりに
バックグラウンド処理は、見た目には地味ですがアプリの信頼性を大きく左右します。
「動いて当然」と思っていたコードが、OSや端末によって止まるのは日常茶飯事です。
今回のケーススタディを通じて、「制約を理解した上で設計する」ことの重要性を実感していただけたら幸いです。