Как узнать, работает ли приложение Android на переднем плане?


83

Я делаю уведомление в строке состояния в своем приложении для Android, которое запускается c2dm. Я не хочу отображать уведомление, если приложение запущено. Как определить, запущено ли приложение и находится ли оно на переднем плане?



Это аналогичный вопрос ... хотя я пробовал использовать флаг onStart / onStop, и он не работал. Я все еще не понимаю разницы между остановкой / запуском и паузой / возобновлением.
— Эндрю Томас

1
Вам следует использовать это решение: stackoverflow.com/questions/3667022/…
— Informatic0re

Начиная с API 16, существует более простой способ использования ActivityManager.getMyMemoryState
— Юозас Контвайнис

Начиная с версии 26 библиотеки поддержки, вам просто нужно запрашивать ProcessLifecycleOwner в любое время. Проверьте stackoverflow.com/a/52678290/6600000
— Кейван Эсбати,

Ответы:


55

Создайте глобальную переменную, например, private boolean mIsInForegroundMode;и присвойте falseзначение в onPause()и trueзначение в onResume().

Образец кода:

private boolean mIsInForegroundMode;

@Override
protected void onPause() {
    super.onPause();
    mIsInForegroundMode = false;
}

@Override
protected void onResume() {
    super.onResume();
    mIsInForegroundMode = true;
}

// Some function.
public boolean isInForeground() {
    return mIsInForegroundMode;
}

2
@MurVotema: Да, это так. Но мы можем свободно передавать эту переменную, например, в настройки или базу данных всякий раз, когда происходит изменение.
— Wroclai

19
@Shelly Но ваша переменная будет часто становиться True / False / True при переключении действий в одном приложении. Это означает, что у вас должен быть гистерезис, чтобы действительно определить, когда ваше приложение теряет передний план.
— Radu

10
Это не лучшее решение. Проверьте решение @Gadenkan ниже.
— Фелипе Лима

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

1
@Tima Если вы хотите сделать его доступным для всех действий, вы можете создать новый MyOwnActivity extends AppCompatActivity ... Затем внутри MyOwnActivity переопределите как onResume () / onPause (), и, наконец, вы расширяете все действия вашего приложения из MyOwnActivity вместо AppCompatActivity. Задача решена. ; )
— elliotching

112

Кроме того, вы можете проверить, ActivityManagerкакие задачи выполняются по getRunningTasksметоду. Затем проверьте первую задачу (задача на переднем плане) в возвращенном Списке задач, если это ваша задача.
Вот пример кода:

public Notification buildNotification(String arg0, Map<String, String> arg1) {

    ActivityManager activityManager = (ActivityManager) appContext.getSystemService(Context.ACTIVITY_SERVICE);
    List<RunningTaskInfo> services = activityManager
            .getRunningTasks(Integer.MAX_VALUE);
    boolean isActivityFound = false;

    if (services.get(0).topActivity.getPackageName().toString()
            .equalsIgnoreCase(appContext.getPackageName().toString())) {
        isActivityFound = true;
    }

    if (isActivityFound) {
        return null;
    } else {
        // write your code to build a notification.
        // return the notification you built here
    }

}

И не забудьте добавить GET_TASKSразрешение в файл manifest.xml , чтобы иметь возможность запускать getRunningTasks()метод в приведенном выше коде:

<uses-permission android:name="android.permission.GET_TASKS" />

p / s: Если вы согласны с этим, обратите внимание, что это разрешение теперь устарело.


24
Если вы хотите использовать этот код, не забудьте добавить <uses-permission android: name = "android.permission.GET_TASKS" />
— Лакшманан

13
Nitpicks: вызов toString()строки, возвращаемой функцией , getPackageName()является избыточным. Кроме того, поскольку нас интересует только первая задача, которую возвращает getRunningTasks(), мы можем передать 1вместо Integer.MAX_VALUE.
— Jonik

14
Примечание: этот метод предназначен только для отладки и представления пользовательских интерфейсов управления задачами. Это никогда не должно использоваться для базовой логики в приложении, такой как выбор между различными поведениями на основе информации, найденной здесь. Такое использование не поддерживается и, скорее всего, выйдет из строя в будущем. Например, если несколько приложений могут активно работать одновременно, предположения, сделанные о значении данных здесь для целей потока управления, будут неверными.
— Informatic0re

5
К сожалению, функция getRunningTasks () устарела с Android L (API 20). Что касается L, этот метод больше не доступен для сторонних приложений: введение ориентированных на документы последних означает, что он может передавать информацию о личности вызывающему. Для обратной совместимости он по-прежнему будет возвращать небольшое подмножество своих данных: по крайней мере, собственные задачи вызывающего абонента и, возможно, некоторые другие задачи, такие как home, которые, как известно, не являются конфиденциальными.
— Сэм Лу

2
Предположительно, если он показывает только задачи вызывающего абонента, тогда первая задача и основная активность всегда будут вашими, что означает, что это всегда будет возвращать истинный пост-леденец.
— Майк Холлер

46

Это довольно старый пост, но все еще актуальный. Принятое выше решение может работать, но неверно. Как писала Дайан Хакборн:

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

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

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

Правильное решение - реализовать: ActivityLifeCycleCallbacks .

Для этого в основном нужен класс приложения, и там можно установить обработчик для определения состояния ваших действий в приложении.


7
Вот пример того, как это использовать baroqueworksdev.blogspot.com/2012/12/…
— Калоян Русев

2
Я думаю, что это лучшее решение и, вероятно, не сломается в будущем. Интересно, почему не набралось достаточно голосов.
— Рохан Кандвал,

2
Если вы напишете пример кода, вы получите больше голосов, потому что некоторые люди пропускают ответы без примера ... но это лучшее решение ...
— Саман Салехи,

Чем это отличается от метода переопределения onPauseи onResumeметода, использованного в принятом ответе?
— Сайтама

24

Как говорит Виней, вероятно, лучшим решением (для поддержки новых версий Android, 14+) является использование ActivityLifecycleCallbacksв Applicationреализации класса.

package com.telcel.contenedor.appdelegate;

import android.app.Activity;
import android.app.Application.ActivityLifecycleCallbacks;
import android.os.Bundle;

/** Determines global app lifecycle states. 
 * 
 * The following is the reference of activities states:
 * 
 * The <b>visible</b> lifetime of an activity happens between a call to onStart()
 * until a corresponding call to onStop(). During this time the user can see the
 * activity on-screen, though it may not be in the foreground and interacting with 
 * the user. The onStart() and onStop() methods can be called multiple times, as 
 * the activity becomes visible and hidden to the user.
 * 
 * The <b>foreground</b> lifetime of an activity happens between a call to onResume()
 * until a corresponding call to onPause(). During this time the activity is in front
 * of all other activities and interacting with the user. An activity can frequently
 * go between the resumed and paused states -- for example when the device goes to
 * sleep, when an activity result is delivered, when a new intent is delivered -- 
 * so the code in these methods should be fairly lightweight. 
 * 
 * */
public class ApplicationLifecycleManager implements ActivityLifecycleCallbacks {

    /** Manages the state of opened vs closed activities, should be 0 or 1. 
     * It will be 2 if this value is checked between activity B onStart() and
     * activity A onStop().
     * It could be greater if the top activities are not fullscreen or have
     * transparent backgrounds.
     */
    private static int visibleActivityCount = 0;

    /** Manages the state of opened vs closed activities, should be 0 or 1
     * because only one can be in foreground at a time. It will be 2 if this 
     * value is checked between activity B onResume() and activity A onPause().
     */
    private static int foregroundActivityCount = 0;

    /** Returns true if app has foreground */
    public static boolean isAppInForeground(){
        return foregroundActivityCount > 0;
    }

    /** Returns true if any activity of app is visible (or device is sleep when
     * an activity was visible) */
    public static boolean isAppVisible(){
        return visibleActivityCount > 0;
    }

    public void onActivityCreated(Activity activity, Bundle bundle) {
    }

    public void onActivityDestroyed(Activity activity) {
    }

    public void onActivityResumed(Activity activity) {
        foregroundActivityCount ++;
    }

    public void onActivityPaused(Activity activity) {
        foregroundActivityCount --;
    }


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

    public void onActivityStarted(Activity activity) {
        visibleActivityCount ++;
    }

    public void onActivityStopped(Activity activity) {
        visibleActivityCount --;
    }
}

И в onCreate()методе применения :

registerActivityLifecycleCallbacks(new ApplicationLifecycleManager());

Тогда ApplicationLifecycleManager.isAppVisible()или ApplicationLifecycleManager.isAppInForeground()будет использоваться, чтобы узнать желаемое состояние.


Он будет работать только с isAppVisible (). Если вы закроете приложение, отправьте его на передний план, это не будет иметь никакого эффекта
— AlwaysConfused

19

Начиная с API 16, это можно сделать так:

static boolean shouldShowNotification(Context context) {
    RunningAppProcessInfo myProcess = new RunningAppProcessInfo();
    ActivityManager.getMyMemoryState(myProcess);
    if (myProcess.importance != RunningAppProcessInfo.IMPORTANCE_FOREGROUND)
        return true;

    KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
    // app is in foreground, but if screen is locked show notification anyway
    return km.inKeyguardRestrictedInputMode();
}

Эффективное решение. Спасибо :)
— Md. Sajedul Karim

Очень красивое решение. Разрешения не требуются.
— user3144836 02

Потрясающе! Я использовал список задач, но для этого требуется ПОЛУЧИТЬ ЗАДАЧУ .. очень признателен!
— hexagod 03

15

К вашему сведению, если вы используете решение Gadenkan (что отлично !!), не забудьте добавить

<uses-permission android:name="android.permission.GET_TASKS" />

в манифест.


9
В Android Lollipop это разрешение устарело
— Мусса

14

Немного доработанная версия решения Гаденкана . Поместите в него любое Activity или, возможно, базовый класс для всех ваших Activity.

protected boolean isRunningInForeground() {
    ActivityManager manager = 
         (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    List<ActivityManager.RunningTaskInfo> tasks = manager.getRunningTasks(1);
    if (tasks.isEmpty()) {
        return false;
    }
    String topActivityName = tasks.get(0).topActivity.getPackageName();
    return topActivityName.equalsIgnoreCase(getPackageName());
}

Чтобы иметь возможность звонить getRunningTasks(), вам необходимо добавить это в свой AndroidManifest.xml:

<uses-permission android:name="android.permission.GET_TASKS"/>

Обратите внимание на то, что ActivityManager.getRunningTasks() говорит Javadoc:

Примечание: этот метод предназначен только для отладки и представления пользовательских интерфейсов управления задачами. Это никогда не должно использоваться для базовой логики в приложении, такой как выбор между разными поведениями на основе информации, найденной здесь. Такое использование не поддерживается и, скорее всего, выйдет из строя в будущем.

Обновление (февраль 2015 г.)

Обратите внимание, что getRunningTasks()это устарело на уровне API 21 !

На LOLLIPOPданный момент этот метод больше не доступен для сторонних приложений: внедрение ориентированных на документы последних означает, что он может передавать информацию о личности вызывающему. Для обратной совместимости он по-прежнему будет возвращать небольшое подмножество своих данных: по крайней мере, собственные задачи вызывающего абонента и, возможно, некоторые другие задачи, такие как home, которые, как известно, не являются конфиденциальными.

Так что то, что я написал ранее, даже более актуально:

Во многих случаях вы, вероятно, сможете найти лучшее решение. Например, выполнение чего-либо в onPause()и onResume(), возможно, в BaseActivity для всех ваших действий.

(В нашем случае мы не хотели запускать офлайн-оповещение, если мы не на переднем плане, поэтому в BaseActivity onPause()мы просто отказываемся от подписки на RxJava, Subscriptionпрослушивая сигнал «перешел в автономный режим».)


не могли бы вы опубликовать код RxJava, который вы использовали? Я хочу использовать RxJava, и я не хочу тратить много времени на изучение RxJava в данный момент :(
— MBH

@MBH: Использование RxJava, вероятно, не будет очень плодотворным, если не потратить некоторое время на ознакомление с основными концепциями ... :-) В любом случае, вот небольшой пример .
— Jonik

9

После ответа Гаденкана мне нужно было что-то вроде этого, чтобы я мог определить, работает ли мое приложение на переднем плане, но мне нужно было что-то, что было бы для всего приложения и не требовало от меня установки / снятия флагов во всем моем приложении.

Код Гаденкана в значительной степени попал в точку, но он был не в моем стиле, и мне казалось, что он может быть более аккуратным, поэтому в моем приложении он сократился до этого.

if (!context.getPackageName().equalsIgnoreCase(((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getRunningTasks(1).get(0).topActivity.getPackageName()))
{
// App is not in the foreground
}

(Примечание: вы можете просто удалить!, Если хотите, чтобы проверка работала наоборот)

Хотя при таком подходе нужно GET_TASKSразрешение.


3

Начиная с версии 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делать запросы всякий раз, когда хотите проверить состояние приложения, например, чтобы проверить, запущено ли приложение на переднем плане, вам просто нужно сделать это:

 boolean isAppInForeground = ProcessLifecycleOwner.get().getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED);
 if(!isAppInForeground)
    //Show Notification in status bar

1
Это правильный ответ. Многие другие следует считать устаревшими
— мин.

Для AndroidX используйте Java implementation 'androidx.lifecycle:lifecycle-process:2.2.0'в своем проекте gradle.
— Джонатан

1

Основываясь на различных ответах и ​​комментариях, вот более встроенная версия, которую вы можете добавить во вспомогательный класс:

public static boolean isAppInForeground(Context context) {
  List<RunningTaskInfo> task =
      ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE))
          .getRunningTasks(1);
  if (task.isEmpty()) {
    return false;
  }
  return task
      .get(0)
      .topActivity
      .getPackageName()
      .equalsIgnoreCase(context.getPackageName());
}

Как упоминалось в других ответах, вам необходимо добавить следующее разрешение к вашему AndroidManifest.xml.

<uses-permission android:name="android.permission.GET_TASKS"/>

В Android Lollipop это разрешение устарело
— Мусса

1

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

Этот метод дает вам больше контроля над реальной логикой приложения и вряд ли изменится в будущем.

@Override
protected void onPause() {
    unregisterReceiver(mHandleMessageReceiver);
    super.onPause();
}

@Override
protected void onResume() {
    super.onResume();
    registerReceiver(mHandleMessageReceiver, new IntentFilter(DISPLAY_MESSAGE_ACTION));
}

1

Я нашел более простой и точный способ проверить, находится ли приложение на переднем плане или в фоновом режиме, сопоставив действия с логическими значениями.

Проверьте всю суть здесь


1

Вот код красивого простого решения, описанного выше @ user2690455. Хотя это выглядит немного многословно, в целом вы увидите, что на самом деле он довольно легкий

В моем случае мы также используем AppCompatActivity, поэтому мне пришлось иметь 2 базовых класса.

public class BaseActivity extends Activity {

    /**
     * Let field be set only in base class
     * All callers must use accessors,
     * and then it's not up to them to manage state.
     *
     * Making it static since ..
     * 1. It needs to be used across two base classes
     * 2. It's a singleton state in the app
     */
    private static boolean IS_APP_IN_BACKGROUND = false;

    @Override
    protected void onResume() {
        super.onResume();

        BaseActivity.onResumeAppTracking(this);

        BaseActivity.setAppInBackgroundFalse();
    }

    @Override
    protected void onStop() {
        super.onStop();

        BaseActivity.setAppInBackgroundTrue();
    }

    @Override
    protected void onPause() {
        super.onPause();

        BaseActivity.setAppInBackgroundFalse();
    }

    protected static void onResumeAppTracking(Activity activity) {

        if (BaseActivity.isAppInBackground()) {

            // do requirements for returning app to foreground
        }

    }

    protected static void setAppInBackgroundFalse() {

        IS_APP_IN_BACKGROUND = false;
    }

    protected static void setAppInBackgroundTrue() {

        IS_APP_IN_BACKGROUND = true;
    }

    protected static boolean isAppInBackground() {

        return IS_APP_IN_BACKGROUND;
    }
}

1

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

Вместо использования диспетчера действий есть простой трюк, который можно выполнить с помощью кода. Если вы внимательно понаблюдаете за циклом активности, переход между двумя действиями и от переднего плана к фону выглядит следующим образом. Предположим, что A и B - два вида деятельности.

При переходе от A к B: 1. вызывается onPause () для A 2. вызывается onResume () для B 3. onStop () для A вызывается, когда B полностью возобновляется

Когда приложение переходит в фоновый режим: 1. вызывается onPause () из A 2. вызывается onStop () из A

Вы можете обнаружить фоновое событие, просто установив флажок в Activity.

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

В абстрактной активности создать флаг isAppInBackground.

В методе onCreate (): isAppInBackground = false;

В методе onPause (): isAppInBackground = false;

В методе onStop (): isAppInBackground = true;

Вам просто нужно проверить свой onResume (), если isAppInBackground истинно. n после того, как вы проверите свой флаг, снова установите isAppInBackground = false

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

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


1

Вот метод, который я использую (и поддерживающий метод):

private boolean checkIfAppIsRunningInForeground() {
    ActivityManager activityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
    for(ActivityManager.RunningAppProcessInfo appProcessInfo : activityManager.getRunningAppProcesses()) {
        if(appProcessInfo.processName.contains(this.getPackageName())) {
            return checkIfAppIsRunningInForegroundByAppImportance(appProcessInfo.importance);
        }
    }
    return false;
}

private boolean checkIfAppIsRunningInForegroundByAppImportance(int appImportance) {
    switch (appImportance) {
        //user is aware of app
        case ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND:
        case ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE:
            return true;
        //user is not aware of app
        case ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND:
        case ActivityManager.RunningAppProcessInfo.IMPORTANCE_EMPTY:
        case ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE:
        case ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE:
        default:
            return false;
    }
}

похоже, я проголосовал за решение, которое было опубликовано два года назад. Этот метод больше не работает после различных обновлений безопасности в Android.
— Дроид Крис

0

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

Следуй за этим


0
     public static boolean isAppRunning(Context context) {

 // check with the first task(task in the foreground)
 // in the returned list of tasks

   ActivityManager activityManager = (ActivityManager)
   context.getSystemService(Context.ACTIVITY_SERVICE);
 List<RunningTaskInfo> services =
 activityManager.getRunningTasks(Integer.MAX_VALUE);
     if
     (services.get(0).topActivity.getPackageName().toString().equalsIgnoreCase(context.getPackageName().toString()))
     {
     return true;
     }
     return false;
     }

getRunningTasks больше не подходит - новая модель безопасности делает это практически бесполезным, и оно устарело.
— Тони Маро

@AnaghHegde См. Ответ Гаденкана выше, вытащив список выполняемых действий из системы.
— Тони Маро

0

Упомянутые здесь предыдущие подходы не оптимальны. Подход, основанный на задачах, требует разрешения, которое может быть нежелательным, а «логический» подход предрасположен к ошибкам одновременной модификации.

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

Создайте класс MainApplication для отслеживания количества действий в AtomicInteger :

import android.app.Application;

import java.util.concurrent.atomic.AtomicInteger;

public class MainApplication extends Application {
    static class ActivityCounter {
        private static AtomicInteger ACTIVITY_COUNT = new AtomicInteger(0);

        public static boolean isAppActive() {
            return ACTIVITY_COUNT.get() > 0;
        }

        public static void activityStarted() {
            ACTIVITY_COUNT.incrementAndGet();
        }

        public static void activityStopped() {
            ACTIVITY_COUNT.decrementAndGet();
        }
    }
}

И создайте базовый класс Activity, который будут расширять другие действия:

import android.app.Activity;
import android.support.annotation.CallSuper;

public class TestActivity extends Activity {
    @Override
    @CallSuper
    protected void onStart() {
        MainApplication.ActivityCounter.activityStarted();
        super.onStart();
    }

    @Override
    @CallSuper
    protected void onStop() {
        MainApplication.ActivityCounter.activityStopped();
        super.onStop();
    }
}

Хорошо ... но подумайте об использовании ACTIVITY_COUNT.decrementAndGet (); в activityStopped ().
— ChrisCarneiro
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.