class LocationActivity : AppCompatActivity() {
private var locationClient: LocationClient? = null
override fun onStart() {
super.onStart()
// Start listening when visible to the user
locationClient?.startLocationUpdates()
}
override fun onStop() {
super.onStop()
// Release when invisible to save battery and prevent leaks on process kill
locationClient?.stopLocationUpdates()
}
}
Androidアプリケーションの4つの主要コンポーネントは、Activity、Service、BroadcastReceiver、ContentProviderです。Activityは画面操作のためのユーザーインターフェイスを提供します。Serviceは専用のUIを持たずにバックグラウンド処理を実行します。BroadcastReceiverはシステム全体またはアプリレベルのブロードキャスト通知をリッスンし、応答します。ContentProviderは構造化データを管理し、他のアプリケーションや内部モジュールに公開します。
デフォルトでは、Activity(onCreate、onStart、onResumeなど)、Service(onCreate、onStartCommand、onBindなど)、BroadcastReceiver(onReceive)の主要なライフサイクルコールバックは、アプリケーションのメインスレッド(UIスレッド)上で同期的に実行されます。ContentProviderのメソッド(onCreateなど)もメインスレッドで初期化されますが、プロセス間をまたいで呼び出されるqueryやinsertなどの操作はBinderスレッドプールのスレッド上で実行されます。
デフォルトのコールバックはメインスレッドで実行されるため、ネットワークリクエストや大規模なデータベースのディスクI/Oといった重いブロッキング処理をコールバック内で直接実行すると、UIループがブロックされ、ANR(Application Not Responding)エラーが発生します。
class DataSyncService : Service() {
private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// onStartCommand runs on the Main Thread; heavy work must be offloaded
serviceScope.launch {
performDiskAndNetworkSync()
stopSelf(startId)
}
return START_NOT_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
override fun onDestroy() {
super.onDestroy()
serviceScope.cancel()
}
private fun performDiskAndNetworkSync() {
// Blocking I/O safely executed on Dispatchers.IO
}
}
@Composable
fun CounterExample() {
// Resets to 0 on screen rotation
var localCount by remember { mutableStateOf(0) }
// Persists across screen rotation and process death
var persistentCount by rememberSaveable { mutableStateOf(0) }
Column {
Button(onClick = { localCount++; persistentCount++ }) {
Text("Local: $localCount | Saved: $persistentCount")
}
}
}
val displayName: String? = intent.getStringExtra("display_name")
val greeting = "Hello, ${displayName ?: "Guest"}"
val lengthText = displayName?.length?.toString() ?: "No name provided"
// Risky: crashes if displayName is null
val riskyLength = displayName!!.length
10Google Play App Signing を利用する際、アップロード鍵とアプリ署名鍵にはどのような違いがありますか?
Google Play App Signing において、アップロード鍵とアプリ署名鍵は2つの異なるセキュリティ上の役割を果たします。
アップロード鍵は開発者が保持し、ビルド成果物(AAB)を Google Play Console にアップロードする前に署名するために使用され、開発者の身元を Google に対して証明します。
アプリ署名鍵は Google のクラウドインフラ内に安全に保管され、エンドユーザーのデバイスに実際に配信・インストールされる生成済み APK に Google Play が署名するために使用されます。これにより、Android 上におけるアプリの恒久的な暗号アイデンティティが確立されます。
運用上の大きなメリットは鍵の復旧性です。開発者がアップロード鍵を紛失または侵害された場合でも、Google Play サポートに本人確認を行うことで、アプリのアップデート経路を壊すことなくアップロード鍵を再設定できます。開発者がアプリ署名鍵を直接保持していた従来の方式では、鍵を紛失するとアプリを二度と更新できなくなっていました。
FragmentManager の状態喪失(`IllegalStateException: Can not perform this action after onSaveInstanceState`)や非同期の画面遷移における競合状態は、ネットワークコールバックやリアクティブストリームなどの非同期処理が、ホストのライフサイクルが `onSaveInstanceState()` や `onStop()` を通過した後に UI トランザクションを実行しようとした際に発生します。本番環境でこれらを検知および診断するために、`Application.ActivityLifecycleCallbacks` や `FragmentManager.FragmentLifecycleCallbacks` を介してライフサイクルのテレメトリとブレッドクラムを実装し、クラッシュ前のタイムスタンプ付き遷移ログ、保留中のバックスタック数、実行コンテキストを記録します。アーキテクチャとして状態喪失を防ぐには、UI トランザクションや画面遷移を生の非同期コールバックからではなく、ライフサイクルを認識する状態オブザーバー(`repeatOnLifecycle(Lifecycle.State.RESUMED)` やライフサイクルバインディングを伴って collect される `StateFlow` など)によって排他的に駆動させる必要があります。画面遷移イベントは、単一方向データフロー(UDF: Unidirectional Data Flow)の離散的な状態遷移、またはライフサイクル状態が少なくとも `STARTED` または `RESUMED` である間のみ消費される単発イベントとしてモデル化すべきです。さらに、アーキテクチャ上のガードレールとして、復元不要な一時的コンテキストでのみ `commitStateLoss()` を明示的に許可するか、より望ましくは厳格なライフサイクル境界を持つ Jetpack Navigation コンポーネントへ移行することを強制します。カスタム Android Lint ルールによる静的解析や、デバッグビルドでのランタイム強制(例: Fragment の StrictMode)により、本番環境に到達する前に違反を検出できます。
class NavigationDispatcher @Inject constructor() {
private val _events = Channel<NavigationCommand>(Channel.BUFFERED)
val events: Flow<NavigationCommand> = _events.receiveAsFlow()
fun navigate(command: NavigationCommand) {
_events.trySend(command)
}
}
// In Fragment / Activity
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
navigationDispatcher.events.collect { command ->
CrashReporting.leaveBreadcrumb("Navigating to ${command.destination} at state ${lifecycle.currentState}")
command.execute(parentFragmentManager)
}
}
}