Android manages the lifetime and data of sandboxed applications without “asking” the developer. Therefore some applications that should always be running in the background might suffer from lack of control. Users also often try to manipulate programs, in order to manually close them or wipe their data. While developing applications for this platform, I learned a couple of ways to improve this situation.

Apps running in the background

The well known and documented part of the solution is to create a foreground service and return START_STICKY to allow running the service continuously. This service is still vulnerable to various actions that the system or user might take to stop it and clean the data that belongs to this app.

Requesting the application as the device administrator also allows to defend such program from being closed or uninstalled. Unfortunately Android 2.3 and older don’t protect the “Administrator application” from being forcedly closed or wiped in the application manager. Before I explain how this can be avoided, I’d like to clear up some misconceptions about apps running in the background on Android devices.

Is this app still running in the background?

Typical Android users can see the recent applications on the launcher screen as the apps currently running in the background. That’s true, although these applications might not be using any active device resources. They might be simply “frozen” in the last state in which they were left, and even if Android decides to close them (i.e. because of the memory limitations), they will resume in the same state (assuming developers did the right job).

Common perception is that swiping these applications out of the screen terminates them. Once started again, they will begin at the standard “entry point” of the application. So have they been removed? Visually, yes. Some say “out of sight, out of mind”. But the truth is that programs might have components that run in the background silently.

Those who navigate through the menus more will probably find the “Running” tab in the Apps menu in the Settings. These are the processes that can be stopped (at least Android UI gives that illusion). But this can be like an uphill battle because many applications being killed this way often re-appear in the “running” section (like a zombie)! The Google Location Service is a prime example of this behaviour.

There are some power-users who search for 3rd-party applications to help them monitor and manage tasks currently running in the background, some of them periodically wiping the list of the running apps. They’re not very effective though, when faced with more “stubborn” applications.

Working around the task killer

Continuous execution of a service can be assured with a couple of programming tricks. Setting the recurring delayed events from the service allows to restart if it suddenly crashed. This can be achieved with System alarms. The best option here is to create the receiver for the time tick event.

<receiver android:name="com.example.app.receiver.TickReceiver"> <intent-filter> <action android:name="com.example.app.receiver.TIME_TICK"/> </intent-filter> </receiver> 1 2 3 4 5 <receiver android : name = "com.example.app.receiver.TickReceiver" > <intent-filter> <action android : name = "com.example.app.receiver.TIME_TICK" /> </intent-filter> </receiver>

onCreate() // In service's onCreate() add the TIME_TICK broadcast for safety Intent intent = new Intent(TickReceiver.TIME_TICK); PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE); long now = System.currentTimeMillis(); long interval = 10 * 1000; // 10 seconds manager.setRepeating(AlarmManager.RTC_WAKEUP, now + interval, interval, pendingIntent); // Schedule recurring timer 1 2 3 4 5 6 7 8 // In service's onCreate() add the TIME_TICK broadcast for safety Intent intent = new Intent ( TickReceiver . TIME_TICK ) ; PendingIntent pendingIntent = PendingIntent . getBroadcast ( this , 0 , intent , 0 ) ; AlarmManager manager = ( AlarmManager ) getSystemService ( ALARM_SERVICE ) ; long now = System . currentTimeMillis ( ) ; long interval = 10 * 1000 ; // 10 seconds manager . setRepeating ( AlarmManager . RTC_WAKEUP , now + interval , interval , pendingIntent ) ; // Schedule recurring timer

This doesn’t work when programs are killed from the “running processes” tab in the Application Manager. Alarms are removed when the process is stopped from the “running processes” tab, but I have discovered that this doesn’t apply to all type of events. Creating broadcast alarms allows to restart the service stopped this way.

onDestroy() // in service's OnDestroy() Intent i = new Intent(TickReceiver.TIME_TICK); sendBroadcast(i); // restart service if it wasn't explicitly disabled by the user if (MyService.isActivated()) { Intent i2 = new Intent(this, MyService.class); i2.setAction(MY_SERVICE); startService(i2); } 1 2 3 4 5 6 7 8 9 10 // in service's OnDestroy() Intent i = new Intent ( TickReceiver . TIME_TICK ) ; sendBroadcast ( i ) ; // restart service if it wasn't explicitly disabled by the user if ( MyService . isActivated ( ) ) { Intent i2 = new Intent ( this , MyService . class ) ; i2 . setAction ( MY_SERVICE ) ; startService ( i2 ) ; }

Can I really, really close that app now?!

Not many Android users are actually aware that there is a better way to terminate the application running in the background, by actually going into this application’s details and clicking on the “Force stop” button. This is one of the most effective ways to stop application from waking up in the background again.

Application data

Each application installed in the Android device has its own storage space, usually in the “/data/data/com.example.app” folder that is secure and private to this application (unless developers decide to make these files MODE_WORLD_READABLE which is highly discouraged).

When the “Clear data” button is pressed in the application details, this private data is completely wiped, together with preferences, cache and databases which might have been stored there. This usually leaves the application in the “factory” state, like if it was deleted and installed again. While this might be a useful function, it doesn’t have to be good to all users to expose the “wipe” functionality to everyone who can accidentally access the device.

One of the ways to prevent this from happening is to set the application as the Device Administrator, but this might be unreasonable if the application’s purpose is not clearly to take over the phone or if the brand behind the application doesn’t create enough trust. And even with Device Administrator active on Android Froyo and Gingerbread application is vulnerable to wiping its data.

There is a way to secure the “clean data” with password. This can be achieved by assigning the property in the application manifest.

android:manageSpaceActivity="com.example.app.ManageDataActivity" 1 android : manageSpaceActivity = "com.example.app.ManageDataActivity"

Then in the activity, we can check credentials or whatever confirmation that’s required and clean the app with the following code:

clearApplicationData() private void clearApplicationData() { // stop the service stopService(new Intent(MyService.MY_SERVICE)); File cache = getCacheDir(); File appDir = new File(cache.getParent()); if (appDir.exists()) { String[] children = appDir.list(); for (String s : children) { if (!s.equals("lib")) { deleteDir(new File(appDir, s)); if (GlobalSettings.DEBUG) Log.d(GlobalSettings.TAG, "*** File /data/data/com.example.app/" + s + " DELETED ***"); } } } SharedPreferences.Editor edit = PreferenceManager.getDefaultSharedPreferences(this).edit(); edit.clear().commit(); System.exit(0); } private static boolean deleteDir(File dir) { if (dir != null && dir.isDirectory()) { String[] children = dir.list(); for (int i = 0; i < children.length; i++) { boolean success = deleteDir(new File(dir, children[i])); if (!success) { return false; } } } return dir.delete(); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 private void clearApplicationData ( ) { // stop the service stopService ( new Intent ( MyService . MY_SERVICE ) ) ; File cache = getCacheDir ( ) ; File appDir = new File ( cache . getParent ( ) ) ; if ( appDir . exists ( ) ) { String [ ] children = appDir . list ( ) ; for ( String s : children ) { if ( ! s . equals ( "lib" ) ) { deleteDir ( new File ( appDir , s ) ) ; if ( GlobalSettings . DEBUG ) Log . d ( GlobalSettings . TAG , "*** File /data/data/com.example.app/" + s + " DELETED ***" ) ; } } } SharedPreferences . Editor edit = PreferenceManager . getDefaultSharedPreferences ( this ) . edit ( ) ; edit . clear ( ) . commit ( ) ; System . exit ( 0 ) ; } private static boolean deleteDir ( File dir ) { if ( dir != null && dir . isDirectory ( ) ) { String [ ] children = dir . list ( ) ; for ( int i = 0 ; i < children . length ; i ++ ) { boolean success = deleteDir ( new File ( dir , children [ i ] ) ) ; if ( ! success ) { return false ; } } } return dir . delete ( ) ; }

Wrap-up

Knowing the programming techniques pinpointed above, you can take much more control of the data and application life-cycle in the Android system. All this has been done with the use of legitimate ways and within the limits of the good programming practice.

But even in this sandboxed environment there are ways to make a “zombie app” really hard to eradicate. It is out of the scope of this article, but stay focused and keep reading our blog to get to know more in the next part of this series.

Thanks to Xavi, Hannes and Jordi for the research done in contribution to developing these solutions.

78EBA888-1918-4C8C-B455-3F3BBC7BD051 Created with sketchtool. Liked it?