Як я можу зрозуміти, чи працює програма Android на передньому плані?


83

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



Це подібне питання ... хоча я спробував позначити на onStart / onStop, і це не спрацювало. Я все ще не отримую різниці між зупинкою / запуском та паузою / відновленням.
Ендрю Томас

1
Вам слід скористатися цим рішенням: stackoverflow.com/questions/3667022/…
Informatic0re

Оскільки API 16 існує простіший спосіб використання ActivityManager.getMyMemoryState
Juozas Kontvainis

Оскільки бібліотека підтримки підтримує версію 26, вам просто потрібно запитувати ProcessLifecycleOwner, коли завгодно. Перевірте stackoverflow.com/a/52678290/6600000
Keivan Esbati

Відповіді:


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: Так, це так. Але ми можемо передавати цю змінну, наприклад, налаштуванням або базі даних, коли відбувається зміна.
Вроцлай,

19
@Shelly Але ваша змінна буде мати значення True / False / True при переключенні між діями в одному додатку. Це означає, що ви повинні мати гістерезис, щоб реально виявити, коли ваш додаток втрачає передній план.
Раду

10
Це не найкраще рішення. Перевірте рішення @Gadenkan нижче.
Феліпе Ліма

Це працює для простих сценаріїв, але якщо у вас є onActivityResult () щодо вашої діяльності, все ускладнюється. Я хочу запустити фонову службу тоді і тільки тоді, коли всі дії мого додатка перебувають у фоновому режимі, і це рішення вище не відповідає.
Джош

1
@Tima Якщо ви хочете зробити його доступним для всіх видів діяльності, ви можете створити новий MyOwnActivity, розширює 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" />

п / с: Якщо ви згодні з цим, зверніть увагу, що цей дозвіл зараз застарілий.


24
Якщо ви хочете використовувати цей код, не забудьте додати <koristi-дозвіл android: name = "android.permission.GET_TASKS" />
Лакшманан

13
Nitpicks: Виклик toString()рядка, що повертається, getPackageName()зайвий. Крім того, оскільки нас цікавить лише перше завдання, яке повертає getRunningTasks(), ми можемо пройти 1замість Integer.MAX_VALUE.
Йонік

14
Примітка: цей метод призначений лише для налагодження та представлення користувацьких інтерфейсів управління завданнями. Це ніколи не повинно використовуватися для основної логіки в програмі, наприклад, для вибору між різними способами поведінки на основі інформації, що міститься тут. Подібне використання не підтримується, і, ймовірно, у майбутньому буде порушено. Наприклад, якщо кілька програм можуть активно працювати одночасно, зроблені припущення щодо значення даних тут для цілей управління потоком будуть неправильними.
Informatic0re

5
На жаль, getRunningTasks () застаріла ще з часів Android L (API 20). Починаючи з L, цей метод більше не доступний для сторонніх додатків: введення документально орієнтованих останніх означає, що він може передавати інформацію про особу, що телефонує. Для зворотної сумісності він все одно поверне невелику підмножину своїх даних: принаймні власні завдання абонента та, можливо, деякі інші завдання, такі як домашній, які, як відомо, не чутливі.
Sam Lu

2
Імовірно, якщо воно відображає лише завдання абонента, то перше завдання та найвища активність завжди будуть за вами, тобто це завжди поверне справжній пост-льодяник.
Mike Holler

46

Це досить стара публікація, але все ще досить актуальна. Вище прийняте рішення може спрацювати, але є помилковим. Як писала Діанн Хакборн:

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

Так, існує список, який зберігається в пам'яті для цих речей. Однак це вимикається в іншому процесі, яким керують потоки, що працюють окремо від вашого, а не те, на що ви можете розраховувати (а) побачити вчасно, щоб прийняти правильне рішення, або (б) мати послідовну картину до моменту повернення. Плюс рішення про те, до якого "наступного" заходу слід переходити, завжди приймається в точці, де має відбутися перемикання, і лише тоді, коли буде здійснено перемикання на той самий момент (коли стан активності буде ненадовго заблокований) насправді знати для таких, якою буде наступна річ.

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

Правильним рішенням є реалізація: ActivityLifeCycleCallbacks .

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


7
Ось приклад, як ним користуватися baroqueworksdev.blogspot.com/2012/12/…
JK

2
Я думаю, що це найкраще рішення і, мабуть, не зламається в майбутньому. Цікаво, чому йому не вистачило голосів.
Рохан Кандвал

2
Якщо ви напишете приклад коду, ви отримаєте більше голосів, тому що деякі люди пропускають відповіді без прикладу ... але це найкраще рішення ...
Saman Salehi

чим це відрізняється від заміни 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();
}

Ефективне рішення. Дякую :)
Пані Саедул Карім

Дуже приємне рішення. Дозволи не потрібні.
user3144836 02

Чудово! Я використовував список завдань, але для цього потрібно ОТРИМАТИ ЗАДАЧУ .. справді вдячний за це!
hexagod

15

До уваги, якщо ви використовуєте рішення Gadenkan (що чудово !!), не забудьте додати

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

до маніфесту.


9
В android Lollipop цей дозвіл застарілий
Мусса

14

Трохи очищена версія рішення Гаденкана . Поставте це будь-яке заняття, або, можливо, базовий клас для всіх ваших занять.

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даний момент цей метод більше не доступний для сторонніх додатків: введення документально орієнтованих останніх означає, що він може передавати інформацію про особу, що телефонує. Для зворотної сумісності він все одно поверне невелику підмножину своїх даних: принаймні власні завдання абонента та, можливо, деякі інші завдання, такі як домашній, які, як відомо, не чутливі.

Тож те, що я писав раніше, є ще більш актуальним:

У багатьох випадках ви, мабуть, можете придумати краще рішення. Наприклад, робити що - то onPause()і onResume(), можливо , в BaseActivity для всієї вашої діяльності.

(У нашому випадку ми не хотіли б запускати офлайн-сповіщення, якщо ми не знаходимось на передньому плані, тому в BaseActivity onPause()ми просто відмовляємося від підписки на RxJava, Subscriptionпрослуховуючи сигнал "вийшов з мережі".)


Ви можете розмістити код RxJava, який Ви використовували? Я хочу використовувати RxJava, і я не хочу витрачати багато часу на вивчення RxJava в цей момент :(
MBH

@MBH: Використання RxJava, мабуть, не буде дуже плідним, не витративши деякий час на ознайомлення з основними поняттями ... :-) У будь-якому випадку, ось невеликий приклад .
Йонік

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
Це правильна відповідь. Багато інших слід вважати застарілими
11м0

Для 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

Я хотів би додати, що більш безпечним способом зробити це - ніж перевіряти, чи ваш додаток у фоновому режимі перед створенням сповіщення - є просто вимкнути та ввімкнути Broadcast Receiver 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: 1. викликається onPause () A 2. onResume () B називається 3. Викликається onStop () A, коли B повністю відновлюється

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

Ви можете виявити свою фонову подію, просто ввімкнувши прапор.

Зробіть абстрактне заняття та розширте його від інших видів діяльності, щоб вам не довелося копіювати код для всіх інших видів діяльності, де б вам не потрібно було фонової події.

В абстрактній діяльності створюйте прапор 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.
Droid Chris

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.