Android SDK: バッテリーマネージャーを使いこなす
Android は、バックグラウンド サービスを積極的に停止することで悪名高いです。中国の OEM (Xiaomi、Huawei など) には、標準の Android バックグラウンド実行制限を完全に破るカスタム電源マネージャーさえあります。
このページでは、Android がアプリを強制終了する 理由 と、現実世界で Tracelet の特定の Android 設定がそれを解決する 方法 について説明します。
オプションの依存関係: 高精度 GMS 位置情報
Tracelet の Android SDK は Google Play サービスの場所 を使用できます — FusedLocationProviderClient
(精度とバッテリーの向上)、ハードウェア アクティビティ認識 (歩行/運転/静止)、
そしてハードウェア ジオフェンシング。 SDK を軽量にし、Google のないデバイスでも使用できるようにするために、これは
依存関係は バンドルされていません。高精度のパスが必要な場合にのみ追加します。
// android/app/build.gradle.kts
dependencies {
implementation("com.google.android.gms:play-services-location:21.3.0")
}これはオプションであり、SDK は正常に機能を低下させます。 play-services-location を使用しない場合、Tracelet
標準の AOSP LocationManager (通常の GPS) に戻ります。追跡はまだ機能します - あなたはただ
融合プロバイダーの精度/バッテリーの利点、ハードウェアアクティビティ認識、およびハードウェアが失われます。
ジオフェンシング。 推奨: Google なし / AOSP デバイスを特にターゲットにしない限り追加します
(例: GMS のない Huawei、または Google から除外された ROM)。
最小バージョン: 21.2.0。 Tracelet の Android コードは、インターフェイスベースの
FusedLocationProviderClient および ActivityRecognitionClient API は、
play-services-location 21.2.0 のインターフェイス。古いリリース (19.0.0 など) にはこれらが同梱されています
具体的なクラスとして呼び出すとスローされます
実行時の java.lang.IncompatibleClassChangeError。 Tracelet は Gradle を公開します
次の場合に play-services-location を 21.2.0+ に自動的に引き上げる依存関係 制約
別の依存関係により古いバージョンが取り込まれますが、自分でバージョンを固定する場合は、そのバージョンを維持してください
21.2.0 以降 (21.3.0 を推奨)。
play-services-location を使用する | それなし (AOSP フォールバック) |
|---|---|
| 融合位置 (最高の精度 + バッテリー) | 生の GPS / LocationManager 経由のネットワーク |
ハードウェアアクティビティ認識 (onActivityChange) | 加速度センサーのみのモーション検出 |
| ハードウェアジオフェンシング | ソフトウェア (SDK 内) ジオフェンス評価 |
オプションの依存関係: Play Integrity (デバイス認証)
Tracelet の デバイス認証 機能 (AttestationConfig) を使用する場合、Android
側は Google Play Integrity を通じて暗号トークンを生成します。まるで
play-services-location、この依存関係は バンドルされていません - 次の場合にのみ追加します。
構成証明を有効にする:
// android/app/build.gradle.kts
dependencies {
implementation("com.google.android.play:integrity:1.6.0")
}これはオプションであり、認証は正常に機能低下します。 Play Integrity がない場合
依存関係があれば、Tracelet の他のすべては正常に動作します。影響を受けるのは構成証明のみです。
AttestationConfig(enabled: true) は警告をログに記録し、Tracelet.getAttestationToken()
クラッシュするのではなく、トークンの代わりに null を返します。次の場合にのみ依存関係を追加します。
構成証明を有効にし、Android で Play Integrity の判定が必要です。を参照してください。
デバイス認証ガイド
完全なワークフローとサーバー側の検証については、
compileOnly ではなく、implementation である必要があります。 Play Integrity 内部では compileOnly です。
Tracelet SDK (したがって、構成証明を使用しないアプリに同梱されたり、強制されたりすることはありません)。もし
app モジュールに依存関係を追加せずに構成証明を有効にすると、
implementation の場合、クラスは実行時に存在せず、構成証明は暗黙的に null を返します。
権限とマニフェストのセットアップ
Tracelet は、アプリをコンパイルするときに必要な権限を AndroidManifest.xml に自動的に挿入します。デフォルトでは、次のことを要求します。
ACCESS_COARSE_LOCATIONおよびACCESS_FINE_LOCATION(追跡に必要)ACCESS_BACKGROUND_LOCATION(アプリを閉じたときの追跡に必要)FOREGROUND_SERVICEおよびFOREGROUND_SERVICE_LOCATION(継続的なバックグラウンド実行に必要)ACTIVITY_RECOGNITION(スマートモーション検出エンジンに必要)POST_NOTIFICATIONS(Android 13 以降のフォアグラウンド サービス UI に必要)SCHEDULE_EXACT_ALARM(正確な周期モード追跡に必要)
使用されていない権限の削除 (マニフェストのマージ)
アプリに特定の機能が必要ない場合 (たとえば、クライアントがモーション検出を必要としない場合、または定期モードを使用しない場合)、アプリレベル AndroidManifest.xml (android/app/src/main/AndroidManifest.xml) で Android のマニフェスト マージ tools:node="remove" ディレクティブを使用して、権限を強制的に削除できます。
Tracelet のネイティブ コードは checkSelfPermission() で完全に保護されています。 権限が不足していると、クラッシュではなく正常なフォールバックがトリガーされます。
| 許可 | 削除時の効果 (tools:node="remove") |
|---|---|
| 非トランスレート | 安全。 基本的な加速度計のみのモーション検出に戻ります。 onActivityChange ストリームは起動されません (歩行/ランニング/運転の分類はありません)。 |
| 非トランスレート | 安全。 Android 10 以降では、アプリが完全にバックグラウンドになったり、スワイプされて消えたりすると、追跡が制限されます。 |
| 非トランスレート | 安全。 定期モードは、バッテリーに優しいものの、不正確な WorkManager タイマーの使用に戻ります。 |
| 非トランスレート | 安全。 フォアグラウンド サービスの永続通知は Android 13 以降では非表示になります。サービスは引き続き実行されます。 |
| 非トランスレート | 安全。 OS からバッテリー最適化の除外をリクエストすることはできません。 |
FOREGROUND_SERVICE / FOREGROUND_SERVICE_LOCATION | ジオフェンス専用/定期専用アプリにとって安全です。 それらを削除すると、継続的な前景追跡 (start()) が無効になりますが、標準のジオフェンスと定期モードは引き続き動作します。 継続的なバックグラウンド追跡には必須。 |
ACCESS_FINE_LOCATION / COARSE | 安全ではありません。 位置情報の許可がなければ、Tracelet は位置を取得できません。 |
Google Play フォアグラウンド サービス ポリシー (2026 年 10 月 28 日発効)。 ジオフェンシングは
フォアグラウンド サービスの場所の使用例は許可されなくなりました。アプリが
フォアグラウンド サービス ジオフェンシング専用のため、削除する必要があります
マージされたものからの FOREGROUND_SERVICE_LOCATION (および FOREGROUND_SERVICE)
マニフェストします。 Tracelet の標準ジオフェンス モードはネイティブ ジオフェンス API を使用し、
フォアグラウンド サービスを開始しないため、ジオフェンス専用アプリは準拠しています。
以下の権限を剥奪し、foregroundService.enabled = false を維持します。を参照してください。
ジオフェンシング ガイド
詳細については。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Geofence-only / periodic-only app: no continuous foreground tracking -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" tools:node="remove" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" tools:node="remove" />
</manifest>例: モーションと背景の権限の削除
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Client doesn't want Activity/Motion tracking -->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" tools:node="remove" />
<!-- Client only wants foreground tracking -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" tools:node="remove" />
<!-- Remove Exact Alarms -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" tools:node="remove" />
</manifest>自動 ProGuard / R8 ルール
Tracelet は、バックグラウンドの Kotlin サービス、Rust コアの JNA バインディング、および反射型 Pigeon チャネルを多用します。これらが難読化されているか、不適切に縮小されている場合、アプリがリリース用にコンパイルされるときに、バックグラウンド追跡が静かに失敗します。
Tracelet 用に ProGuard を手動で構成する必要はありません。
tracelet_android プラグインには、consumer-rules.pro が組み込まれた状態で自動的に出荷されます。 Flutter アプリをリリース モードでビルドすると、Android の R8 シュリンカーはこれらのルールを自動的に抽出し、必要なすべてのクラス (HeadlessTaskService、BootReceiver、Rust uniffi バインディングなど) が圧縮後も存続することを保証します。
シナリオ 1: 配送ドライバー (継続的追跡)
検討した概念: Doze モード、フォアグラウンド サービス、ウェイロック
問題
ユーザーはピザの配達ドライバーです。彼らは携帯電話をポケットに入れ、画面をオフにしました。 15 分後、Android は Doze モード に入ります。ネットワーク アクセスをシャットダウンし、バックグラウンド ジョブを延期し、バッテリーを節約するために CPU ウェイクアップを大幅に制限します。アプリが GPS を取得するために単純なタイマーに依存している場合、Android はタイマーの実行を拒否します。
Tracelet がそれを解決する方法: フォアグラウンド サービス
フォアグラウンド サービスは Android に次のように伝えます: 「おい、私は今非常に重要なことをやっていて、ユーザーはそれを十分に認識している。私を殺さないでください。」
android: tl.AndroidConfig(
foregroundService: tl.ForegroundServiceConfig(
enabled: true,
channelName: 'Delivery Tracking',
notificationText: 'Tracking your route to the customer',
notificationOngoing: true, // User cannot swipe it away
showNotificationOnPauseOnly: false, // Always visible — see the section below
actions: ['Pause', 'Complete'], // Adds interactive buttons to the notification
),
)Tracelet は、ステータス バーに永続的な通知を表示することで、アプリの優先順位をフォアグラウンド UI のほぼレベルに引き上げます。 Android では、Doze モードの制限を回避して、Tracelet を無期限に実行できます。
通知の外観をカスタマイズする
永続的な通知をアプリのブランドと一致させるには、アイコンと色をカスタマイズできます。
android: tl.AndroidConfig(
foregroundService: tl.ForegroundServiceConfig(
enabled: true,
notificationTitle: 'Delivery Mode',
notificationText: 'Tracking your route...',
notificationColor: '#0F9D58', // Your brand hex color
notificationSmallIcon: 'ic_tracelet_icon', // The icon name
notificationLargeIcon: 'ic_large_logo',
),
)重要なアイコンのルール:
- ファイルの場所: 画像ファイルは Android プロジェクトの描画可能フォルダー (
android/app/src/main/res/drawable/ic_tracelet_icon.png) に配置する必要があります。 - 小さいアイコンの制約: Android では、
notificationSmallIconが完全にフラット、透明、白のみである必要があります。色付きのロゴを使用すると、Android は単純に灰色または白の実線の正方形としてレンダリングします。 - 色のプロパティ:
notificationColorプロパティは、ブランドに合わせて小さなアイコンの背景に色を付けます。
Android 13+ 注: サービスを開始する前に POST_NOTIFICATIONS 権限をリクエストする必要があります。そうしないと、通知は OS によってサイレントに抑制されます。 Dart コードからこれをリクエストする正確な方法については、Flutter SDK: 権限 ページを参照してください。
追跡中の通知の更新 (updateNotification())
追跡がすでに開始された「後」に通知を変更する必要があります。例:旅行中にタイトルを「途中」から「到着」に更新したり、テキストを交換したり、アクション ボタンを変更したりすることはできますか?新しい ForegroundServiceConfig を指定して setConfig() を呼び出すと、値は保持されますが、通知のみの変更では、Android がすでに表示している通知が再投稿されません**。新しいコンテンツは、無関係なサービスの再起動またはフォアグラウンド遷移の後にのみ表示されます。
v3.6.8 以降、Tracelet.updateNotification() は 追跡パイプラインを再起動せずに、ライブ通知を適切に更新します。
// 1. Apply the new notification content.
await Tracelet.setConfig(
const tl.Config(
android: tl.AndroidConfig(
foregroundService: tl.ForegroundServiceConfig(
notificationTitle: 'Arriving',
notificationText: 'Your driver is 2 minutes away',
),
),
),
);
// 2. Repost the active notification with the new content.
await Tracelet.updateNotification();設計により安全です。 updateNotification() は追跡を再開することはなく、フォアグラウンド サービスが現在実行されていない (更新するものが何もない) 場合は何も行われません。 iOS では、代わりに実行中の ライブ アクティビティ が更新されます (liveActivityConfig 経由でオプトインした場合)。 Web では何も操作しません。そのため、同じ呼び出しはどのプラットフォームでも安全です。
通知を静かに更新する (notificationOnlyAlertOnce)
上記のパターンには落とし穴があります。 Android は、特に指定しない限り、すべての再投稿を 新鮮なアラート として扱うため、1 分に 1 回更新される通知では、追跡が実行されている限り、1 分に 1 回通知音が再生されます。
Tracelet のチャンネルは振動とライトを無効にして作成されているため、これはブザー音ではなく 音 ですが、デフォルトの notificationPriority では聞こえるため、ユーザーはすぐに気づきます。
notificationOnlyAlertOnce を設定すると、最初の投稿アラートのみとその後のすべての更新がサイレントに配信されます。
await Tracelet.setConfig(
const tl.Config(
android: tl.AndroidConfig(
foregroundService: tl.ForegroundServiceConfig(
notificationOnlyAlertOnce: true,
),
),
),
);これは、以下のタイマーだけでなく、残りのストップ数、保留中のアップロード、現在のジオフェンス、ライブ精度など、追跡の実行中に内容が変更されるあらゆる通知に対して設定する価値があります。
優先度を下げてみてはいかがでしょうか? notificationPriority を low または min に下げるとチャンネルも沈黙しますが、最初の 投稿も沈黙し、Android はチャンネルが 作成されるときにチャンネルの重要性を修正します。デフォルトの重要性チャネルを使用してすでにインストールされているアプリは、channelId も変更しない限り、後でチャネルを下げることはできません。これにより、システム設定で古いチャネルが孤立します。 notificationOnlyAlertOnce には問題はなく、どの優先順位でも機能します。
デフォルトは false で、既存のすべてのアプリの動作が保持されます。オプトインするまでは何も変わりません。
アプリを開いているときに通知を非表示にする (showNotificationOnPauseOnly)
showNotificationOnPauseOnly: true は、ユーザーが実際にアプリを見ている間は追跡通知を削除し、ユーザーが離れると元に戻します。これは、アプリが画面上に表示されている間、通知トレイがきれいなままになるという、本当に優れたエクスペリエンスです。ただし、1 つ厳しい制約があり、その制約は明らかではありません。
通知を非表示にすることは、サービスを降格することを意味します。 Android には通知のないフォアグラウンド サービスの概念がないため、通知を非表示にする唯一の方法は stopForeground() です。追跡は続行され、サービスは実行され続けますが、通知が非表示になっている限り、プロセスはフォアグラウンド サービスを保持しません。
これはまさに、ユーザーがアプリをスワイプして最近のアプリを削除したときに Android が強制終了する状態です。
stopOnTerminate: false (#378 ) の間、showNotificationOnPauseOnly は無視されます。 2 つの設定は互換性のないものを要求しますが、生存の約束が勝ち、通知は表示されたままになります。
なぜ機能させることができないのか
タスクが削除されると、ActivityManager は proc.foregroundServices からどのプロセスを強制終了するかを決定し、onTaskRemoved がアプリのメインスレッドにディスパッチされる 前 に決定します。したがって、SDK は削除時に通知を投稿することで状況を救うことはできません。その時までに評決は出ています。Tracelet にはまさにその軽減策がありましたが、役に立ちませんでした。
結果は誰にも見られないレースとなった。アプリが画面を離れてから通知が戻ってくるまでの隙間でアプリをスワイプすると、プロセスが終了しました。ヘッドレス タスクもイベントもログも、stopOnTerminate: false が保証するものは何もありませんでした。 1 秒後にスワイプすると、すべてが完璧に機能しました。ギャップは、Pixel Fold では 285 ms、Android 15 デバイスでは 700 ~ 1500 ms で測定されました。
代わりにTraceletが行うこと
| あなたの設定 | 行動 |
|---|---|
stopOnTerminate: false + showNotificationOnPauseOnly: true | フラグは無視されます。通知は表示されたままになり、サービスが降格されることはなく、最近の履歴からスワイプしてもプロセスが強制終了されることはありません。 |
stopOnTerminate: true + showNotificationOnPauseOnly: true | 文書どおりに動作し、アプリが画面上にある間は非表示になります。ここでのスワイプ以降は何も約束されていないため、守るべきものは何もありません。 |
| 非トランスレート | 通知は常に表示されます。これがデフォルトです。 |
オーバーライドはサイレントではありません。これは、元のバグのコストの半分でした。 start() ごとに 1 行が常時オンのライフサイクル ログ チャネルに書き込まれ、killed-state の実行を含む任意のログ レベルで Tracelet.getLogs() で読み取り可能です。
notification: showNotificationOnPauseOnly ignored because stopOnTerminate=false — hiding the
notification demotes the foreground service, and a swipe from recents in that window kills the
process (#378). Set stopOnTerminate=true to hide it while the app is open, or
showNotificationOnPauseOnly=false to stop asking.getForegroundServiceHealth() は反対側から同じことを報告します。通知が正当に非表示にされている場合 (stopOnTerminate: true)、serviceForeground は false、lastForegroundPromotionResult は suppressed になります。これは、ユーザーが要求した降格であり、OS が拒否した昇進とは異なります。
選択する
実際にどれが必要かを判断してください。
- 追跡はユーザーがアプリをスワイプしても存続する必要があります →
stopOnTerminate: falseを維持し、通知を表示させます。これは、配送、フリート、従業員、安全アプリの正しいデフォルトです。 - 通知トレイがきれいであることは、スワイプを生き残ることよりも重要です →
stopOnTerminate: trueを設定します。追跡は、アプリが最近のものから削除されると終了します。これは、多くの消費者向けアプリにとって、とにかくユーザーが期待しているものとまったく同じです。
2 番目のケースで得られるものは限られていることに注意してください。Android 14 以降では、アプリが画面外にある場合でも、位置情報フォアグラウンド サービスは通知を表示する必要があるため、非表示はユーザーがアプリ内にいる間のみ適用されます。
これは Android のみです。 iOS にはフォアグラウンド サービス、非表示にする通知、およびこの種のタスク削除による強制終了はありません。showNotificationOnPauseOnly は AndroidConfig 上に存在し、iOS SDK はそれを読み取りません。
通知に経過タイマーを表示する
配達の実行、シフト、ジョブ、ドライブなどの制限されたセッションを中心に構築されたアプリの場合、通知には、選択した瞬間からカウントアップする実行クロックが表示されます。
final shiftStartedAt = DateTime.now();
await Tracelet.setConfig(
tl.Config(
android: tl.AndroidConfig(
foregroundService: tl.ForegroundServiceConfig(
notificationTitle: 'Shift in progress',
notificationStartedAt: shiftStartedAt.millisecondsSinceEpoch,
notificationShowTimer: true,
notificationOnlyAlertOnce: true,
),
),
),
);
await Tracelet.updateNotification();Android は時計自体をレンダリングします。 これを 1 回呼び出します。 OS は、通知が存続している間、ディスプレイを 2 番目の解像度で表示します。アプリが再描画のために起動することはなく、新しいテキストや設定の変更など、無関係な理由で通知を再投稿してもカウントは再開されません。
ユーザーが関心を持つ期間は、追跡が開始される前に開始されたり、追跡の再開後も存続することが多いため、Tracelet が start() から取得するのではなく、notificationStartedAt を指定します。 「追跡が開始されてから」を表示するには、start() を呼び出した瞬間を通過します。
知っておく価値のある意味論。
notificationShowTimer: false(デフォルト) は、正確に今日の通知をレンダリングします。notificationStartedAtを指定しないnotificationShowTimer: trueでは、タイマーが表示されず、その理由がログに記録されます。 「now」を置き換えると、再投稿するたびに時計が再起動され、セッションが誤って報告されます。- 将来の
notificationStartedAtは投稿ごとの現在時刻に固定され、保存された値は変更されないままになるため、デバイスのクロック補正は次の再投稿で修復されます。 - タイマーはカウントアップのみです。表示を停止するには、
notificationShowTimer: falseを設定します。開始インスタントは保存されたままですが、不活性になります。
タイマーの notificationText を書き換えることで、経過時間を自分でレンダリングできます。これは適切なツールである静的なカウントです。これを好む理由は、コストと解像度です。1 分ごとの書き換えは、ネイティブへの完全往復と再投稿 (8 時間の勤務でおよそ 480 件) になり、その方法で「秒」を刻むことを表示するのはまったく実用的ではありません。 OSクロノメーターは一発コールです。
iOS に相当するのは ライブ アクティビティ タイマー で、liveActivityConfig で同じ 2 つのフィールドを受け取ります。
シナリオ 2: 天気アプリ (定期的な追跡)
検討した概念: WorkManager と正確なアラームの比較
問題
ユーザーはローカル天気予報アプリをダウンロードしました。 4 時間ごとにバックグラウンドで起動し、位置情報を取得して、地域の天気予報をダウンロードしたいと考えています。
永続的なフォアグラウンド サービス通知は「必要ありません」。ユーザーは、引き出しに「天気追跡」通知が永続的に表示されるのを嫌がるでしょう。
Tracelet による問題の解決方法: 定期モード
Tracelet は、継続的なサービスの代わりに、Android のジョブ スケジューラを使用して、一時的に起動し、修正を取得してからスリープに戻ることができます。
android: tl.AndroidConfig(
periodicUseForegroundService: false,
periodicUseExactAlarms: true, // Uses AlarmManager instead of WorkManager
)2 つのスケジューラ
-
ワークマネージャー (
periodicUseExactAlarms: false) デフォルト。バッテリーに非常に優しいです。ただし、Android を「いつ」実行するかは Android が決定します。 15 分ごとに実行するように設定すると、Android は 45 分待って、ユーザーが携帯電話のロックを解除して Instagram をチェックしたときに実行する可能性があります (「バッチ処理」と呼ばれます)。それは非常に不正確です。 -
アラームマネージャー (
periodicUseExactAlarms: true) 特定の時間に正確にデバイスを起動する必要がある場合は、これを使用してください。android/app/src/main/AndroidManifest.xmlでSCHEDULE_EXACT_ALARM権限を宣言する必要があります。<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />注意: Google Play はこの許可を厳密に審査します。アプリが目覚まし時計やカレンダーである場合、または正確なタイミングが絶対に必要な場合にのみ使用してください。
シナリオ 3: OEM の攻撃
検討した概念: 設定ヘルス API
問題
あなたはすべてを正しく行いました。フォアグラウンド サービスがあります。しかし、ユーザーはXiaomiの携帯電話を所有しています。 MIUI には独自の「バッテリー セーバー」があり、5 分間画面がオフになるとフォアグラウンド サービスも強制終了します。
Tracelet による問題の解決方法: 自動軽減策とプロンプト
Tracelet は、可能な場合には自動的に内部緩和策を適用しますが、場合によっては、ユーザーが OEM 独自の設定メニューでアプリを物理的にホワイトリストに登録する必要がある場合があります。
final health = await tl.Tracelet.getSettingsHealth();
if (health['isAggressiveOem'] == true) {
// Automatically opens the manufacturer-specific settings screen
// (e.g. Xiaomi Autostart, Huawei App Launch, Samsung Sleeping Apps)
await tl.Tracelet.showPowerManager();
}これを Flutter UI に実装する方法の詳細な例については、診断ツールと電源管理 ページを参照してください。