Проверка, работает ли приложение Android в фоновом режиме - PullRequest
297 голосов
/ 08 сентября 2010

Под фоном я подразумеваю, что ни одно из действий приложения в настоящее время не видимо пользователю?

Ответы [ 29 ]

384 голосов
/ 03 мая 2011

Существует несколько способов определить, работает ли ваше приложение в фоновом режиме, но только один из них является полностью надежным:

  1. Правильное решение (кредиты идут на Дан , CommonsWare и NeTeInStEiN )
    Отслеживайте видимость своего приложения самостоятельно, используя методы Activity.onPause, Activity.onResume. Сохраните статус "видимости" в каком-то другом классе. Хороший выбор - ваша собственная реализация Application или Service (есть также несколько вариантов этого решения, если вы хотите проверить видимость активности из службы).

    * ** 1023 тысяча двадцать-дв * Пример
    Реализуйте пользовательский класс Application (обратите внимание на статический метод isActivityVisible()):

    public class MyApplication extends Application {
    
      public static boolean isActivityVisible() {
        return activityVisible;
      }  
    
      public static void activityResumed() {
        activityVisible = true;
      }
    
      public static void activityPaused() {
        activityVisible = false;
      }
    
      private static boolean activityVisible;
    }
    

    Зарегистрируйте класс приложения в AndroidManifest.xml:

    <application
        android:name="your.app.package.MyApplication"
        android:icon="@drawable/icon"
        android:label="@string/app_name" >
    

    Добавьте onPause и onResume к каждому Activity в проекте (вы можете создать общего предка для своей деятельности, если хотите, но если ваша деятельность уже расширена с MapActivity / ListActivity и т. д. вам все еще нужно написать следующее от руки):

    @Override
    protected void onResume() {
      super.onResume();
      MyApplication.activityResumed();
    }
    
    @Override
    protected void onPause() {
      super.onPause();
      MyApplication.activityPaused();
    }
    


    Обновление
    ActivityLifecycleCallbacks были добавлены на уровне API 14 (Android 4.0). Вы можете использовать их для отслеживания того, видна ли активность вашего приложения в данный момент для пользователя. Проверьте ответ Cornstalks ниже для деталей.

  2. Неправильный
    Раньше я предлагал следующее решение:

    Вы можете определить текущее переднее / фоновое приложение с помощью ActivityManager.getRunningAppProcesses(), который возвращает список RunningAppProcessInfo записей. Чтобы определить, находится ли ваше приложение на переднем плане, проверьте поле RunningAppProcessInfo.importance на равенство RunningAppProcessInfo.IMPORTANCE_FOREGROUND, а RunningAppProcessInfo.processName равно имени пакета вашего приложения.

    Также, если вы вызовете ActivityManager.getRunningAppProcesses() из потока пользовательского интерфейса приложения, он вернет значение IMPORTANCE_FOREGROUND для вашей задачи, независимо от того, находится ли она на переднем плане или нет. Вызовите его в фоновом потоке (например, через AsyncTask), и он вернет правильные результаты.

    Хотя это решение может работать (и оно действительно работает большую часть времени), я настоятельно рекомендую воздержаться от его использования. И вот почему. Как написала Дайан Хэкборн :

    Эти API-интерфейсы предназначены не для того, чтобы приложения основывали свой поток пользовательского интерфейса, а для того, чтобы показывать пользователю запущенные приложения или диспетчер задач или что-то подобное.

    Да, в памяти хранится список этих вещей. Тем не менее, он отключен в другом процессе, управляемом потоками, работающими отдельно от вас, и вы не можете рассчитывать на то, что (a) увидите вовремя для принятия правильного решения или (b) получите непротиворечивую картину к тому времени, когда вы вернетесь. Кроме того, решение о том, к какому «следующему» действию перейти, всегда принимается в момент, когда должно произойти переключение, и только в той точной точке (где состояние действия кратко блокируется для выполнения переключения), что мы на самом деле точно знаю, что будет дальше.

    И реализация, и глобальное поведение здесь не гарантируются в будущем.

    Хотелось бы, чтобы я прочитал это до того, как опубликовал ответ на SO, но, надеюсь, еще не поздно признать мою ошибку.

  3. Еще одно неправильное решение
    Библиотека Droid-Fu , упомянутая в одном из ответов, использует ActivityManager.getRunningTasks для своего метода isApplicationBroughtToBackground. См. Комментарий Дайанны выше и не используйте этот метод.

256 голосов
/ 11 декабря 2012

НЕ ИСПОЛЬЗУЙТЕ ЭТО ОТВЕТ

user1269737 ответ является правильным (одобренным Google / Android) способом сделать это . Прочитайте их ответ и дайте им + 1.

Я оставлю свой оригинальный ответ здесь ради потомков. Это было лучшее из доступных в 2012 году, но теперь Android имеет соответствующую поддержку.

Оригинальный ответ

Ключ использует ActivityLifecycleCallbacks (обратите внимание, что для этого требуется Android API уровня 14 (Android 4.0)). Просто проверьте, равно ли количество остановленных действий количеству запущенных действий. Если они равны, ваша заявка в фоновом режиме. Если есть еще запущенные действия, ваше приложение все еще отображается. Если действия возобновлены, а не приостановлены, приложение не только отображается, но и на переднем плане. Существуют 3 основных состояния, в которых ваша деятельность может находиться, затем: видимая и на переднем плане, видимая, но не на переднем плане, и не видимая и не на переднем плане (т.е. на заднем плане).

Действительно приятная вещь в этом методе состоит в том, что у него нет асинхронных проблем getRunningTasks(), но вам также не нужно изменять каждый Activity в вашем приложении для установки / сброса чего-либо в onResumed() / onPaused(). Это всего лишь несколько строк кода, которые самодостаточны и работают во всем приложении. Плюс, никаких фанки-разрешений тоже не требуется.

MyLifecycleHandler.java:

public class MyLifecycleHandler implements ActivityLifecycleCallbacks {
    // I use four separate variables here. You can, of course, just use two and
    // increment/decrement them instead of using four and incrementing them all.
    private int resumed;
    private int paused;
    private int started;
    private int stopped;

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    }

    @Override
    public void onActivityDestroyed(Activity activity) {
    }

    @Override
    public void onActivityResumed(Activity activity) {
        ++resumed;
    }

    @Override
    public void onActivityPaused(Activity activity) {
        ++paused;
        android.util.Log.w("test", "application is in foreground: " + (resumed > paused));
    }

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

    @Override
    public void onActivityStarted(Activity activity) {
        ++started;
    }

    @Override
    public void onActivityStopped(Activity activity) {
        ++stopped;
        android.util.Log.w("test", "application is visible: " + (started > stopped));
    }

    // If you want a static function you can use to check if your application is
    // foreground/background, you can use the following:
    /*
    // Replace the four variables above with these four
    private static int resumed;
    private static int paused;
    private static int started;
    private static int stopped;

    // And these two public static functions
    public static boolean isApplicationVisible() {
        return started > stopped;
    }

    public static boolean isApplicationInForeground() {
        return resumed > paused;
    }
    */
}

MyApplication.java:

// Don't forget to add it to your manifest by doing
// <application android:name="your.package.MyApplication" ...
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        // Simply add the handler, and that's it! No need to add any code
        // to every activity. Everything is contained in MyLifecycleHandler
        // with just a few lines of code. Now *that's* nice.
        registerActivityLifecycleCallbacks(new MyLifecycleHandler());
    }
}

@ Mewzer задал несколько хороших вопросов об этом методе, на которые я хотел бы ответить в этом ответе для всех:

onStop() не вызывается в ситуациях нехватки памяти; это проблема здесь?

Нет. Документы для onStop() говорят:

Обратите внимание, что этот метод никогда не может быть вызван в ситуациях нехватки памяти, когда системе не хватает памяти для поддержания процесса вашей активности после вызова метода onPause ().

Ключевым моментом здесь является то, чтобы «поддерживать процесс вашей активности в рабочем состоянии ...». Если эта ситуация с нехваткой памяти когда-либо будет достигнута, ваш процесс фактически будет уничтожен (а не только ваша деятельность). Это означает, что этот метод проверки на фоновую целостность все еще действителен, потому что а) вы все равно не можете проверить фоновую обработку, если ваш процесс убит, и б) если ваш процесс запускается снова (потому что создается новое действие), член переменные (статические или нет) для MyLifecycleHandler будут сброшены на 0.

Это работает для изменений конфигурации?

По умолчанию нет. Вы должны явно установить configChanges=orientation|screensize (| с чем угодно) в файле манифеста и обработать изменения конфигурации, иначе ваша деятельность будет уничтожена и воссоздана. Если вы не установите это, методы вашей деятельности будут вызываться в следующем порядке: onCreate -> onStart -> onResume -> (now rotate) -> onPause -> onStop -> onDestroy -> onCreate -> onStart -> onResume. Как видите, перекрытия нет (как правило, при переключении между двумя действиями очень кратко перекрываются два действия, как работает этот метод обнаружения фона). Чтобы обойти это, вы должны установить configChanges, чтобы ваша деятельность не была разрушена. К счастью, мне пришлось установить configChanges уже во всех моих проектах, потому что было нежелательно, чтобы вся моя деятельность разрушалась при повороте / изменении размера экрана, поэтому я никогда не считал это проблематичным. (спасибо dpimka за то, что освежил мою память об этом и исправил меня!)

Одна нота:

Когда я сказал «фон» в этом ответе, я имел в виду «ваше приложение больше не видно». Действия Android могут быть видны, но не на переднем плане (например, если есть прозрачное наложение уведомлений). Вот почему я обновил этот ответ, чтобы отразить это.

Важно знать, что у Android есть странный неопределенный момент при переключении действий, когда ничего не находится на переднем плане .По этой причине, если вы проверяете, находится ли ваше приложение на переднем плане при переключении между действиями (в том же приложении), вам будет сказано, что вы не на переднем плане (даже если ваше приложение все еще является активным приложением и отображается).

Вы можете проверить, находится ли ваше приложение на переднем плане в вашем Activity onPause() методе после super.onPause().Просто запомните странное состояние неопределенности, о котором я только что говорил.

Вы можете проверить, является ли ваше приложение видимым (то есть, если оно не на заднем плане) в вашем Activity onStop() методе после super.onStop().

94 голосов
/ 13 февраля 2018

GOOGLE SOLUTION - не хак, как предыдущие решения. Использовать ProcessLifecycleOwner

class ArchLifecycleApp : Application(), LifecycleObserver {

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

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onAppBackgrounded() {
        //App in background
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onAppForegrounded() {
        // App in foreground
    }

}

в app.gradle

dependencies {
    ...
    implementation "android.arch.lifecycle:extensions:1.1.0"

    //New Android X dependency is this - 
    implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"

}

allprojects {
    repositories {
        ...
        google()
        jcenter()
        maven { url 'https://maven.google.com' }
    }
}

Подробнее о компонентах архитектуры, связанных с жизненным циклом, можно прочитать здесь - https://developer.android.com/topic/libraries/architecture/lifecycle

17 голосов
/ 10 апреля 2012

Ответ Идолона подвержен ошибкам и гораздо более сложен, хотя и повторяется здесь проверить, находится ли приложение Android на переднем плане или нет? и здесь Определение текущего приложения на переднем плане из фоновой задачи или службы

Существует гораздо более простой подход:

На BaseActivity, которое распространяется на все виды деятельности:

protected static boolean isVisible = false;

 @Override
 public void onResume()
 {
     super.onResume();
     setVisible(true);
 }


 @Override
 public void onPause()
 {
     super.onPause();
     setVisible(false);
 }

Всякий раз, когда вам нужно проверить , если какое-либо из ваших действий приложения находится на переднем плане, просто отметьте isVisible();

Чтобы понять этот подход, проверьте ответ о жизненном цикле параллельной деятельности: Жизненный цикл параллельной деятельности

13 голосов
/ 20 сентября 2016

Начиная с Android API 16, есть простой способ проверить, находится ли приложение на переднем плане.Это может быть небезопасно, но никакие методы на Android не являются надежными.Этот метод достаточно хорош для использования, когда ваша служба получает обновление от сервера и должна решить, показывать уведомление или нет (потому что если пользовательский интерфейс находится на переднем плане, пользователь заметит обновление без уведомления).

12 голосов
/ 06 октября 2018

Начиная с версии 26 библиотеки поддержки, вы можете использовать ProcessLifecycleOwner , просто добавьте его в свою зависимость, как описано здесь , например:

dependencies {
    def lifecycle_version = "1.1.1"

    // ViewModel and LiveData
    implementation "android.arch.lifecycle:extensions:$lifecycle_version"
    // alternatively - Lifecycles only (no ViewModel or LiveData).
    //     Support library depends on this lightweight import
    implementation "android.arch.lifecycle:runtime:$lifecycle_version"
    annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version" // use kapt for Kotlin
}

А затем просто запрашивайте ProcessLifecycleOwner всякий раз, когда вы хотите узнать состояние приложения, примеры:

//Check if app is in background
ProcessLifecycleOwner.get().getLifecycle().getCurrentState() == Lifecycle.State.CREATED;

//Check if app is in foreground
ProcessLifecycleOwner.get().getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED);
11 голосов
/ 27 мая 2015

Я попробовал рекомендованное решение, которое использует Application.ActivityLifecycleCallbacks и многие другие, но они не сработали, как ожидалось. Благодаря Sarge я нашел довольно простое и понятное решение, которое я описываю ниже.

Ключом решения является тот факт, что если у нас есть ActivityA и ActivityB и мы вызываем ActivityB из ActivityA (а не вызываем ActivityA.finish), то onStart() ActivityB будет называться до ActivityA onStop().

Это также основное различие между onStop() и onPause(), которое никто не упоминал в статьях, которые я читал.

Таким образом, основываясь на поведении этого жизненного цикла, вы можете просто посчитать, сколько раз onStart() и onPause() вызывались в вашей программе. Обратите внимание, что для каждой Activity вашей программы, вы должны переопределить onStart() и onStop(), чтобы увеличить / уменьшить статическую переменную, используемую для подсчета. Ниже приведен код, реализующий эту логику. Обратите внимание, что я использую класс, который расширяет Application, поэтому не забудьте объявить Manifest.xml внутри тега Application: android:name=".Utilities", хотя его можно реализовать и с помощью простого пользовательского класса.

public class Utilities extends Application
{
    private static int stateCounter;

    public void onCreate()
    {
        super.onCreate();
        stateCounter = 0;
    }

    /**
     * @return true if application is on background
     * */
    public static boolean isApplicationOnBackground()
    {
        return stateCounter == 0;
    }

    //to be called on each Activity onStart()
    public static void activityStarted()
    {
        stateCounter++;
    }

    //to be called on each Activity onStop()
    public static void activityStopped()
    {
        stateCounter--;
    }
}

Теперь для каждого действия нашей программы мы должны переопределить onStart() и onStop() и увеличить / уменьшить, как показано ниже:

@Override
public void onStart()
{
    super.onStart();
    Utilities.activityStarted();
}

@Override
public void onStop()
{
    Utilities.activityStopped();
    if(Utilities.isApplicationOnBackground())
    {
        //you should want to check here if your application is on background
    }
    super.onStop();
}

При такой логике возможны 2 случая:

  1. stateCounter = 0: Количество остановленных равно количеству запущенных операций, что означает, что приложение работает в фоновом режиме.
  2. stateCounter > 0: Количество запущенных больше, чем количество остановленных, что означает, что приложение работает на переднем плане.

Примечание: stateCounter < 0 будет означать, что больше остановленных действий, чем начатых, что невозможно. Если вы сталкиваетесь с этим случаем, это означает, что вы не увеличиваете / уменьшаете счетчик, как следует.

Вы готовы к работе. Вы должны проверить, находится ли ваше приложение в фоновом режиме внутри onStop().

8 голосов
/ 08 сентября 2010

Нет способа, если вы не отслеживаете это самостоятельно, чтобы определить, являются ли какие-либо из ваших действий видимыми или нет.Возможно, вам следует подумать о том, чтобы задать новый вопрос StackOverflow, объяснив, чего вы пытаетесь добиться от взаимодействия с пользователем, чтобы мы могли предложить вам альтернативные идеи реализации.

5 голосов
/ 31 марта 2016

Вы можете использовать ComponentCallbacks2 , чтобы определить, находится ли приложение в фоновом режиме. Кстати, этот обратный вызов доступен только на уровне API 14 (Ice Cream Sandwich) и выше.

Вы получите вызов по методу:

public abstract void onTrimMemory (int level)

если уровень равен ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN, приложение находится в фоновом режиме.

Вы можете реализовать этот интерфейс для activity, service и т. Д.

public class MainActivity extends AppCompatActivity implements ComponentCallbacks2 {
   @Override
   public void onConfigurationChanged(final Configuration newConfig) {

   }

   @Override
   public void onLowMemory() {

   }

   @Override
   public void onTrimMemory(final int level) {
     if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
        // app is in background
     }
   }
}
4 голосов
/ 23 ноября 2016

Опираясь на ответ @Cornstalks, добавим пару полезных функций.

Дополнительные функции:

  • введен одноэлементный шаблон, поэтому вы можете сделать это в любом месте приложения: AppLifecycleHandler.isApplicationVisible () и AppLifecycleHandler.isApplicationInForeground ()
  • добавлена ​​обработка дублированных событий (см. Комментарии // предпринять некоторые действия по изменению видимости и // предпринять некоторые действия по изменению на переднем плане)

App.java

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        registerActivityLifecycleCallbacks(AppLifecycleHandler.getInstance());
    }
}

AppLifecycleHandler.java

public class AppLifecycleHandler implements Application.ActivityLifecycleCallbacks {
    private int resumed;
    private int started;

    private final String DebugName = "AppLifecycleHandler";

    private boolean isVisible = false;
    private boolean isInForeground = false;

    private static AppLifecycleHandler instance;

    public static AppLifecycleHandler getInstance() {
        if (instance == null) {
            instance = new AppLifecycleHandler();
        }

        return instance;
    }

    private AppLifecycleHandler() {
    }

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    }

    @Override
    public void onActivityDestroyed(Activity activity) {
    }

    @Override
    public void onActivityResumed(Activity activity) {
        ++resumed;
        android.util.Log.w(DebugName, "onActivityResumed -> application is in foreground: " + (resumed > 0) + " (" + activity.getClass() + ")");
        setForeground((resumed > 0));
    }

    @Override
    public void onActivityPaused(Activity activity) {
        --resumed;
        android.util.Log.w(DebugName, "onActivityPaused -> application is in foreground: " + (resumed > 0) + " (" + activity.getClass() + ")");
        setForeground((resumed > 0));
    }

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

    @Override
    public void onActivityStarted(Activity activity) {
        ++started;
        android.util.Log.w(DebugName, "onActivityStarted -> application is visible: " + (started > 0) + " (" + activity.getClass() + ")");
        setVisible((started > 0));
    }

    @Override
    public void onActivityStopped(Activity activity) {
        --started;
        android.util.Log.w(DebugName, "onActivityStopped -> application is visible: " + (started > 0) + " (" + activity.getClass() + ")");
        setVisible((started > 0));
    }

    private void setVisible(boolean visible) {
        if (isVisible == visible) {
            // no change
            return;
        }

        // visibility changed
        isVisible = visible;
        android.util.Log.w(DebugName, "App Visiblility Changed -> application is visible: " + isVisible);

        // take some action on change of visibility
    }

    private void setForeground(boolean inForeground) {
        if (isInForeground == inForeground) {
            // no change
            return;
        }

        // in foreground changed
        isInForeground = inForeground;
        android.util.Log.w(DebugName, "App In Foreground Changed -> application is in foreground: " + isInForeground);

        // take some action on change of in foreground

    }

    public static boolean isApplicationVisible() {
        return AppLifecycleHandler.getInstance().started > 0;
    }

    public static boolean isApplicationInForeground() {
        return AppLifecycleHandler.getInstance().resumed > 0;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...