Instead of introducing
Many practical tasks require the execution of various background actions, whether it be playing music, exchanging data with the server, or simply tracking the actions of the user in order to steal credit card details from him. Well, if it does not work out, then at least fill it with targeted advertising, using the information received. As everyone knows long ago, in Android such things are made out in the form of a service (Service).
Official documentation states that Android OS stops the service only in case of insufficient memory. However, there are other cases. The user can stop the service himself using the tools provided by the Settings / Apps menu, in the same place he can make a complete stop of the application. But for this he needs to strain and, in general, be aware of his actions and their consequences. Unfortunately, for the destruction of the service he has other opportunities that he can use unconsciously. In particular, if at least one Activity that was visible in history was previously launched in our application, the user will be able to handle the corresponding task with just one finger movement. Paradoxically, along the way, Android will knock out the whole process along with the service.
Personally, this behavior of Android does not seem logical to me. A user often simply cleans Recent Apps from a long-forgotten stuff, it is not at all necessary that he wants to give up the benefits that the running service provides him. However, Google developers thought a little differently. In a different way, in a different way, their right, but in the end, we too must somehow live.
So, the framework of the simplest application for working out methods of struggle.
')
SomeActivity.java:
public class SomeActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i("Test", "Activity: onCreate"); Intent serviceIntent = new Intent(this, MyService.class); startService(serviceIntent); } @Override protected void onDestroy() { super.onDestroy(); Log.i("Test", "Activity: onDestroy"); } }
KamikadzeService.java:
public class KamikadzeService extends Service { @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { Log.i("Test", "Service: onCreate"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i("Test", "Service: onStartCommand"); return START_STICKY; } @Override public void onDestroy() { super.onDestroy(); Log.i("Test", "Service: onDestroy"); } @Override public void onTaskRemoved(Intent rootIntent) { Log.i("Test", "Service: onTaskRemoved"); } }
Everything is elementary here. SomeActivity at creation starts the KamikadzeService service, which, in turn, starts as sticky or sticky. For agents of hostile platforms, I will explain that the service at start gives an indication to the operating system in case of an unexpected termination of the service, restart it at the first opportunity. It does this by returning the START_STICKY from the onStartCommand method. If the service is not sticky, then after the user deletes the task, she will not have a chance of rebirth after death.
The onTaskRemoved method is called by the system just as the user deletes the task. Here it is absolutely necessary to mention the android: stopWithTask service attribute, which can be set in the manifest. As you can guess by its name (or simply by reading the documentation), if android: stopWithTask = ”true”, then the voluntary movement of the user's finger on the desired square in the Recent Tasks List, along with deleting the task, will also stop the service. Since in this case the service will be considered as agreeing to stop, no one will restart automatically - she died, she died.
At the very beginning of my struggle for the relative stability of services, having discovered the presence of this flag, I was naive to assume that the problem will be solved by installing android: stopWithTask = ”false” and the service will no longer die with the task. Alas, reality and dreams had a number of significant differences. Indeed, in this case, the system will not stop the service. She will just beat her without a warning. By the way, by default this attribute is “false”, from which it was already possible to guess that its explicit installation will lead to nothing
For equally ingenious developers, to summarize: the value of the service attribute android: stopWithTask does not affect its chances of remaining alive after the task is deleted by the user, the service is doomed in any case. This attribute only determines which method the service will be called before it is destroyed. If it is “true”, then the onDestroy method will be called for the service (not in all cases, to put it mildly, but more on that later). And if the attribute is “false”, then the last breath of the service, noticeable to the developer, will be the launch of the onTaskRemoved method.
After examining all this and experimenting thoroughly with the above program, we can draw the following conclusion: we cannot avoid the death of the background service when deleting a task. Well, it will not work and will not work, in the end, no one promised an easy life. Since the system can restart our sticky service, let it do it. And we will simply keep her condition from time to time, restoring it when the service is revived from the ashes. Alas, not so simple.
KitKat. No rest for the wicked
Back in the USSR at the end of the 80s, in the framework of the program “How many nights there with Thames Television” showed an advertisement for KitKat chocolate. At that time, no one heard KitKat, but the advertisement was new and viewed it with interest. And I perfectly remembered the slogan, which now and stuck in the name of the section. For it reflects.
As a preface. It was mentioned above that when android: stopWithTask = ”true”, the service stops, that is, before death it receives its soothing onDestroy. So it was before the advent of Android KitKat, with the advent of which everything has changed subtly. When a user deletes a task in this and later versions of Android, the service will move to another world ... without a trace. In the vast majority of cases. Unless of course, do not count the possible onDestroy call from the Activity, which fell under the finger of the user. Obviously, all this makes android: stopWithTask completely useless for our purposes.
But the release of Android KitKat is well remembered by the developers of background services for no reason at all. The fact is that in the original versions of this version there was one entertaining detail, which at one time personally drove me into a state of deep depression. KitKat never restarted sticky services.
And then there were the cries of shower programmers on stackoverflow, a bunch of tickets to Google, fixes, updates, etc. How many of these firmware are still alive on the devices, no one knows. But the fact that they still come across is for sure. Solution to the forehead with an attempt to restart the service
@Override public void onTaskRemoved(Intent rootIntent) { Log.i("Test", "Service: onTaskRemoved"); if (Build.VERSION.SDK_INT == 19) { Intent restartIntent = new Intent(this, getClass()); startService(restartIntent); } }
It does not give anything, since Android will first work off the start, and only then, with a calm conscience, will destroy the service. Here you have to add a crutch in the form of AlarmManager:
@Override public void onTaskRemoved(Intent rootIntent) { Log.i("Test", "Service: onTaskRemoved"); if (Build.VERSION.SDK_INT == 19) { Intent restartIntent = new Intent(this, getClass()); AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE); PendingIntent pi = PendingIntent.getService(this, 1, restartIntent, PendingIntent.FLAG_ONE_SHOT); restartIntent.putExtra(“RESTART” am.setExact(AlarmManager.RTC, System.currentTimeMillis() + 3000, pi); } }
That is, we plan to restart the service manually three seconds after deleting the task. Time taken from the ceiling.
Cutting edge groping intelligence
The one who read this article first remembers my statement about the inevitability of the death of background service when deleting a task. I admit, I manipulated terms here a little. In fact, the service has a way to remain safe and sound, but in the form of foreground service. For example:
public class KamikadzeService extends Service {
When creating the service creates a notification, in our case it is just an application icon. The created notification is transmitted to the startForeground method and - voila - the service becomes almost immortal. Deleting a task will not affect it in any way, and even if there is a shortage of memory, it will stop only as a last resort. Almost the only way to stop it is to press the corresponding buttons in Settings / Apps, which, in general, was required. So why should I have a garden before this? And the point is this very notice that Google has long demanded to bring the service to the fore. It is noticeable to the user, noticeable even if it is created with a transparent icon. And for a number of applications this is not always good. I’m not talking about Trojans and other malicious programs now, their creators are hardly concerned about the problem being described in general, since, by definition, they don’t have to show the user something they can pull. Just showing notifications, not due to a real need, looks, in my opinion, silly. The user also feels this and often it even annoys him, as can be seen from the comments to some applications on Google Play.
But against the notifications, we found methods, though it is no longer a crutch, but rather, a hack. Add another service to the project:
public class HideNotificationService extends Service { @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { Notification.Builder builder = new Notification.Builder(this) .setSmallIcon(R.drawable.ic_launcher); Notification notification; if (Build.VERSION.SDK_INT < 16) notification = builder.getNotification(); else notification = builder.build(); startForeground(777, notification); stopForeground(true); } }
And onCreate in KamikadzeService rewrite so:
public class KamikadzeService extends Service {
The essence of the approach is that the HideNotificationService service, having come out not foreground with the same identifier 777, goes back to the back with the removal of its notification. At the same time, the KamikadzeService notification is also destroyed, but the latter remains in the foreground, and already "at first glance, as if it is not visible." After that, the HideNotificationService service stops working. It should be clarified that the order of launching services, as well as their coming to the forefront, does not matter here, the main thing is to ensure that stopForeground second (HideNotificationService) is called later than startForeground first (KamikadzeService). And the equality of the identifiers transmitted in startForeground is necessary.
And here again a reasonable question arises - if all this works fine, why did I chew about the habits of “purely” background services earlier for a long time and tediously? Yes, because the described technique is hack and hack is dirty enough to be used for a long time. Although in the emulator with the recently arrived Android 6.0 it still works. Hope or not hope - the reader decides.