How to set an alarm to be scheduled at an exact time after all the newest restrictions on Android?

后端 未结 6 548
半阙折子戏
半阙折子戏 2020-12-13 13:49

Note: I tried various solutions that are written about here on StackOverflow (example here). Please do not close this without checking if your solution from what you\'ve fou

相关标签:
6条回答
  • 2020-12-13 14:05

    I know this isn't efficient but might be more consistent with an accuracy of 60 seconds.

    https://developer.android.com/reference/android/content/Intent#ACTION_TIME_TICK

    if this broadcast receiver is used inside a foreground service , you can check the time every minute and make decision on taking an action.

    0 讨论(0)
  • 2020-12-13 14:08

    I think you can ask for the user to set the permission so it disables the energy-saving mode, and warn the user that if he does not use it, exact times won't be achieved.

    Here's the code to request it:

    PowerManager powerManager = (PowerManager) getApplicationContext().getSystemService(POWER_SERVICE);
                String packageName = "your Package name";
                if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    Intent i = new Intent();
                    if (!powerManager.isIgnoringBatteryOptimizations(packageName)) {
                        i.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
                        i.setData(Uri.parse("package:" + packageName));
                        startActivity(i);
    
    0 讨论(0)
  • 2020-12-13 14:12

    I am the author of the open source project you have mentioned in your question (simple alarm clock).

    I am surprised that using AlarmManager.setAlarmClock did not work for you, because my app does exactly that. The code is in the file AlarmSetter.kt. Here is a snippet:

      val pendingAlarm = Intent(ACTION_FIRED)
                    .apply {
                        setClass(mContext, AlarmsReceiver::class.java)
                        putExtra(EXTRA_ID, id)
                        putExtra(EXTRA_TYPE, typeName)
                    }
                    .let { PendingIntent.getBroadcast(mContext, pendingAlarmRequestCode, it, PendingIntent.FLAG_UPDATE_CURRENT) }
    
                val pendingShowList = PendingIntent.getActivity(
                        mContext,
                        100500,
                        Intent(mContext, AlarmsListActivity::class.java),
                        PendingIntent.FLAG_UPDATE_CURRENT
                )
    
                am.setAlarmClock(AlarmManager.AlarmClockInfo(calendar.timeInMillis, pendingShowList), pendingAlarm)
    

    Basically it is nothing special, just make sure that intent has an action and a target class, which is a broadcast receiver in my case.

    0 讨论(0)
  • 2020-12-13 14:19

    Found a weird workaround (sample here) that seems to work for all versions, including even Android R:

    1. Have the permission SAW permission declared in the manifest:
          <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    

    On Android R you will have to also have it granted. On before, doesn't seem like it's needed to be granted, just declared. Not sure why this changed on R, but I can say that SAW could be required as a possible solution to start things in the background, as written here for Android 10.

    1. Have a service that will detect when the tasks was removed, and when it does, open a fake Activity that all it does is to close itself:
    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()
        }
    
    }
    

    FakeActivity.kt

    class FakeActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            Log.d("AppLog", "FakeActivity")
            finish()
        }
    }
    

    You can also make this Activity almost invisible to the user using this theme:

        <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>
    

    Sadly, this is a weird workaround. I hope to find a nicer workaround to this.

    The restriction talks about starting Activity, so my current idea is that maybe if I start a foreground service for a split of a second it will also help, and for this I won't even need SAW permission.

    EDIT: OK I tried with a foreground service (sample here), and it didn't work. No idea why an Activity is working but not a service. I even tried to re-schedule the alarm there and tried to let the service stay for a bit, even after re-schedule. Also tried a normal service but of course it closed right away, as the task was removed, and it didn't work at all (even if I created a thread to run in the background).

    Another possible solution that I didn't try is to have a foreground service forever, or at least till the task is removed, but this is a bit weird and I don't see the apps I've mentioned using it.

    EDIT: tried to have a foreground service running before removal of the app's task, and for a bit afterwards, and the alarm still worked. Also tried to have this service to be the one in charge of task-removed event, and to close itself right away when it occurs, and it still worked (sample here). The advantage of this workaround is that you don't have to have the SAW permission at all. The disadvantage is that you have a service with a notification while the app is already visible to the user. I wonder if it's possible to hide the notification while the app is already in the foreground via the Activity.


    EDIT: Seems it's a bug on Android Studio (reported here, including videos comparing versions). When you launch the app from the problematic version I tried, it could cause the alarms to be cleared.

    If you launch the app from the launcher, it works fine.

    This is the current code to set the alarm:

            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)
    

    I don't even have to use "pendingShowList". Using null is also ok.

    0 讨论(0)
  • 2020-12-13 14:20

    We Don't have anything to do.

    Once your app is not whitelisted it will be always killed once removed from recent-apps.

    Because Original Equipment Manufacturer (OMEs) constantly violating Android compliance.

    So If your app is not whitelisted from the device Manufacture it won't fire any background work even alarms - in case your app is removed from recent-apps.

    You can find a list of devices with that behavior here ALSO you might find a side-solution, However, it won't work well.

    0 讨论(0)
  • 2020-12-13 14:27
    1. Make sure the intent you broadcast is explicit and has the Intent.FLAG_RECEIVER_FOREGROUND flag.

    https://developer.android.com/about/versions/oreo/background#broadcasts

    Intent intent = new Intent(context, Receiver.class);
    intent.setAction(action);
    ...
    intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
    
    PendingIntent operation = PendingIntent.getBroadcast(context, 0, intent, flags);
    
    1. Use setExactAndAllowWhileIdle() when targeting API 23+.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, operation);
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        alarmManager.setExact(AlarmManager.RTC_WAKEUP, time, operation);
    } else {
        alarmManager.set(AlarmManager.RTC_WAKEUP, time, operation);
    }
    
    1. Start your alarm as a Foreground Service:

    https://developer.android.com/about/versions/oreo/background#migration

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        context.startForegroundService(intent);
    } else {
        context.startService(intent);
    }
    
    1. And don't forget permissions:
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    
    0 讨论(0)
提交回复
热议问题