[解決済み] Androidの最新制限をすべてクリアした上で、正確な時刻にスケジュールされるアラームを設定するには?
質問内容
注:StackOverflowに書かれている様々な解決策を試しました(例 こちら ). 以下に書いたテストを使って、あなたが見つけたものからの解決策がうまくいくかどうか確認せずに、これを閉じないでください。
背景
アプリには、ユーザーが特定の時間にリマインダーを設定するという要件があります。そのため、この時間にアプリがトリガーされると、バックグラウンドで小さな処理(単なるDBクエリ処理)を行い、リマインダーを伝える簡単な通知を表示します。
以前は、比較的特定の時刻に何かをスケジュールするために、簡単なコードを使用していました。
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val pendingIntent = PendingIntent.getBroadcast(context, requestId, Intent(context, AlarmReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
when {
VERSION.SDK_INT >= VERSION_CODES.KITKAT -> alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
else -> alarmManager.set(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
}
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d("AppLog", "AlarmReceiver onReceive")
//do something in the real app
}
}
使用方法
val timeToTrigger = System.currentTimeMillis() + java.util.concurrent.TimeUnit.MINUTES.toMillis(1)
setAlarm(this, timeToTrigger, 1)
問題点
このコードを新しいAndroidバージョンのエミュレータとAndroid 10搭載のPixel 4でテストしましたが、トリガーしないか、あるいは私が提供したものから非常に長い時間が経過してからトリガーするようです。私はよく ひどい動作 その 一部のOEM が最近のタスクからアプリを削除することに追加されましたが、こちらはエミュレータとPixel 4端末(純正)の両方で確認できました。
で読んだことがあります。 ドキュメント アラームの設定について、あまり頻繁に発生しないようにアプリで制限されるようになったということですが、これには特定の時間にアラームを設定する方法が書かれていませんし、また、どのようにして Googleの時計アプリ が成功しました。
それだけでなく、私の理解では、特に端末の低電力状態に対して制限をかけるべきとありますが、私の場合、端末もエミュレータもこの状態にはなっていませんでした。今から約1分後にアラームが鳴るように設定しています。
多くの目覚まし時計アプリが以前のように動作しなくなったのを見ると、ドキュメントに欠けているものがあるように思います。そのようなアプリの例としては、人気のある タイムリー であったアプリを Googleに買収された が、新しい制限に対応するための新しいアップデートを受けることはなく、現在、ユーザーは を返せということです。 . ただし、以下のような人気のあるアプリは問題なく動作します。 これ .
試してみたこと
アラームが実際に動作することをテストするために、アプリを初めてインストールした後、デバイスをPCに接続した状態で、今から1分後にアラームを作動させようとして、以下のテストを実行しました(ログを見るため)。
- アプリがフォアグラウンドにあり、ユーザーから見える状態でのテスト。- は1-2分かかりました。
- アプリがバックグラウンドに送られたときのテスト(ホームボタンなどを使用) - 約1分かかった
- アプリのタスクが最近のタスクから削除されたときのテスト。- 20分以上待ってもアラームが作動する様子が見られず、ログに書き込まれました。
- 3と同じように、画面も消してください。おそらくもっとひどいことになると思いますが・・・。
次のものを使ってみましたが、すべてうまくいきません。
-
alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)
-
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
-
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
-
上記のいずれかの組み合わせに、:
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) alarmManager.setWindow(AlarmManager.RTC_WAKEUP, 0, 60 * 1000L, pendingIntent)
-
BroadcastReceiverの代わりにサービスを使用することを試みました。また、別のプロセスで試してみました。
-
バッテリー最適化からアプリを無視させるようにしてみたが(効果なし)、他のアプリが必要としていない以上、私も使うべきでない。
-
これを使ってみた。
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP)
alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
- のトリガーを持つサービスを持ってみました。 onTaskRemoved でアラームを再スケジュールしましたが、これも役に立ちませんでした(サービスは正常に動作しています)。
Googleの時計アプリについては、トリガーされる前に通知が表示されること以外は特に見当たりませんでしたし、バッテリー最適化の設定画面の「"not optimized"」の項目にも見当たりません。
これはバグらしいということで、この件について報告しました。 ここで サンプルプロジェクトとビデオを含む、この問題を紹介します。
複数のバージョンのエミュレータで確認したところ、API 27 (Android 8.1 - Oreo) からこの動作が始まったようです。見る ドキュメントで , AlarmManagerについては触れられていないようですが、その代わりに様々な裏方作業について書かれていました。
質問内容
-
今時、比較的正確な時刻に何かが起動するように設定するにはどうしたらいいのでしょうか?
-
上記の解決策がうまくいかなくなったのはなぜですか?何かが足りないのでしょうか?パーミッション?もしかして、Workerを代わりに使うべき?しかし、それは時間通りにトリガーされない可能性があることを意味しませんか?
-
Google "Clock"アプリはどのようにしてこれらのことを克服し、たとえ1分前にトリガーされたとしても、常に正確な時刻にトリガーするのでしょうか?それはシステムアプリだからでしょうか?システムアプリだからということなのでしょうか?
システムアプリだからということであれば、2分間で2回アラームを鳴らすことができる別のアプリを見つけたのですが。 こちら フォアグラウンドのサービスを使うことがあるようですが。
EDIT: Githubの小さなリポジトリを作って、アイデアを試しています。 こちら .
編集部:ついに サンプルを発見 はオープンソースで、この問題がない。残念ながら、このアプリは非常に複雑で、最近のタスクからアプリを削除した後もアラームがスケジュールされるようにするために、何がそんなに違うのか(そして、私のPOCに追加すべき最小限のコードは何か)をまだ解明しようとしています。
解決方法は?
奇妙な回避策を発見した(サンプル ここで ) を使えば、Android Rさえも含むすべてのバージョンで動作するようです。
- マニフェストでSAWパーミッションを宣言してもらう。
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
Android Rでは、grassedも必要です。以前は、宣言するだけで、付与する必要はないように思えたのですが。なぜRでこのようになったのかはわかりませんが、バックグラウンドで物事を開始するための解決策として、SAWが必要になる可能性があることは、以下のように書かれています。 これ をAndroid 10に対応させました。
EDIT:以下は 案内 をリクエストする方法について説明します。
- タスクが削除されたことを検知し、削除されたら、自分自身を閉じるだけの偽のActivityを開くサービスを用意してください。
class OnTaskRemovedDetectorService : Service() {
override fun onBind(intent: Intent?) = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int) = START_STICKY
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
Log.e("AppLog", "onTaskRemoved")
applicationContext.startActivity(Intent(this, FakeActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
stopSelf()
}
}
フェイクアクティヴィティ.kt
class FakeActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("AppLog", "FakeActivity")
finish()
}
}
また、このテーマを使って、このActivityをユーザーからほとんど見えないようにすることができます。
<style name="AppTheme.Translucent" parent="@style/Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>
</style>
悲しいかな、これは変な回避策です。もっと素敵な回避策を見つけたいものです。
制限にはActivityの起動について書かれているので、フォアグラウンドサービスを一瞬だけ起動すれば、それも解決するのではないか、そのためにはSAW権限も必要ないのではないか、というのが今の私の考えです。
EDIT: OK フォアグラウンドサービスで試してみました (サンプル こちら )で、うまくいきませんでした。アクティビティは動作するのに、サービスが動作しない理由は不明です。そこでアラームの再スケジュールも試したし、再スケジュール後もサービスを少し滞在させようとした。また、通常のサービスも試しましたが、タスクが削除されたため、もちろんすぐに終了し、まったく動作しませんでした(バックグラウンドで実行するスレッドを作成しても)。
もうひとつの可能な解決策は、フォアグラウンドサービスを永遠に、少なくともタスクが削除されるまで続けることですが、これは少し奇妙で、私が言及したアプリがこれを使用しているのを見たことがありません。
EDIT: アプリのタスクを削除する前と、削除後少しの間、フォアグラウンドサービスを実行させてみましたが、アラームはまだ作動しました。また、このサービスをタスク削除イベントの担当とし、発生したらすぐに終了するようにしてみたところ、やはり動作しました(サンプル こちら ). この回避策の利点は、SAWパーミッションが全く必要ないことです。デメリットは、アプリがすでにユーザーから見えている状態で、通知付きのサービスを利用することです。アクティビティ経由でアプリがすでにフォアグラウンドにある間に通知を非表示にすることは可能でしょうかね。
EDIT: Android Studioのバグらしいです(報告済み こちら バージョン比較の動画もあります)。 私が試した問題のあるバージョンからアプリを起動すると、アラームがクリアされることがあります。
ランチャーからアプリを起動すると、正常に動作します。
これが現在のアラームを設定するコードです。
val timeToTrigger = System.currentTimeMillis() + 10 * 1000
val pendingShowList = PendingIntent.getActivity(this, 1, Intent(this, SomeActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
val pendingIntent = PendingIntent.getBroadcast(this, 1, Intent(this, AlarmReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
manager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingShowList), pendingIntent)
pendingShowList"を使う必要もないんだ。nullを使うのもOKです。
関連
-
[解決済み】コンテンツには、id属性が「android.R.id.list」であるListViewが必要です。
-
[解決済み】Bluestackの向きを変更する : ポートレート/ランドスケープモード
-
[解決済み】sendUserActionEvent()がnullである。
-
[解決済み】Android 8:クリアテキストのHTTPトラフィックが許可されない
-
[解決済み】android.content.res.Resources$NotFoundExceptionの取得:androidにリソースが存在する場合でも例外が発生する。
-
[解決済み] コンパイルした.apkを端末にインストールしようとするとINSTALL_FAILED_UPDATE_INCOMPATIBLEが表示される
-
[解決済み] com.android.supportのライブラリは全て全く同じバージョン表記である必要があります。
-
[解決済み] Androidのソフトキーボードをプログラムで閉じる/隠すにはどうすればよいですか?
-
[解決済み] Androidで現在の時刻と日付を取得する方法
-
[解決済み] Androidのエラーです。デバイス*に*.apkをインストールできませんでした: タイムアウト
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】「ArrayAdapterはリソースIDがTextViewであることが必要」XMLの問題点
-
[解決済み】Android Intent コンストラクタを解決できない
-
[解決済み】インストールエラー。インストールエラー:install_failed_older_sdk
-
[解決済み】Dalvikとdalvik-cacheとは何ですか?
-
[解決済み] 現在のテーマでスタイル 'coordinatorLayoutStyle' を見つけることができませんでした。
-
[解決済み】Android Studioです。「プロジェクトが C ドライブに作成されている場合、「タスク ':app:mergeDebugResources' の実行に失敗しました。
-
[解決済み】Bluestackの向きを変更する : ポートレート/ランドスケープモード
-
[解決済み】android.content.res.Resources$NotFoundExceptionの取得:androidにリソースが存在する場合でも例外が発生する。
-
[解決済み] sendUserActionEvent() は null です。
-
[解決済み] Xlint:deprecationを使用して再コンパイルする方法