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.

12 comments :

  1. Hello. Your tutorial was really helpful to me. But I need your help at one instance.
    In the mainActivity's button onClick, I have written the following code

    Intent intent = new Intent(Intent.ACTION_CALL);
    intent.setData(Uri.parse("tel:" + "1234"));
    startActivity(intent);

    Now, the problem is I get an exception: Performing pause of activity that is not resumed (MainActivity).

    I am completely perplexed as to why is this coming.
    It seems that after the onStop, onPause is called which is not true ideally.

    Can you suggest me something onto this end?

    ReplyDelete
    Replies
    1. Hi AnujaKanchan,

      I am not getting the error/ exception you got.
      If possible, provide me the source code you have tested.

      Regards
      Vardhan

      Delete
    2. If you launch a generic intent and there isn't any application which can handle it, the application will crash. Check this article from Google developers:
      http://developer.android.com/guide/components/intents-filters.html#ExampleSend

      Delete
  2. Thank you for sharing this.

    ReplyDelete
  3. The onStop method is called after the on trim method so the checked state of lifecycle in onTrim is never "Stop"

    ReplyDelete
  4. Hello Vardhan, I like your way. But my application need to be compatible from API 8/9 . Can we use it for older version, is there any support library? I have found https://github.com/BoD/android-activitylifecyclecallbacks-compat. Did you tried it?

    ReplyDelete
    Replies
    1. There is no direct approach for below version...Only way is having common super class and achieving...You can check this post too http://vardhan-justlikethat.blogspot.in/2013/05/android-solution-to-detect-when-android.html

      Delete
  5. Why are we not using TRIM_MEMORY_UI_HIDDEN ? why do we have to keep track of life cycle . As of the previous commenters mentioned we cannot assure on onTrimMemory being called after onStop . Wanted to know if there is any downside in using TRIM_MEMORY_UI_HIDDEN

    ReplyDelete
  6. Vardhan I have successfully implemented your code but I want to avoid onBackPressed which too may call activity form background and I just want to fire event when the app actually goes to background by pressing home button or opening of other apps... Any help will save my life

    ReplyDelete
    Replies
    1. you can directly override onbackpressed to handle the case

      Delete
  7. I'm new in android May i know,What are the difference between these and How it use in android code. OnTrimmemory(),System.gc()Finalize().

    ReplyDelete
  8. if (stateOfLifeCycle.equals("Stop")) {
    wasInBackground = true;
    }
    super.onTrimMemory(level);
    Log.d(TAG, "onTrimMemory " + level);

    this don't works for me.

    onActivityPaused MainActivity always before onTrimMemory

    ReplyDelete