Skip to content

Instantly share code, notes, and snippets.

@steveliles
Last active January 22, 2024 18:06
Show Gist options
  • Save steveliles/11116937 to your computer and use it in GitHub Desktop.
Save steveliles/11116937 to your computer and use it in GitHub Desktop.
Class for detecting and eventing whether an Android app is currently foreground or background (requires API level 14+)
package com.sjl.util;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Usage:
*
* 1. Get the Foreground Singleton, passing a Context or Application object unless you
* are sure that the Singleton has definitely already been initialised elsewhere.
*
* 2.a) Perform a direct, synchronous check: Foreground.isForeground() / .isBackground()
*
* or
*
* 2.b) Register to be notified (useful in Service or other non-UI components):
*
* Foreground.Listener myListener = new Foreground.Listener(){
* public void onBecameForeground(){
* // ... whatever you want to do
* }
* public void onBecameBackground(){
* // ... whatever you want to do
* }
* }
*
* public void onCreate(){
* super.onCreate();
* Foreground.get(this).addListener(listener);
* }
*
* public void onDestroy(){
* super.onCreate();
* Foreground.get(this).removeListener(listener);
* }
*/
public class Foreground implements Application.ActivityLifecycleCallbacks {
public static final long CHECK_DELAY = 500;
public static final String TAG = Foreground.class.getName();
public interface Listener {
public void onBecameForeground();
public void onBecameBackground();
}
private static Foreground instance;
private boolean foreground = false, paused = true;
private Handler handler = new Handler();
private List<Listener> listeners = new CopyOnWriteArrayList<Listener>();
private Runnable check;
/**
* Its not strictly necessary to use this method - _usually_ invoking
* get with a Context gives us a path to retrieve the Application and
* initialise, but sometimes (e.g. in test harness) the ApplicationContext
* is != the Application, and the docs make no guarantees.
*
* @param application
* @return an initialised Foreground instance
*/
public static Foreground init(Application application){
if (instance == null) {
instance = new Foreground();
application.registerActivityLifecycleCallbacks(instance);
}
return instance;
}
public static Foreground get(Application application){
if (instance == null) {
init(application);
}
return instance;
}
public static Foreground get(Context ctx){
if (instance == null) {
Context appCtx = ctx.getApplicationContext();
if (appCtx instanceof Application) {
init((Application)appCtx);
}
throw new IllegalStateException(
"Foreground is not initialised and " +
"cannot obtain the Application object");
}
return instance;
}
public static Foreground get(){
if (instance == null) {
throw new IllegalStateException(
"Foreground is not initialised - invoke " +
"at least once with parameterised init/get");
}
return instance;
}
public boolean isForeground(){
return foreground;
}
public boolean isBackground(){
return !foreground;
}
public void addListener(Listener listener){
listeners.add(listener);
}
public void removeListener(Listener listener){
listeners.remove(listener);
}
@Override
public void onActivityResumed(Activity activity) {
paused = false;
boolean wasBackground = !foreground;
foreground = true;
if (check != null)
handler.removeCallbacks(check);
if (wasBackground){
Log.i(TAG, "went foreground");
for (Listener l : listeners) {
try {
l.onBecameForeground();
} catch (Exception exc) {
Log.e(TAG, "Listener threw exception!", exc);
}
}
} else {
Log.i(TAG, "still foreground");
}
}
@Override
public void onActivityPaused(Activity activity) {
paused = true;
if (check != null)
handler.removeCallbacks(check);
handler.postDelayed(check = new Runnable(){
@Override
public void run() {
if (foreground && paused) {
foreground = false;
Log.i(TAG, "went background");
for (Listener l : listeners) {
try {
l.onBecameBackground();
} catch (Exception exc) {
Log.e(TAG, "Listener threw exception!", exc);
}
}
} else {
Log.i(TAG, "still foreground");
}
}
}, CHECK_DELAY);
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}
@Override
public void onActivityStarted(Activity activity) {}
@Override
public void onActivityStopped(Activity activity) {}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
@Override
public void onActivityDestroyed(Activity activity) {}
}
@azorrozua
Copy link

Really useful. Thank you!

@NunciosChums
Copy link

@Ankita1405

  1. download that file and add your project.
  2. create Application class see here
  3. insert Foreground.init(this); to onCreate() of Application class
  4. call Foreground.get().isForeground() where you want.

@prgomet
Copy link

prgomet commented Jun 7, 2016

You have to change private boolean foreground = false, paused = true; to private boolean foreground = true, paused = false;,
otherwise this wont work first time after you open app and put it to background.

@vladiulianbogdan
Copy link

Good job, mate! Thank you!

@saintjab
Copy link

saintjab commented Nov 7, 2016

Good job. Thanks!

@af-fess
Copy link

af-fess commented Nov 8, 2016

Doesn't work for hybrid applications such: Cordova, Unity, Nativescript, react Native

@Iamgiam
Copy link

Iamgiam commented Feb 1, 2017

Thank you for you hard work. I had a nightmare trying to flag when I went Background to Foreground with crashes on resume with fragments.
This helps massively.

@llew2011
Copy link

good , thanks

@ecramer
Copy link

ecramer commented Mar 16, 2017

🙌

@nucleons
Copy link

Fantastic

@Gilb007
Copy link

Gilb007 commented Mar 31, 2017

thanks a lot

@Kang-heesuk
Copy link

Kang-heesuk commented Apr 7, 2017

@steveliles Thanks a lot! I have a question, If I use this source code in my commercial application then, Is there any license like a MIT, Apache something? Plz, let me know :)

@chihung93
Copy link

chihung93 commented May 12, 2017

I think we have some issues, When I request Permissions on my device, it's onBecameForeground and onBecameBackground

public class App extends Application implements Foreground.Listener {
    @Override
    public void onCreate(){
        super.onCreate();
        //ForeGround & Background
        Foreground.init(this).addListener(this);
    }
    @Override
    public void onBecameForeground() {
        Log.d("Application","onBecameForeground");
    }

    @Override
    public void onBecameBackground() {
        Log.d("Application","onBecameBackground");
    }
}

@rok5ek
Copy link

rok5ek commented Jun 28, 2017

I think you can simplify it like this

public class AppLifecycleListener implements Application.ActivityLifecycleCallbacks {
    private int numStarted;
    private boolean isForeground;

    @Override
    public void onActivityStarted(Activity activity) {
        if (numStarted == 0) {
            isForeground = true;
        }
        numStarted++;
    }

    @Override
    public void onActivityStopped(Activity activity) {
        numStarted--;
        if (numStarted == 0) {
            isForeground = false;
        }
    }
}

@hoangduong95
Copy link

you saved my day

@KernelFrenzy
Copy link

Thanks @j796160836 for the solution, I had no idea why it was falling over :)

@hegazy
Copy link

hegazy commented Jan 12, 2018

Forget that and just use ProcessLifecycleOwner from the Android Architecture Components. It's dead simple to use and it also handles when a permission dialog is displayed

class MyApplication : Application(), LifecycleObserver {

    override fun onCreate() {
        super.onCreate()
        ProcessLifecycleOwner.get().lifecycle.addObserver(this)
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onAppForegrounded() {
        Log.d(TAG, "In foreground")
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onAppBackgrounded() {
        Log.d(TAG, "In background")
    }
}

@CosimoSguanci
Copy link

It actually works. Great job!

@pratheepkanati
Copy link

Thanks lot

@leonlatsch
Copy link

@hegazy Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment