To write this article I was pushed by the task that was set before me in one of the working projects: to implement Push-notifications in the application. Everything seemed simple: you study the documentation, examples and go ahead. In addition, the experience with notifications has already been. But it was not there…
The service, in which the application is implemented under Android, makes quite stringent requirements for the work of Push-notifications. It is necessary within 30-60 seconds to notify the user about some action. If the notification is successful, a request is sent from the user's device to the server with the corresponding status. It is known from the documentation that the GCM service (Google Cloud Messaging) does not guarantee the delivery of PUSH notifications to devices, therefore, as a backdoor option, if this time frame is violated, our service notifies the user via SMS. Since the cost of an SMS message is significantly higher than push-notifications, it is necessary to minimize the flow of SMS messages to client devices.
After reviewing the documentation and screwing up push-notifications, we sent the first application build for the test to several clients and waited. The results were similar to the following:
- With an active Wifi connection, everything works perfectly: notifications are delivered, customers are happy.
- With the active mobile Internet, the fun began.
Some customers wrote that they were experiencing delays in the delivery of push, or received both PUSH and SMS at the same time, which is not practical enough. Others wrote that they did not receive any notification at all, but only SMS. At the third, as well as at us on test devices, everything was ok. Having collected the maximum possible information from dissatisfied customers, they began to understand the problem and brought out the following list of restrictions (this list later turned into a full-fledged FAQ):
')
- Power saving mode enabled (for example, Stamina on Sony devices) affects push notifications;
- the user must have at least 1 active Google account on the device;
- you need to make sure that the latest version of the Google Play Services application is installed on your device;
- check if the notifications for the application are disabled (a check mark on the application page in the phone settings);
- check whether the background operation is limited for the application (the setting is located in the “Data usage” menu);
- the GCM documentation states that notifications are sent only to certain ports, so the settings of the router, firewall, and antivirus should also be taken into account.
Having sent this memo to all customers, we again began to wait for results. And they were again "not very." Began to dig further.
At this stage, the
article written by the guys from Mail.ru really helped. It describes in detail the subtleties of the GCM implementation on the client side, as well as moments in connection with which Push notifications in mobile networks refuse to work. Ultimately, it was decided to keep its connection to the server in conjunction with the GCM.
Before proceeding to the solution, it is worthwhile to highlight a few very important points that allow us to narrow the circle of potentially "non-working" devices:
- The problem occurs only when connected to the mobile Internet;
- According to customers, the problem occurs on the version of android 4 and above.
And so, we proceed to the implementation.
An experienced Android developer will immediately say that at least 2 solutions to the problem: use Service or AlarmManager. We tried both options. Consider the first one.
In order to create a service not killed by the system, which will constantly hang in the background and perform our task, we used the method:
startForeground(int notificationID, Notification notification);
Where
- notificationId - some unique notification identifier, which will be displayed in the status bar and in the departing curtain;
- notification - the notification itself.
In this case, a prerequisite is to display a notification in the status bar. This approach ensures that the service will be given higher priority (since it interacts with the UI part of the system) at the time of low memory on the device and the system will unload it with one of the last. We do not need this notification, so we used the following bike: it is enough to run the second one simultaneously with the first service and use the same value as the
notificationID for both services. Then kill the second service. At the same time, the notification will disappear from the status of the bar, but the functional and priority features of the first service will remain.
Having implemented this approach, we sent the assembly to the test. According to the results, it turned out that the system still unloads the service, and from the logs we saw how significant time gaps occurred when requesting data in the background from our server. Therefore, we started the implementation of the second option - AlarmManager.
AlarmManager is a class that provides work with, roughly speaking, an “alarm clock”. It allows you to specify the time at which the system will send a broadcast notification that will awaken our application and enable it to perform the necessary actions. There are some limitations in the operation of this method, and they need to be processed:
- information about the "alarm clocks" will be erased after the device is rebooted;
- data on the "alarms" will be erased after updating the application.
The first rake we stepped on was the method
setRepeating()
which allows you to set an “alarm” that repeats with a certain interval. Screwing this method, they began to test, and the tests showed the opposite - the “alarm clock” was not repeated. Began to understand what was happening, looked at the documentation. And it was there that found the answer to the question - starting with the 19 API lvl (Kitkat), absolutely all the “alarms” in the system became one-time. Conclusion - always read the documentation.
These rakes were not a reason for frustration, because the solution to the problem is quite simple - to start a one-time “alarm clock” and reset it after triggering. When implementing this approach, we stumbled upon the following rake - it turned out that for different API levels it was necessary to set alarm clocks in different ways, while the documentation did not say anything. But this problem was solved quite simply - by “spear” and “googling”. Below is a sample code that allows you to properly set the "alarm clocks":
private static void setUpAlarm(final Context context, final Intent intent, final int timeInterval) { final AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); final PendingIntent pi = PendingIntent.getBroadcast(context, timeInterval, intent, 0); am.cancel(pi); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { final AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(System.currentTimeMillis() + timeInterval, pi); am.setAlarmClock(alarmClockInfo, pi); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + timeInterval, pi); else am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + timeInterval, pi); }
I want to draw attention to the
AlarmManager.RTC_WAKEUP flag - it is with the help of it that the system will allow our application to “wake up” with the screen inactive when the device is in a locked state.
This approach with “alarms” gave us the desired result - the application in the background correctly polls the server for new data. Now we are finalizing the algorithm. At the moment we are implementing and testing the following optimization, which will allow us to narrow the range of devices and thereby reduce the server load:
- a message sent by GCM to the device contains some unique ID;
- receiving data GET request in the background, check whether there is already an entry with the same ID on the device;
- if there is no such data locally on the device, we remember this ID and the time it was received T1;
- waiting for PUSH with the same ID, when receiving we remember the time T2 and check the difference between T2 and T1;
- if the difference is greater than some time criterion (value), then the device has a problem with the delivery of notifications and for the service to work correctly, you must constantly request data in the background from the server (I advise you to select the criterion based on the problem being solved. 5 minutes);
- This difference should be calculated several times, for example, 5-10 times, only after that it can be concluded that the device actually contains a problem with receiving Push notifications (thus eliminating the situation of a banal disconnection, timeout, etc.);
- It is necessary to run this algorithm periodically (for example, once a week, or after updating the OS on the device).
All good. And smaller similar crutches.
PS
In the testing process, a
tool that gives the opportunity to see information on the sent guns helped a lot. This tool is available to developers for free. I recommend everyone to use it.
Pss
I predict, in the comments for sure there will be questions about the battery consumption. I conducted several tests, leaving a personal phone for the night with the included mobile Internet. The results were around 20-25% of the charge for 8-9 hours. Also, the clients to whom we sent test assemblies did not complain about problems with increasing battery consumption.