Как узнать, работает ли приложение 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.