Tuesday, February 25, 2014

Android: Solution to detect when an Android app goes to the background and come back to the foreground Using Application.ActivityLifecycleCallbacks and ComponentCallbacks2

We don’t have any direct approach to find whether our application gone to background but we have few interfaces from android which helps to solve this.

The following interfaces which help us are:
  1. Application.ActivityLifecycleCallbacks
  2. ComponentCallbacks2

Both the interfaces are added in API level 14.

Application.ActivityLifecycleCallbacks: This interface helps us to trigger the life cycle of all activities. We can use this ActivityLifecycleCallbacks interface in the Custom Application class, So that we can trigger all the activity life cycles @ one location.

ComponentCallbacks2:  This class extended ComponentCallbacks interface with a new callback for finer-grained memory management. This interface is available in all application components (Activity, Service, ContentProvider, and Application).
The public method onTrimMemory triggers when the operating system has determined that it is a good time for a process to trim unneeded memory from its process.  This will happen for example when it goes in the background and there is not enough memory to keep as many background processes running as desired.

Normally, onTrimMemory will be triggered when ever application goes to background, UI should be released at this point to allow memory to be better managed but we should never compare to exact values of the level, since new intermediate values may be added -- we will typically want to compare if the value is greater or equal to a level you are interested in.

Constants
Return type
Level 
Description
Constant Value
int
Level for onTrimMemory(int): the process has gone on to the LRU list. This is a good opportunity to clean up resources that can efficiently and quickly be re-built if the user returns to the app.
40
int
Level for onTrimMemory(int): the process is nearing the end of the background LRU list, and if more memory isn't found soon it will be killed.
80
int
Level for onTrimMemory(int): the process is around the middle of the background LRU list; freeing memory can help the system keep other processes running later in the list for better overall performance.
60
int
Level for onTrimMemory(int): the process is not an expendable background process, but the device is running extremely low on memory and is about to not be able to keep any background processes running.
15
int
Level for onTrimMemory(int): the process is not an expendable background process, but the device is running low on memory.
10
int
Level for onTrimMemory(int): the process is not an expendable background process, but the device is running moderately low on memory.
5
int
Level for onTrimMemory(int): the process had been showing a user interface, and is no longer doing so. Large allocations with the UI should be released at this point to allow memory to be better managed.
20

By using the above 2 interfaces, we can write our logic to trigger when application goes to background in the custom application class.

Create AppStatusApplication.java class which extends Application.java and two interfaces classes ActivityLifecycleCallbacks, ComponentCallbacks2 as follows

AppStatusApplication.java 

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Application;
import android.app.Application.ActivityLifecycleCallbacks;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;

/**
 * AppStatusApplication helps us to find, whether the application came to
 * foreground or not by using interfaces like ActivityLifecycleCallbacks,
 * ComponentCallbacks2 and Broadcast receiver.
 *
 * @author Vardhan
 *
 */
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public class AppStatusApplication extends Application implements
              ActivityLifecycleCallbacks, ComponentCallbacks2 {

       private static String TAG = AppStatusApplication.class.getName();

       public static String stateOfLifeCycle = "";

       public static boolean wasInBackground = false;

       @Override
       public void onCreate() {
              super.onCreate();
              registerActivityLifecycleCallbacks(this);
...
       }

       @Override
       public void onActivityCreated(Activity activity, Bundle arg1) {
              wasInBackground = false;
              stateOfLifeCycle = "Create";
       }

       @Override
       public void onActivityStarted(Activity activity) {
              stateOfLifeCycle = "Start";
       }

       @Override
       public void onActivityResumed(Activity activity) {
              stateOfLifeCycle = "Resume";
       }

       @Override
       public void onActivityPaused(Activity activity) {
              stateOfLifeCycle = "Pause";
       }

       @Override
       public void onActivityStopped(Activity activity) {
              stateOfLifeCycle = "Stop";
       }

       @Override
       public void onActivitySaveInstanceState(Activity activity, Bundle arg1) {
       }

       @Override
       public void onActivityDestroyed(Activity activity) {
              wasInBackground = false;
              stateOfLifeCycle = "Destroy";
       }

       @Override
       public void onTrimMemory(int level) {
              if (stateOfLifeCycle.equals("Stop")) {
                     wasInBackground = true;
              }
              super.onTrimMemory(level);
       }

       ...
}

Then create two activities to check whether application will trigger when app comes to foreground or not.
Here my two activities are MainActivity.java and NavigatedActivity.java

MainActivity.java

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

/**
 * MainActivity is used to check the life cycle flow and to test the application
 * status.
 *
 * @author Vardhan
 *
 */
public class MainActivity extends Activity {

       private static final String TAG = MainActivity.class.getName();

       @Override
       protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              Log.d(TAG, TAG + " onCreate");
              setContentView(R.layout.main);
       }

       @Override
       protected void onStart() {
              super.onStart();
              Log.d(TAG, TAG + " onStart");

              if (AppStatusApplication.wasInBackground) {
                     Toast.makeText(getApplicationContext(),
                                  "Application came to foreground", Toast.LENGTH_SHORT)
                                  .show();
                     AppStatusApplication.wasInBackground = false;
              }

              ((Button) findViewById(R.id.btn))
                           .setOnClickListener(new OnClickListener() {

                                  @Override
                                  public void onClick(View arg0) {
                                         startActivity(new Intent(getApplicationContext(),
                                                       NavigatedActivity.class));

                                  }
                           });
       }
}
NavigatedActivity.java
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

/**
 * NavigatedActivity is used to check the life cycle flow when we shift between
 * activities.
 *
 * @author Vardhan
 *
 */
public class NavigatedActivity extends Activity {

       private static final String TAG = NavigatedActivity.class.getName();

       @Override
       protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              Log.d(TAG, TAG + " onCreate");
              setContentView(R.layout.navigated_main);
       }

       @Override
       protected void onStart() {
              super.onStart();
              Log.d(TAG, TAG + " onStart");
              if (AppStatusApplication.wasInBackground) {
                     Toast.makeText(getApplicationContext(),
                                  "Application came to foreground", Toast.LENGTH_SHORT)
                                  .show();
                     AppStatusApplication.wasInBackground = false;
              }
       }
}

By using this three classes we can trigger whether application come to foreground or not.
If User wants to trigger @ screen switch off then we can use broadcast receiver in the custom application.

// Initiate
ScreenOffReceiver screenOffReceiver = new ScreenOffReceiver();

// declare in onCreate class of AppStatusApplication.java
registerReceiver(screenOffReceiver, new IntentFilter(
                           "android.intent.action.SCREEN_OFF"));

// Inner Class
class ScreenOffReceiver extends BroadcastReceiver {

       @Override
       public void onReceive(Context context, Intent intent) {
              wasInBackground = true;
       }
}

Screen Shot:

Source Code
You can download the source code by clicking here: AppStatusUsingActivityCallBacksICS-SourceCode.  This project is built using eclipse IDE. Unzip and import the project into Eclipse, it’s a good idea to use the Project by clean and rebuild from the project menu. It works in all API levels above API 14.

Have something to add to this post? Share it in the comments.