Solution for Android alarm setting

Solution for Android alarm setting

Setting an alarm on Android is not as simple as setting an alarm on iOS. Developers who have done this know how difficult it is. Here is my solution to setting an alarm on Android.

[[172016]]

Main Issues

  1. API19 started to modify the mechanism of AlarmManager.
  2. After the application is killed, the set alarm will not ring.
  3. Entering Doze mode in versions 6.0 and above will stop JobScheduler from working.
  4. After the phone is restarted, the alarm becomes invalid.

Modification of AlarmManager mechanism above API19

Before API 19, AlarmManager provided three methods to set alarms. Since the alarm only needs to be set once, the method set(int type, long startTime, PendingIntent pi) is used. Starting from API 19, the mechanism of AlarmManager is inaccurate transmission. The operating system will convert the alarm to minimize wake-up and battery usage.

Because the previous program did not handle the alarm settings above API19, setting the alarm on phones above 4.4 was unresponsive (there was no alarm even if the application was not killed).

Therefore, setting the alarm needs to be processed and set according to the API version. The code is as follows:

  1. AlarmManager am = (AlarmManager) getActivity()
  2. .getSystemService(Context.ALARM_SERVICE);
  3. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  4. am.setExact(AlarmManager.RTC_WAKEUP, TimeUtils
  5. .stringToLong(recordTime, TimeUtils.NO_SECOND_FORMAT), sender);
  6. } else {
  7. am.set (AlarmManager.RTC_WAKEUP, TimeUtils
  8. .stringToLong(recordTime, TimeUtils.NO_SECOND_FORMAT), sender);
  9. }

This ensures that the alarm is set even when the application is not killed.

What to do when an application is killed

After the application is killed, the set alarm becomes invalid. Here, the daemon process and gray keep alive are used to ensure that the background alarm service is not killed. When the application and the alarm service are killed, the daemon process and gray keep alive restart the alarm service and reset the alarm.

Regarding the processing of daemon processes, an open source daemon library is used here. Android-AppDaemon

Add the open source daemon Android-AppDaemon to the onCreate of the alarm service. The code is as follows:

  1. @Override
  2. public void onCreate() {
  3. super.onCreate();
  4. Daemon.run(DaemonService.this,
  5. DaemonService.class, Daemon.INTERVAL_ONE_MINUTE);
  6. startTimeTask();
  7. grayGuard();
  8. }

To further ensure the survival of the alarm service, add gray keep-alive (using the system vulnerability to start the foreground Service). The code is as follows:

  1. private void grayGuard() {
  2. if (Build.VERSION.SDK_INT < 18) {
  3. //API < 18, this method can effectively hide the icon on the Notification
  4. startForeground(GRAY_SERVICE_ID, new Notification());
  5. } else {
  6. Intent innerIntent = new Intent(this, DaemonInnerService.class);
  7. startService(innerIntent);
  8. startForeground(GRAY_SERVICE_ID, new Notification());
  9. }
  10.  
  11. //Send a wake-up broadcast to restart the hung UI process
  12. AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
  13. Intent alarmIntent = new Intent();
  14. alarmIntent.setAction(WakeReceiver.GRAY_WAKE_ACTION);
  15. PendingIntent operation = PendingIntent.getBroadcast(this,
  16. WAKE_REQUEST_CODE, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);
  17. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  18. alarmManager.setWindow(AlarmManager.RTC_WAKEUP,
  19. System.currentTimeMillis(), ALARM_INTERVAL, operation);
  20. } else {
  21. alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP,
  22. System.currentTimeMillis(), ALARM_INTERVAL, operation);
  23. }
  24. }
  25.  
  26. /**
  27. * Gray keep-alive method for platforms with API >= 18
  28. */
  29. public   static class DaemonInnerService extends Service {
  30.  
  31. @Override
  32. public void onCreate() {
  33. Log.i(LOG_TAG, "InnerService -> onCreate" );
  34. super.onCreate();
  35. }
  36.  
  37. @Override
  38. public   int onStartCommand(Intent intent, int flags, int startId) {
  39. Log.i(LOG_TAG, "InnerService -> onStartCommand" );
  40. startForeground(GRAY_SERVICE_ID, new Notification());
  41. //stopForeground( true );
  42. stopSelf();
  43. return super.onStartCommand(intent, flags, startId);
  44. }
  45.  
  46. @Override
  47. public IBinder onBind(Intent intent) {
  48. throw new UnsupportedOperationException( "Not yet implemented" );
  49. }
  50.  
  51. @Override
  52. public void onDestroy() {
  53. Log.i(LOG_TAG, "InnerService -> onDestroy" );
  54. super.onDestroy();
  55. }
  56. }

The above operation can improve the survival of the alarm service as much as possible. However, on mobile phones with version 5.0 or higher, the alarm service will be completely killed when the system's built-in Clean function is used. In order to solve the problem above version 5.0, the new feature JobScheduler of version 5.0 or higher is introduced here.

JobScheduler 5.0 and above

You can read this article first about the new JobScheduler API in 5.0.

Here, we use JobScheduler 5.0 or higher to create a scheduled task, which periodically checks whether the alarm service exists. If it does not exist, we restart the alarm service. (Here, I set the alarm service to be checked once every minute)

When entering the application, check whether the current system is 5.0 or above. If it is, start the JobScheduler service. The code is as follows:

  1. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  2. mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
  3. JobInfo.Builder builder = new JobInfo.Builder(JOB_ID,
  4. new ComponentName(getPackageName(), JobSchedulerService.class.getName()));
  5.  
  6. builder.setPeriodic(60 * 1000); //Run every 60 seconds
  7. builder.setRequiresCharging( true );
  8. builder.setPersisted( true ); //Set whether to re-execute the task after the device restarts
  9. builder.setRequiresDeviceIdle( true );
  10.  
  11. if (mJobScheduler.schedule(builder.build()) <= 0) {
  12. //If something goes wrong
  13. }
  14. }

The builder.setPersisted(true); method is used to check whether to re-execute the task after the device is restarted. It has been tested that the task can be restarted.

The above operation further ensures that the alarm service is restarted after being killed. However, Doze mode is introduced in 6.0 and above. When a mobile phone above 6.0 enters this mode, JobScheduler will stop working.

Doze mode processing above 6.0

In order to allow JobScheduler to work in Doze mode in versions above 6.0, special processing is done here for Doze mode above 6.0 - ignoring battery optimization.

  • Add permissions in Manifest.xml.

<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>

  • When setting the alarm, determine whether the system is 6.0 or above. If so, determine whether to ignore battery optimization. The code to determine whether to ignore battery optimization is as follows:
  1. TargetApi(Build.VERSION_CODES.M)
  2. public   static boolean isIgnoringBatteryOptimizations(Activity activity){
  3. String packageName = activity.getPackageName();
  4. PowerManager pm = (PowerManager) activity
  5. .getSystemService(Context.POWER_SERVICE);
  6. if (pm.isIgnoringBatteryOptimizations(packageName)) {
  7. return   true ;
  8. } else {
  9. return   false ;
  10. }
  11. }

If the battery optimization is not ignored, a reminder dialog box will pop up to prompt the user to ignore the battery optimization operation. The code is as follows:

  1. /**
  2. * For Doze mode above N
  3. *
  4. * @param activity
  5. */
  6. public   static void isIgnoreBatteryOption(Activity activity) {
  7. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  8. try {
  9. Intent intent = new Intent();
  10. String packageName = activity.getPackageName();
  11. PowerManager pm = (PowerManager) activity.getSystemService(Context.POWER_SERVICE);
  12. if (!pm.isIgnoringBatteryOptimizations(packageName)) {
  13. // intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
  14. intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
  15. intent.setData(Uri.parse( "package:" + packageName));
  16. activity.startActivityForResult(intent, REQUEST_IGNORE_BATTERY_CODE);
  17. }
  18. } catch (Exception e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. }

Rewrite the onActivityResult method in the interface to capture the user's selection. For example, the code is as follows:

  1. @Override
  2. protected void onActivityResult( int requestCode, int resultCode, Intent data) {
  3. if (resultCode == RESULT_OK) {
  4. if (requestCode == BatteryUtils.REQUEST_IGNORE_BATTERY_CODE){
  5. //TODO something
  6. }
  7. } else if (resultCode == RESULT_CANCELED){
  8. if (requestCode == BatteryUtils.REQUEST_IGNORE_BATTERY_CODE){
  9. ToastUtils.show(getActivity(), "Please turn on ignoring battery optimization~" );
  10. }
  11. }
  12. }

Replenish

When the application is killed, but the alarm service is not killed, the alarm is set again. This means that the set alarm is not put into the alarm service. So in this case, the set alarm will be invalid. To solve this problem, use AIDL (the alarm service is in another process and needs inter-process communication) to call the reset alarm method of the alarm service to reset the alarm.

Start the alarm service in the onCreat() method of the application, and then bind the alarm service.

  1. private void initAlarmService() {
  2. startService(new Intent(this, DaemonService.class)); //Start the alarm service
  3. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  4. //JobScheduler
  5. ...
  6. }
  7.  
  8. //Bind alarm service
  9. Intent intent = new Intent(this, DaemonService.class);
  10. intent.setAction( "android.intent.action.DaemonService" );
  11. bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
  12. }

In the onDestroy() method, call the reset alarm method of the alarm service. The code is as follows:

  1. @Override
  2. protected void onDestroy() {
  3. super.onDestroy();
  4. try {//Judge whether there is an alarm, if not, turn off the alarm service
  5. String alarm = localPreferencesHelper.getString(LocalPreferencesHelper.ALARM_CLOCK);
  6. if (daemonService != -1 && mIRemoteService != null ) {
  7. // android.os.Process.killProcess(daemonService);
  8. mIRemoteService.resetAlarm();
  9. }
  10.          
  11. if (!alarm.equals( "[]" )) {
  12. if (daemonService != -1) {
  13. startService(new Intent(this, DaemonService.class));
  14. }
  15. } else {
  16. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  17. mJobScheduler.cancel(JOB_ID);
  18. }
  19.  
  20. }
  21. unbindService(mConnection); //Unbind service.
  22. } catch (Exception e) {
  23.  
  24. }
  25. }

Here is a note, when the service is started and bound, unbindService will not stop the service. For details, please refer to this article.

***

The above does not mean that the alarm clocks of all Android phones can be used. This just tries to ensure that most phones can be used.

<<:  ASUS's unique aesthetics of technology and art

>>:  iTunes cannot verify the server? Try this solution

Recommend

iPhone 6 will have three screen sizes?

It has been rumored that Apple's iPhone 6 wil...

Advanced checklist for private domain operators

What is a private domain operator ? As the name s...

Trans fatty acids: Why are they the "unwelcome guest" in the food world?

We often see words like "zero trans fatty ac...

After missing out on mobile phones, Tencent is unwilling to lose out on TV

Tencent's motivation for making TV Sun Peng, ...

Community operation: How to create a good community atmosphere?

Before starting the article, I would like to shar...

Apple's system version is updated again, from iOS 14.6 to iOS 15.2

At the beginning of the year, Apple released the ...

Asus ZenFone 6 review: large screen, Intel chip, 1,000 yuan

The screen size of ASUS ZenFone 6 is 6.0 inches, w...

4 counter-common sense suggestions for marketing, branding and markets

Just as there are a thousand Hamlets in the eyes ...

Facebook Advertising Tips!

Whether it is Facebook advertising or any other t...

Content operation: content distribution model analysis!

In this article, the author will analyze with you...