Як визначити, коли додаток Android виходить на другий план і повертається на перший план


382

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


2
Можливо, до запитання слід додати випадок використання, оскільки це не здається очевидним, тому він не розглядається у наведених відповідях. Додаток може запустити інший додаток (наприклад, Галерея), який все ще буде знаходитися в тій же стеці і відображатись як один із екранів програми, а потім натисніть кнопку "Головна". Жоден із методів, що спираються на життєвий цикл програми (або навіть управління пам'яттю), не в змозі виявити це. Вони запускатимуть фоновий стан прямо тоді, коли з’являється зовнішня активність, а не тоді, коли ви натискаєте «Головна».
Денніс К

Це відповідь , який ви шукаєте: stackoverflow.com/a/42679191/2352699
Фред Порсіункул

1
Дивіться Google Рішення: stackoverflow.com/questions/3667022 / ...
user1269737

Відповіді:


98

Методи onPause()і onResume()методи називаються, коли додаток буде виведено на другий план і знову на перший план. Однак вони також називаються, коли програма запускається вперше і до її вбиття. Більше можна прочитати у розділі Діяльність .

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

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


173
Однак такий підхід викликає помилкові позитиви, як вказували інші, оскільки ці методи також називаються при переході між видами діяльності в одному додатку.
Джон Леманн

9
Це гірше. Я спробував це, і іноді onResume дзвонить, коли телефон заблокований. Якщо ви побачите визначення onResume в документації, ви знайдете: Майте на увазі, що onResume - не найкращий показник того, що ваша діяльність видна користувачеві; системне вікно, таке як клавіатура, може бути спереду. Використовуйте onWindowFocusChanged (булева), щоб точно знати, що ваша діяльність бачна користувачеві (наприклад, для відновлення гри). developer.android.com/reference/android/app/…
J-Rou

2
Рішення, розміщене у посиланні, не використовує onResume / onPause, замість цього поєднання onBackPress, onStop, onStart та onWindowsFocusChanged. Це спрацювало для мене, і у мене досить складна ієрархія інтерфейсу користувача (з ящиками, динамічними проглядачами тощо)
Мартін Марконніні

18
OnPause та onResume є специфічними для діяльності. Не застосування. Коли додаток буде розміщено на задньому плані та відновлено, він відновить конкретну активність, до якої він був, перш ніж перейти до фону. Це означає, що вам потрібно буде реалізовувати все, що ви хочете, щоб відновитись із фонового режиму у всій діяльності вашої заявки. Я вважаю, що оригінальне запитання шукало щось на зразок "onResume" для програми, а не активності.
SysHex

4
Я не можу повірити, що належний API не пропонується для такої загальної потреби. Спочатку я думав, що onUserLeaveHint () скоротить це, але ви не можете сказати, залишає користувач додаток чи ні
atsakiridis

197

2018 рік: Android підтримує це в основному через компоненти життєвого циклу.

Березень 2018 року ОНОВЛЕННЯ : Зараз є краще рішення. Див. Розділ ProcessLifecycleOwner . Вам потрібно буде використовувати нові архітектурні компоненти 1.1.0 (найновіший на даний момент), але це спеціально розроблено для цього.

У цій відповіді є простий зразок, але я написав зразок програми та допис у блозі .

З тих пір, як я писав це ще у 2014 році, виникали різні рішення. Деякі працювали, деякі вважали, що працюють , але мали недоліки (включаючи мої!), І ми, як громада (Android), навчилися жити з наслідками і писали обхідні шляхи для особливих випадків.

Ніколи не припускайте, що один фрагмент коду - це рішення, яке ви шукаєте, це навряд чи так; ще краще, спробуйте зрозуміти, що це робить і чому це робить.

MemoryBossКлас ніколи не був на насправді використовується мною як тут написано, це був просто шматок псевдо - код , який стався з роботою.

Якщо ви не маєте поважних причин, щоб ви не використовували нові компоненти архітектури (а їх є деякі, особливо якщо ви орієнтуєтесь на супер-старі apis), тоді продовжуйте їх та використовуйте. Вони далекі від досконалості, але жодного з них не було ComponentCallbacks2.

ОНОВЛЕННЯ / ПРИМІТКИ (листопад 2015 р.) : Люди зробили два коментарі, по-перше, це >=слід використовувати замість того, ==що в документації зазначено, що ви не повинні перевіряти точні значення . Це добре для більшості випадків, але майте на увазі , що якщо ви тільки піклуватися про виконання дещо - що , коли додаток пішов на задній план, ви повинні будете використовувати == і також об'єднати його з іншим розчином (наприклад , активність Lifecycle зворотних викликів), або може не отримати бажаного ефекту. Приклад (і це сталося зі мною) полягає в тому, що якщо ви хочете заблокувативаш додаток із екраном пароля, коли він переходить на задній план (наприклад, 1Password, якщо ви знайомі з ним), ви можете випадково заблокувати додаток, якщо у вас не вистачає пам’яті і раптом тестуєте >= TRIM_MEMORY, оскільки Android викликає LOW MEMORYдзвінок, і це вищий за твій. Тому будьте уважні, як / що ви тестуєте.

Крім того, деякі люди запитували про те, як виявити, коли ви повертаєтесь.

Найпростіший спосіб, який я можу придумати, пояснюється нижче, але оскільки деякі люди з ним незнайомі, я додаю тут трохи псевдокоду. Припустимо, що у вас є YourApplicationі MemoryBossкласи у ваших class BaseActivity extends Activity(вам потрібно створити його, якщо у вас його немає).

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

    if (mApplication.wasInBackground()) {
        // HERE YOU CALL THE CODE YOU WANT TO HAPPEN ONLY ONCE WHEN YOUR APP WAS RESUMED FROM BACKGROUND
        mApplication.setWasInBackground(false);
    }
}

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

І це все. Код у блоці if буде виконаний лише один раз , навіть якщо ви перейдете до іншої діяльності, новий (що також extends BaseActivity) буде повідомляти wasInBackground, falseтак що він не буде виконувати код, поки не onMemoryTrimmedбуде викликаний і прапор знову встановлений на істинний .

Сподіваюся, що це допомагає.

ОНОВЛЕННЯ / ПРИМІТКИ (квітень 2015 р.) : Перш ніж скористатись Копіюванням та вставкою цього коду, зауважте, що я знайшов пару випадків, коли він може бути не на 100% надійним і його потрібно поєднувати з іншими методами для досягнення найкращих результатів. Зокрема, є два відомі випадки, коли onTrimMemoryзворотний виклик не гарантується виконанням:

  1. Якщо ваш телефон блокує екран, коли ваш додаток видно (скажімо, ваш пристрій блокується через nn хвилин), цей зворотний дзвінок не викликається (або не завжди), оскільки екран блокування знаходиться лише на вершині, але ваш додаток все ще "працює", хоч і накрито.

  2. Якщо у Вашого пристрою порівняно мало пам’яті (і під напругою пам’яті), Операційна система, здається, ігнорує цей виклик і переходить безпосередньо до більш критичних рівнів.

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

Просто пам’ятайте про вищезазначене та майте хорошу команду з якості;)

Кінець оновлення

Це може бути пізно, але в сендвіч-морозиві (API 14) та вище є надійний метод .

Виявляється, коли у вашої програми немає більш видимого інтерфейсу користувача, спрацьовує зворотний виклик. Зворотний виклик, який можна реалізувати у користувацькому класі, називається ComponentCallbacks2 (так, з двома). Цей зворотний дзвінок доступний лише в API рівня 14 (сендвіч з морозивом) і вище.

Ви в основному отримуєте виклик методу:

public abstract void onTrimMemory (int level)

Рівень 20 або більше конкретно

public static final int TRIM_MEMORY_UI_HIDDEN

Я тестував це, і це завжди працює, тому що рівень 20 - це лише "пропозиція", яку ви, можливо, захочете випустити деякі ресурси, оскільки ваш додаток більше не видно.

Щоб процитувати офіційні документи:

Рівень для onTrimMemory (int): процес демонстрував користувальницький інтерфейс, і він більше не робить цього. Великі асигнування з користувальницьким інтерфейсом повинні бути випущені в цей момент, щоб забезпечити краще управління пам'яттю.

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

Але, що цікаво, це те, що ОС говорить вам: ТОЙ, ваш додаток відійшов на другий план!

Це саме те, що ви хотіли знати в першу чергу.

Як визначити, коли ти повернувся?

Ну це просто, я впевнений, що у вас є "BaseActivity", щоб ви могли використовувати свою функцію onResume (), щоб позначити факт повернення. Тому що єдиний раз, коли ти скажеш, що ти не повернувся, - це коли ти насправді отримуєш дзвінок вищевказаного onTrimMemoryметоду.

Це працює. Ви не отримуєте помилкових позитивних результатів. Якщо діяльність поновлюється, ви повертаєтесь у 100% разів. Якщо користувач знову повернеться до спини, ви отримаєте ще один onTrimMemory()дзвінок.

Потрібно підписати свою діяльність (а ще краще - спеціальний клас).

Найпростіший спосіб гарантувати, що ви завжди отримуєте це, створити такий простий клас, як цей:

public class MemoryBoss 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) {
            // We're in the Background
        }
        // you might as well implement some memory cleanup here and be a nice Android dev.
    }
}

Для того, щоб використовувати це, у своїй реалізації програми (у вас є ПРАВА? ) Зробіть щось на кшталт:

MemoryBoss mMemoryBoss;
@Override
public void onCreate() {
   super.onCreate();
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
      mMemoryBoss = new MemoryBoss();
      registerComponentCallbacks(mMemoryBoss);
   } 
}

Якщо ви створюєте, Interfaceви можете додати elseдо цього ifі реалізувати ComponentCallbacks(без 2), що використовується в чомусь нижче API 14. Цей зворотний виклик має лише onLowMemory()метод і не викликається, коли ви переходите на другий план , але ви повинні використовувати його для обрізання пам'яті .

Тепер запустіть свою програму та натисніть додому. Ваш onTrimMemory(final int level)метод слід викликати (підказка: додайте журнал).

Останній крок - скасувати реєстрацію від зворотного дзвінка. Можливо, найкраще місце займає onTerminate()метод вашої програми, але цей метод не викликається на реальному пристрої:

/**
 * This method is for use in emulated process environments.  It will
 * never be called on a production Android device, where processes are
 * removed by simply killing them; no user code (including this callback)
 * is executed when doing so.
 */

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

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

unregisterComponentCallbacks(mMemoryBoss);

І це все.


Перевіряючи це з сервісу, здається, він спрацьовує лише після натискання кнопки додому. Якщо натиснути кнопку назад, це не спрацьовує на KitKat.
Дізнайтеся OpenGL ES

У сервісу немає інтерфейсу користувача, тому це може бути пов'язано з цим. Зробіть перевірку в своїй базовій діяльності, а не в сервісі. Ви хочете дізнатися, коли ваш інтерфейс прихований (і, можливо, скажіть службі, так що це виходить на передній план)
Мартін Марконніні

1
Це не працює, коли ви вимикаєте телефон. Це не спрацьовує.
Juangcg

2
Використання ComponentCallbacks2.onTrimMemory () (у поєднанні з ActivityLifecycleCallbacks) - єдине надійне рішення, яке я знайшов поки що, дякую Мартіну! Для зацікавлених дивіться мою відповідь.
rickul

3
Я використовую цей метод ще рік тому, і мені це завжди було надійно. Добре знати, що і інші користуються ним. Я просто використовую те, level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDENщо дозволяє уникнути проблеми у вашому оновлення, пункт 2. Щодо пункту 1, це мене не хвилює, оскільки додаток насправді не вийшов на другий план, тож це має працювати.
sorianiv

175

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

По-перше, я використав екземпляр android.app.Application (назвемо це MyApplication), у якому є Timer, TimerTask, константа, яка представляє максимальну кількість мілісекунд, що перехід від однієї активності до іншої міг би прийняти (я пішов зі значенням 2s) і булевим, щоб вказати, чи додаток було "у фоновому режимі":

public class MyApplication extends Application {

    private Timer mActivityTransitionTimer;
    private TimerTask mActivityTransitionTimerTask;
    public boolean wasInBackground;
    private final long MAX_ACTIVITY_TRANSITION_TIME_MS = 2000;
    ...

У додатку також передбачено два способи запуску та зупинки таймера / завдання:

public void startActivityTransitionTimer() {
    this.mActivityTransitionTimer = new Timer();
    this.mActivityTransitionTimerTask = new TimerTask() {
        public void run() {
            MyApplication.this.wasInBackground = true;
        }
    };

    this.mActivityTransitionTimer.schedule(mActivityTransitionTimerTask,
                                           MAX_ACTIVITY_TRANSITION_TIME_MS);
}

public void stopActivityTransitionTimer() {
    if (this.mActivityTransitionTimerTask != null) {
        this.mActivityTransitionTimerTask.cancel();
    }

    if (this.mActivityTransitionTimer != null) {
        this.mActivityTransitionTimer.cancel();
    }

    this.wasInBackground = false;
}

Останній фрагмент цього рішення полягає в тому, щоб додати виклик до кожного з цих методів із подій onResume () та onPause () усіх видів діяльності, або, бажано, в базову діяльність, від якої успадковуються всі ваші конкретні дії:

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

    MyApplication myApp = (MyApplication)this.getApplication();
    if (myApp.wasInBackground)
    {
        //Do specific came-here-from-background code
    }

    myApp.stopActivityTransitionTimer();
}

@Override
public void onPause()
{
    super.onPause();
    ((MyApplication)this.getApplication()).startActivityTransitionTimer();
}

Так, у випадку, коли користувач просто переходить між діями вашого додатка, функція onPause (), що відходить, запускає таймер, але майже відразу ж нова активність, що вводиться, скасовує таймер, перш ніж він зможе досягти максимального часу переходу. І так булоInBackground було б помилково .

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


4
Привіт d60402, ваша відповідь дуже корисна .. дякую вам за цю відповідь ... невелике повідомлення .. MyApplication слід згадати в тезі файлу програми Manifest, як android: name = "MyApplication", інакше додаток виходить з ладу ... просто для допомоги хтось як я
praveenb

2
знаком прекрасного програміста, простого вирішення однієї з найскладніших проблем, з якими я коли-небудь стикався.
Aashish Bhatnagar

2
Дивовижне рішення! Дякую. Якщо хтось отримає помилку "ClassCastException", то, можливо, ви пропустили додавання її в тег програми всередині вашого Manifest.xml <application android: name = "your.package.MyApplication"
Вахіб Уль Хак

27
Це приємна і проста реалізація. Однак я вважаю, що це має бути реалізовано в onStart / onStop, а не onPause / onResume. OnPause буде викликано, навіть якщо я розпочну діалогове вікно, яке частково охоплює активність. І закриття діалогового вікна фактично закликає onReseume, воно виглядає так, ніби додаток щойно вийшов на
перший

7
Я сподіваюся використати варіацію цього рішення. Точка щодо діалогів, визначених вище, є проблемою для мене, тому я спробував пропозицію @ Shubhayu (onStart / onStop). Однак це не допомагає, тому що при переході на A-> B виклик onStart () виклику B перед тим, як активувати A на ontop ().
Тревор

150

Редагувати: нові компоненти архітектури принесли щось багатообіцяюче: ProcessLifecycleOwner , див. Відповідь @ vokilam


Фактичне рішення відповідно до розмови вводу / виводу Google :

class YourApplication : Application() {

  override fun onCreate() {
    super.onCreate()
    registerActivityLifecycleCallbacks(AppLifecycleTracker())
  }

}


class AppLifecycleTracker : Application.ActivityLifecycleCallbacks  {

  private var numStarted = 0

  override fun onActivityStarted(activity: Activity?) {
    if (numStarted == 0) {
      // app went to foreground
    }
    numStarted++
  }

  override fun onActivityStopped(activity: Activity?) {
    numStarted--
    if (numStarted == 0) {
      // app went to background
    }
  }

}

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

Але є надія.


3
Це прекрасно працює! Я вже спробував стільки дивних рішень, що мали стільки вад ... дуже дякую! Я деякий час шукав цього.
Eggakin Baconwalker

7
Він працює для декількох видів діяльності, але для одного - onrotate буде вказувати на те, що всі дії пройшли або у фоновому режимі
мертві риби

2
@Shyri ви праві, але це частина цього рішення, тому потрібно хвилюватися. Якщо firebase покладається на це, я думаю, що мій посередній додаток теж може :) Чудова відповідь BTW.
ElliotM

3
@deadfish Перевірте посилання на введення-виведення, вказане у верхній частині відповіді. Ви можете перевірити проміжки часу між зупинкою діяльності та почати визначати, чи дійсно ви перейшли на другий план чи ні. Це насправді геніальне рішення.
Олексій Бердников

2
Чи є рішення Java? Це котлін.
Джакомо Бартолі

116

ProcessLifecycleOwner здається, також є перспективним рішенням.

ProcessLifecycleOwner розішле ON_START, ON_RESUMEподія, як перші кроки діяльності через ці події. ON_PAUSE, ON_STOPподії будуть надсилатись із запізненням після останньої активності, що пройшла через них. Ця затримка достатньо довга, щоб гарантувати, що ProcessLifecycleOwnerне буде надіслано жодних подій, якщо діяльність буде знищена та відтворена через зміну конфігурації.

Реалізація може бути такою ж простою

class AppLifecycleListener : LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onMoveToForeground() { // app moved to foreground
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onMoveToBackground() { // app moved to background
    }
}

// register observer
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleListener())

Відповідно до вихідного коду, значення поточної затримки становить 700ms.

Також для використання цієї функції потрібно dependencies:

implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"

10
Зауважте, що вам потрібно додати залежності життєвого циклу implementation "android.arch.lifecycle:extensions:1.0.0"та annotationProcessor "android.arch.lifecycle:compiler:1.0.0"зі сховища Google (тобто google())
сер Codesalot

1
Це чудово працювало для мене, дякую. Мені довелося використовувати api 'android.arch.lifecycle: extensions: 1.1.0' замість реалізації через помилку, що стверджує, що залежність від Android має різну версію для компіляції та часу виконання.
FSUWX2011

Це чудове рішення, оскільки воно працює в модулях, не потребуючи посилання на активність!
Макс

Це не працює при збої програми. Чи є якесь рішення, щоб програма пошкодження додатків також через це рішення
tejraj

Прекрасне рішення. Врятував мені день.
Сонячно

69

На основі відповіді Мартіна Марконцініса (спасибі!) Я нарешті знайшов надійне (і дуже просте) рішення.

public class ApplicationLifecycleHandler implements Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {

    private static final String TAG = ApplicationLifecycleHandler.class.getSimpleName();
    private static boolean isInBackground = false;

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

    @Override
    public void onActivityStarted(Activity activity) {
    }

    @Override
    public void onActivityResumed(Activity activity) {

        if(isInBackground){
            Log.d(TAG, "app went to foreground");
            isInBackground = false;
        }
    }

    @Override
    public void onActivityPaused(Activity activity) {
    }

    @Override
    public void onActivityStopped(Activity activity) {
    }

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

    @Override
    public void onActivityDestroyed(Activity activity) {
    }

    @Override
    public void onConfigurationChanged(Configuration configuration) {
    }

    @Override
    public void onLowMemory() {
    }

    @Override
    public void onTrimMemory(int i) {
        if(i == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN){
            Log.d(TAG, "app went to background");
            isInBackground = true;
        }
    }
}

Потім додайте це до свого onCreate () класу Application

public class MyApp extends android.app.Application {

    @Override
    public void onCreate() {
        super.onCreate();

        ApplicationLifeCycleHandler handler = new ApplicationLifeCycleHandler();
        registerActivityLifecycleCallbacks(handler);
        registerComponentCallbacks(handler);

    }

}

Чи можете ви показати, як ви використовуєте це в додатку, я називаю це з класу додатків чи десь ще?
JPM

це ідеально дякую !!
Чудово

Цей приклад, якщо неповний. Що таке registerActivityLifecycleCallbacks?
Номан

його метод у класі android.app.Application
rickul

1
молодці +1, щоб піти вгору, тому що це ідеально, не шукайте інших відповідей, це засновано на @reno відповіді, але на реальному прикладі
Стойчо Андрєєв,

63

Ми використовуємо цей метод. Це здається занадто простим для роботи, але він був добре перевірений у нашому додатку і насправді працює напрочуд добре у всіх випадках, включаючи перехід на головний екран кнопкою "домашній", кнопку "повернення" або після блокування екрана. Спробувати.

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

public abstract class BaseActivity extends Activity {

    private static int sessionDepth = 0;

    @Override
    protected void onStart() {
        super.onStart();       
        sessionDepth++;
        if(sessionDepth == 1){
        //app came to foreground;
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (sessionDepth > 0)
            sessionDepth--;
        if (sessionDepth == 0) {
            // app went to background
        }
    }

}

Редагувати: за коментарями ми також перейшли до OnStart () у пізніших версіях коду. Крім того, я додаю супердзвінки, яких не вистачало на моїй початковій посаді, оскільки це було скоріше концепцією, ніж робочим кодом.


2
Це найнадійніша відповідь, хоча я використовую onStart замість onResume.
Грег Енніс

Ви повинні додавати виклики до super.onResume () та super.onStop () у методах переосмислення. В іншому випадку буде скинуто android.app.SuperNotCalledException.
Ян Лоссманн

1
для мене це не працює ... або, принаймні, воно спричиняє подію, коли ви також обертаєте пристрій (що є хибним позитивним імхо).
Ноя

Дуже просте та ефективне рішення! Але я не впевнений, що це працює з частково прозорими видами діяльності, які дозволяють бачити деякі частини попередньої діяльності. З документів, onStop is called when the activity is no longer visible to the user.
Ніколя Буке

3
що станеться, якщо користувач змінить орієнтацію на першу діяльність? Він повідомить, що додаток перейшов на задній план, що не відповідає дійсності. Як ви справляєтесь із цим сценарієм?
Німрод Даян

54

Якщо ваш додаток складається з декількох активних та / або складених активів, таких як віджет панелі вкладок, перезаміщення onPause () та onResume () не працюватиме. Тобто, коли розпочинається нова діяльність, поточна активність буде призупинена до створення нової діяльності. Те саме стосується закінчення (використання кнопки "назад").

Я знайшов два методи, які, здається, працюють так, як хотілося.

Перший вимагає дозволу GET_TASKS і складається з простого методу, який перевіряє, чи належить найвища активність на пристрої додатку, порівнюючи назви пакетів:

private boolean isApplicationBroughtToBackground() {
    ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    List<RunningTaskInfo> tasks = am.getRunningTasks(1);
    if (!tasks.isEmpty()) {
        ComponentName topActivity = tasks.get(0).topActivity;
        if (!topActivity.getPackageName().equals(context.getPackageName())) {
            return true;
        }
    }

    return false;
}

Цей метод був знайдений в рамках Droid-Fu (тепер називається Ignition).

Другий метод, який я реалізував, не вимагає дозволу GET_TASKS, що добре. Натомість реалізувати трохи складніше.

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

Коли кількість запущених дій досягає 0, додаток переходить у фоновий режим, якщо виконуються такі умови:

  • Дія, яка призупиняється, не закінчується (була використана кнопка "назад"). Це можна зробити за допомогою методу Activity.isFinishing ()
  • Нова активність (та сама назва пакета) не починається. Ви можете замінити метод startActivity (), щоб встановити змінну, яка вказує на це, а потім скинути її в onPostResume (), який є останнім методом, який слід запустити, коли діяльність створюється / відновлюється.

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


18
Google, ймовірно, відхилить додаток, який використовує ActivityManager.getRunningTasks (). У документації зазначено, що це лише для ворожих цілей. developer.android.com/reference/android/app/…
Sky Kelsey


1
Я виявив, що мені потрібно використовувати поєднання цих підходів. onUserLeaveHint () викликався під час запуску діяльності у 14. `@Override public void onUserLeaveHint () {inBackground = isApplicationBroughtToBackground (); } `
перелік човна

7
Користувачі не будуть надто раді використовувати потужний дозвіл android.permission.GET_TASKS.
MSquare

6
getRunningTasks засуджується в рівні API 21.
Noya

33

Створіть клас, який розширюється Application. Тоді в ньому ми можемо використовувати його метод перевизначення onTrimMemory().

Щоб виявити, чи додаток пішов на другий план, ми будемо використовувати:

 @Override
    public void onTrimMemory(final int level) {
        if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { // Works for Activity
            // Get called every-time when application went to background.
        } 
        else if (level == ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { // Works for FragmentActivty
        }
    }

1
Бо FragmentActivityви також можете додати level == ComponentCallbacks2.TRIM_MEMORY_COMPLETEтеж.
Srujan Simha

2
Дякую за те, що вказували на цей метод, мені потрібно показувати діалоговий вікно Pin щоразу, коли користувач відновив активність для фону, використовував цей метод для запису попереднього значення та перевірив це значення на baseActivity.
Сем

18

Розглянемо можливість використання onUserLeaveHint. Це буде викликано лише тоді, коли ваш додаток перейде у другий план. onPause матиме кутові корпуси для обробки, оскільки його можна викликати з інших причин; наприклад, якщо користувач відкриє іншу активність у вашому додатку, наприклад, ваші сторінки налаштувань, буде виклик метод основної активності onPause, навіть якщо вони все ще є у вашому додатку; відстеження того, що відбувається, призведе до помилок, коли ви можете просто використовувати зворотний виклик onUserLeaveHint, який робить те, що ви просите.

Коли на UserLeaveHint викликається, ви можете встановити булевий прапор inBackground у значення true. Коли виклик onResume, припускайте, що ви повернулися на перший план, якщо встановлено прапор inBackground. Це тому, що onResume також буде викликаний вашою основною діяльністю, якщо користувач просто знаходився в меню налаштувань і ніколи не виходив із програми.

Пам’ятайте, що якщо користувач натискає кнопку додому, перебуваючи на екрані налаштувань, у вашій роботі з налаштуваннями буде викликано onUserLeaveHint, а коли він повернеться, Reseume буде викликано у вашій діяльності з налаштуваннями. Якщо у вас є основний код виявлення у вашій основній діяльності, ви пропустите цей випадок використання. Щоб цей код був у всіх ваших заходах без дублювання коду, майте абстрактний клас активності, який розширює Діяльність, і додайте до нього ваш загальний код. Тоді кожна діяльність у вас може розширити цю абстрактну діяльність.

Наприклад:

public abstract AbstractActivity extends Activity {
    private static boolean inBackground = false;

    @Override
    public void onResume() {
        if (inBackground) {
            // You just came from the background
            inBackground = false;
        }
        else {
            // You just returned from another activity within your own app
        }
    }

    @Override
    public void onUserLeaveHint() {
        inBackground = true;
    }
}

public abstract MainActivity extends AbstractActivity {
    ...
}

public abstract SettingsActivity extends AbstractActivity {
    ...
}

19
onUserLeaveHint також викликається під час переходу до іншої діяльності
Jonas Stawski,

3
onUserLeaveHint не викликається, коли, наприклад, телефонує дзвінок, і активізація дзвінка стає активною, тому в цьому випадку є і кращий регістр - також можуть бути й інші випадки, оскільки ви можете додати прапор до наміру придушити виклик onUserLeaveHint. developer.android.com/reference/android/content/…
Groxx

1
Крім того, onResume не працює добре. Я спробував це, і іноді onResume дзвонить, коли телефон заблокований. Якщо ви побачите визначення onResume в документації, ви знайдете: Майте на увазі, що onResume - не найкращий показник того, що ваша діяльність видна користувачеві; системне вікно, таке як клавіатура, може бути спереду. Використовуйте onWindowFocusChanged (булева), щоб точно знати, що ваша діяльність бачна користувачеві (наприклад, для відновлення гри). developer.android.com/reference/android/app/…
J-Rou

це рішення не допомагає визначитися з переднім планом / фоном, якщо є кілька заходів.Plz
Raj Trivedi

14

ActivityLifecycleCallbacks може представляти інтерес, але він недостатньо задокументований.

Хоча, якщо ви викликаєте registerActivityLifecycleCallbacks (), ви повинні мати можливість отримувати зворотні виклики, коли діяльність створюється, знищується тощо. Ви можете викликати getComponentName () для діяльності.


11
Оскільки рівень api 14 = \
imort

Схоже, цей чистий і працює для мене. Дякую
duanbo1983

Чим це відрізняється від прийнятої відповіді, і обидва покладаються на одне й те саме життєвий цикл діяльності?
Сайтама

13

Пакет android.arch.lifecycle надає класи та інтерфейси, які дозволяють створювати компоненти, орієнтовані на життєвий цикл

У вашій програмі має бути реалізований інтерфейс LifecycleObserver:

public class MyApplication extends Application implements LifecycleObserver {

    @Override
    public void onCreate() {
        super.onCreate();
        ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    private void onAppBackgrounded() {
        Log.d("MyApp", "App in background");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    private void onAppForegrounded() {
        Log.d("MyApp", "App in foreground");
    }
}

Для цього вам потрібно додати цю залежність до файлу build.gradle:

dependencies {
    implementation "android.arch.lifecycle:extensions:1.1.1"
}

Як рекомендує Google, ви повинні мінімізувати код, виконаний у методах життєвого циклу:

Загальна закономірність полягає в реалізації дій залежних компонентів у життєвих циклах методів діяльності та фрагментів. Однак ця закономірність призводить до поганої організації коду та до поширення помилок. Використовуючи компоненти, орієнтовані на життєвий цикл, ви можете перемістити код залежних компонентів із методів життєвого циклу та в самі компоненти.

Більше ви можете прочитати тут: https://developer.android.com/topic/libraries/architecture/lifecycle


і додайте це до маніфесту, як: <application android: name = ". AnotherApp">
Дан Альботеану

9

У свою програму додайте зворотний виклик та перевіряйте кореневу активність таким чином:

@Override
public void onCreate() {
    super.onCreate();
    registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
        @Override
        public void onActivityStopped(Activity activity) {
        }

        @Override
        public void onActivityStarted(Activity activity) {
        }

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

        @Override
        public void onActivityResumed(Activity activity) {
        }

        @Override
        public void onActivityPaused(Activity activity) {
        }

        @Override
        public void onActivityDestroyed(Activity activity) {
        }

        @Override
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            if (activity.isTaskRoot() && !(activity instanceof YourSplashScreenActivity)) {
                Log.e(YourApp.TAG, "Reload defaults on restoring from background.");
                loadDefaults();
            }
        }
    });
}

Я б розглядав можливість використання цього способу реалізації. Перехід від однієї діяльності до іншої займає лише кілька мілісекунд. Виходячи з часу, коли остання активність зникає, можна вважати повторним входом користувача за допомогою певної стратегії.
drindt

6

Я створив проект на додатку Github -foreground-background-liste

Створіть у своїй програмі BaseActivity для всієї активності.

public class BaseActivity extends Activity {

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    }

    public static boolean isAppInFg = false;
    public static boolean isScrInFg = false;
    public static boolean isChangeScrFg = false;

    @Override
    protected void onStart() {
        if (!isAppInFg) {
            isAppInFg = true;
            isChangeScrFg = false;
            onAppStart();
        }
        else {
            isChangeScrFg = true;
        }
        isScrInFg = true;

        super.onStart();
    }

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

        if (!isScrInFg || !isChangeScrFg) {
            isAppInFg = false;
            onAppPause();
        }
        isScrInFg = false;
    }

    public void onAppStart() {

        // Remove this toast
        Toast.makeText(getApplicationContext(), "App in foreground",    Toast.LENGTH_LONG).show();

        // Your code
    }

    public void onAppPause() {

        // Remove this toast
        Toast.makeText(getApplicationContext(), "App in background",  Toast.LENGTH_LONG).show();

        // Your code
    }
}

Тепер використовуйте цю BaseActivity як супер клас всієї вашої діяльності, як-от MainActivity розширює BaseActivity, а onAppStart буде викликано, коли ви запускаєте програму, а onAppPause () буде викликатись, коли програма перейде на задній план з будь-якого екрана.


@kiran boghra: Чи є помилкові позитиви у вашому рішенні?
Harish Vishwakarma

У цьому випадку можна використовувати ідеальну відповідь на функції onStart () та onStop (). який розповідає про ваш додаток
Пір Фахім Шах

6

Це досить просто за допомогою ProcessLifecycleOwner

Додайте ці залежності

implementation "android.arch.lifecycle:extensions:$project.archLifecycleVersion"
kapt "android.arch.lifecycle:compiler:$project.archLifecycleVersion"

У Котліні :

class ForegroundBackgroundListener : LifecycleObserver {


    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun startSomething() {
        Log.v("ProcessLog", "APP IS ON FOREGROUND")
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun stopSomething() {
        Log.v("ProcessLog", "APP IS IN BACKGROUND")
    }
}

Тоді у вашій базовій діяльності:

override fun onCreate() {
        super.onCreate()

        ProcessLifecycleOwner.get()
                .lifecycle
                .addObserver(
                        ForegroundBackgroundListener()
                                .also { appObserver = it })
    }

Дивіться мою статтю на цю тему: https://medium.com/@egek92/how-to-actually-detect-foreground-background-changes-in-your-android-application-without-wanting-9719cc822c48


5

Ви можете використовувати ProcessLifecycleOwner, приєднавши до нього спостерігача життєвого циклу.

  public class ForegroundLifecycleObserver implements LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    public void onAppCreated() {
        Timber.d("onAppCreated() called");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    public void onAppStarted() {
        Timber.d("onAppStarted() called");
    }

    @OnLifecycleEvent(Event.ON_RESUME)
    public void onAppResumed() {
        Timber.d("onAppResumed() called");
    }

    @OnLifecycleEvent(Event.ON_PAUSE)
    public void onAppPaused() {
        Timber.d("onAppPaused() called");
    }

    @OnLifecycleEvent(Event.ON_STOP)
    public void onAppStopped() {
        Timber.d("onAppStopped() called");
    }
}

то в onCreate()класі вашого програми ви називаєте це:

ProcessLifecycleOwner.get().getLifecycle().addObserver(new ForegroundLifecycleObserver());

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


4

Не існує простих методів життєвого циклу, які дозволять вам повідомити, коли вся програма переходить на другий план / передній план.

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

З невеликим вирішенням можливо. Тут на допомогу приходить ActivityLifecycleCallbacks . Дозвольте мені пройти крок за кроком.

  1. Спочатку створіть клас, який розширює android.app.Application та реалізує інтерфейс ActivityLifecycleCallbacks . У Application.onCreate () зареєструйте зворотний виклик.

    public class App extends Application implements 
        Application.ActivityLifecycleCallbacks {
    
        @Override
        public void onCreate() {
            super.onCreate();
            registerActivityLifecycleCallbacks(this);
        }
    }
  2. Зареєструвати клас «App» в маніфесті , як показано нижче, <application android:name=".App".

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

    Оголосіть 2 змінні, як показано нижче, у класі "Додаток".

    private int activityReferences = 0;
    private boolean isActivityChangingConfigurations = false;

    activityReferencesбуде зберігати підрахунок кількості заходів у запущеному стані. isActivityChangingConfigurations- прапор, який вказує, чи відбувається поточна активність через зміну конфігурації, як перемикач орієнтації.

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

    @Override
    public void onActivityStarted(Activity activity) {
        if (++activityReferences == 1 && !isActivityChangingConfigurations) {
            // App enters foreground
        }
    }
  5. Це як визначити, чи додаток переходить у фоновий режим.

    @Override
    public void onActivityStopped(Activity activity) {
        isActivityChangingConfigurations = activity.isChangingConfigurations();
        if (--activityReferences == 0 && !isActivityChangingConfigurations) {
            // App enters background
        }
    }

Як це працює:

Це невеликий трюк, який виконується способом послідовного виклику методів життєвого циклу. Дозвольте мені проаналізувати сценарій.

Припустимо, що користувач запускає додаток і запускається активність A Launcher. Дзвінки з життєвого циклу будуть,

A.onCreate ()

A.onStart () (++ activityReferences == 1) (додаток виходить на передній план)

A.onResume ()

Тепер активність A починається з діяльності B.

A.onPause ()

B.onCreate ()

B.onStart () (++ ActivityReferences == 2)

B.onResume ()

A.onStop () (--activityReferences == 1)

Потім користувач повертається назад із діяльності B,

B.onPause ()

A.onStart () (++ ActivityReferences == 2)

A.onResume ()

B.onStop () (--activityReferences == 1)

B.onDestroy ()

Потім користувач натискає кнопку "Головна",

A.onPause ()

A.onStop () (--activityReferences == 0) (Додаток переходить у фоновий режим)

У випадку, якщо користувач натисне кнопку "Домашня сторінка" від "Діяння В" замість кнопки "Назад", вона все одно буде однаковою, і буде "Референції" 0. Отже, ми можемо виявити, як додаток переходить до фону.

Отже, яка роль isActivityChangingConfigurations? Припустімо, у вищенаведеному сценарії діяльність B змінює орієнтацію. Послідовність зворотного дзвінка буде,

B.onPause ()

B.onStop () (--activityReferences == 0) (Додаток переходить у фон ??)

B.onDestroy ()

B.onCreate ()

B.onStart () (++ activityReferences == 1) (Додаток виходить на передній план ??)

B.onResume ()

Тому ми маємо додаткову перевірку, isActivityChangingConfigurationsщоб уникнути сценарію, коли Активність відбувається через зміни Конфігурації.


3

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

/**
 * Custom Application which can detect application state of whether it enter
 * background or enter foreground.
 *
 * @reference http://www.vardhan-justlikethat.blogspot.sg/2014/02/android-solution-to-detect-when-android.html
 */
 public abstract class StatusApplication extends Application implements ActivityLifecycleCallbacks {

public static final int STATE_UNKNOWN = 0x00;
public static final int STATE_CREATED = 0x01;
public static final int STATE_STARTED = 0x02;
public static final int STATE_RESUMED = 0x03;
public static final int STATE_PAUSED = 0x04;
public static final int STATE_STOPPED = 0x05;
public static final int STATE_DESTROYED = 0x06;

private static final int FLAG_STATE_FOREGROUND = -1;
private static final int FLAG_STATE_BACKGROUND = -2;

private int mCurrentState = STATE_UNKNOWN;
private int mStateFlag = FLAG_STATE_BACKGROUND;

@Override
public void onCreate() {
    super.onCreate();
    mCurrentState = STATE_UNKNOWN;
    registerActivityLifecycleCallbacks(this);
}

@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    // mCurrentState = STATE_CREATED;
}

@Override
public void onActivityStarted(Activity activity) {
    if (mCurrentState == STATE_UNKNOWN || mCurrentState == STATE_STOPPED) {
        if (mStateFlag == FLAG_STATE_BACKGROUND) {
            applicationWillEnterForeground();
            mStateFlag = FLAG_STATE_FOREGROUND;
        }
    }
    mCurrentState = STATE_STARTED;

}

@Override
public void onActivityResumed(Activity activity) {
    mCurrentState = STATE_RESUMED;

}

@Override
public void onActivityPaused(Activity activity) {
    mCurrentState = STATE_PAUSED;

}

@Override
public void onActivityStopped(Activity activity) {
    mCurrentState = STATE_STOPPED;

}

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

}

@Override
public void onActivityDestroyed(Activity activity) {
    mCurrentState = STATE_DESTROYED;
}

@Override
public void onTrimMemory(int level) {
    super.onTrimMemory(level);
    if (mCurrentState == STATE_STOPPED && level >= TRIM_MEMORY_UI_HIDDEN) {
        if (mStateFlag == FLAG_STATE_FOREGROUND) {
            applicationDidEnterBackground();
            mStateFlag = FLAG_STATE_BACKGROUND;
        }
    }else if (mCurrentState == STATE_DESTROYED && level >= TRIM_MEMORY_UI_HIDDEN) {
        if (mStateFlag == FLAG_STATE_FOREGROUND) {
            applicationDidDestroyed();
            mStateFlag = FLAG_STATE_BACKGROUND;
        }
    }
}

/**
 * The method be called when the application been destroyed. But when the
 * device screen off,this method will not invoked.
 */
protected abstract void applicationDidDestroyed();

/**
 * The method be called when the application enter background. But when the
 * device screen off,this method will not invoked.
 */
protected abstract void applicationDidEnterBackground();

/**
 * The method be called when the application enter foreground.
 */
protected abstract void applicationWillEnterForeground();

}


3

Ви можете використовувати:

захищена недійсність onRestart ()

Щоб відрізнятись між новими запусками та перезапусками.

введіть тут опис зображення


3

Редагувати 2: Те, що я написав нижче, насправді не працює. Google відхилив додаток, що включає дзвінок у ActivityManager.getRunningTasks (). З документації видно, що цей API призначений лише для налагодження та розробки. Я оновлю цю посаду, як тільки встигну оновити проект GitHub нижче за допомогою нової схеми, яка використовує таймери і майже так само хороша.

Редагувати 1: Я написав допис у блозі та створив простий сховище GitHub, щоб зробити це справді просто.

Прийнята та найкраще оцінена відповідь - це насправді не найкращий підхід. Реалізація вищої оцінки відповіді isApplicationBroughtToBackground () не обробляє ситуацію, коли основна діяльність додатка поступається діяльності, визначеній у тому самому додатку, але має інший пакет Java. Я придумав спосіб зробити це, що буде працювати в такому випадку.

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

public static boolean isApplicationBroughtToBackground(final Activity activity) {
  ActivityManager activityManager = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
  List<ActivityManager.RunningTaskInfo> tasks = activityManager.getRunningTasks(1);

  // Check the top Activity against the list of Activities contained in the Application's package.
  if (!tasks.isEmpty()) {
    ComponentName topActivity = tasks.get(0).topActivity;
    try {
      PackageInfo pi = activity.getPackageManager().getPackageInfo(activity.getPackageName(), PackageManager.GET_ACTIVITIES);
      for (ActivityInfo activityInfo : pi.activities) {
        if(topActivity.getClassName().equals(activityInfo.name)) {
          return false;
        }
      }
    } catch( PackageManager.NameNotFoundException e) {
      return false; // Never happens.
    }
  }
  return true;
}

FYI, зателефонувавши до цього в onStart (), уникне його виклику, коли просте діалогове вікно, наприклад, відключення тривоги.
Sky Kelsey

2

Правильна відповідь тут

Створіть клас з ім'ям MyApp, як показано нижче:

public class MyApp implements Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {

    private Context context;
    public void setContext(Context context)
    {
        this.context = context;
    }

    private boolean isInBackground = false;

    @Override
    public void onTrimMemory(final int level) {
        if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {


            isInBackground = true;
            Log.d("status = ","we are out");
        }
    }


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

    }

    @Override
    public void onActivityStarted(Activity activity) {

    }

    @Override
    public void onActivityResumed(Activity activity) {

        if(isInBackground){

            isInBackground = false;
            Log.d("status = ","we are in");
        }

    }

    @Override
    public void onActivityPaused(Activity activity) {

    }

    @Override
    public void onActivityStopped(Activity activity) {

    }

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

    }

    @Override
    public void onActivityDestroyed(Activity activity) {

    }

    @Override
    public void onConfigurationChanged(Configuration configuration) {

    }

    @Override
    public void onLowMemory() {

    }
}

Потім, куди завгодно (краще першу діяльність, запущену в додатку), додайте код нижче:

MyApp myApp = new MyApp();
registerComponentCallbacks(myApp);
getApplication().registerActivityLifecycleCallbacks(myApp);

Готово! Тепер, коли програма знаходиться у фоновому режимі, ми отримуємо журнал, status : we are out і коли ми переходимо в додаток, ми отримуємо журналstatus : we are out


1

Моє рішення було натхнене відповіддю @ d60402, а також покладається на часовий вікно, але не використовує Timer:

public abstract class BaseActivity extends ActionBarActivity {

  protected boolean wasInBackground = false;

  @Override
  protected void onStart() {
    super.onStart();
    wasInBackground = getApp().isInBackground;
    getApp().isInBackground = false;
    getApp().lastForegroundTransition = System.currentTimeMillis();
  }

  @Override
  protected void onStop() {
    super.onStop();
    if( 1500 < System.currentTimeMillis() - getApp().lastForegroundTransition )
      getApp().isInBackground = true;
  }

  protected SingletonApplication getApp(){
    return (SingletonApplication)getApplication();
  }
}

де the SingletonApplication- розширення Applicationкласу:

public class SingletonApplication extends Application {
  public boolean isInBackground = false;
  public long lastForegroundTransition = 0;
}

1

Я використовував це з Google Analytics EasyTracker, і це спрацювало. Це може бути розширено, щоб робити те, що ви шукаєте, використовуючи просте ціле число.

public class MainApplication extends Application {

    int isAppBackgrounded = 0;

    @Override
    public void onCreate() {
        super.onCreate();
        appBackgroundedDetector();
    }

    private void appBackgroundedDetector() {
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle bundle) {

            }

            @Override
            public void onActivityStarted(Activity activity) {
                EasyTracker.getInstance(MainApplication.this).activityStart(activity);
            }

            @Override
            public void onActivityResumed(Activity activity) {
                isAppBackgrounded++;
                if (isAppBackgrounded > 0) {
                    // Do something here
                }
            }

            @Override
            public void onActivityPaused(Activity activity) {
                isAppBackgrounded--;
            }

            @Override
            public void onActivityStopped(Activity activity) {
                EasyTracker.getInstance(MainApplication.this).activityStop(activity);
            }

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

            }

            @Override
            public void onActivityDestroyed(Activity activity) {

            }
        });
    }
}

1

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

створити зворотний виклик життєвого циклу активності таким чином:

 class ActivityLifeCycle implements ActivityLifecycleCallbacks{

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

    }

    @Override
    public void onActivityStarted(Activity activity) {

    }

    Activity lastActivity;
    @Override
    public void onActivityResumed(Activity activity) {
        //if (null == lastActivity || (activity != null && activity == lastActivity)) //use this condition instead if you want to be informed also when  app has been killed or started for the first time
        if (activity != null && activity == lastActivity) 
        {
            Toast.makeText(MyApp.this, "NOW!", Toast.LENGTH_LONG).show();
        }

        lastActivity = activity;
    }

    @Override
    public void onActivityPaused(Activity activity) {

    }

    @Override
    public void onActivityStopped(Activity activity) {

    }

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

    }

    @Override
    public void onActivityDestroyed(Activity activity) {

    }
}

і просто зареєструйте його у вашому класі додатків, як показано нижче:

public class MyApp extends Application {

@Override
public void onCreate() {
    super.onCreate();
    registerActivityLifecycleCallbacks(new ActivityLifeCycle());
}

Це називається весь час під час кожного заняття. Як я можу це використати, якщо, наприклад, хочу виявити статус користувача в Інтернеті
Максим Князев,

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

якщо ви маєте на увазі підключення до Інтернету, я думаю, що краще перевірити це, коли вам це потрібно. якщо вам потрібно зателефонувати в api, перевірте підключення до Інтернету безпосередньо перед тим, як зателефонувати.
Амір Зіараті

1

Це, мабуть, одне із найскладніших питань в Android, оскільки (на момент написання цього запису) Android не має еквівалентів iOS applicationDidEnterBackground()абоapplicationWillEnterForeground() зворотного зв’язку . Я використовував бібліотеку AppState, яку зібрав @jenzz .

[AppState - це проста, реактивна бібліотека Android, заснована на RxJava, яка відстежує зміни стану додатків. Він повідомляє передплатників щоразу, коли додаток переходить на другий план і повертається на перший план.

Виявилося, що це саме те, що мені було потрібно, тим більше, що в моєму додатку було кілька дій, тому просто перевірити onStart()або onStop()активність не збиралася його скорочувати.

Спочатку я додав ці залежності до gradle:

dependencies {
    compile 'com.jenzz.appstate:appstate:3.0.1'
    compile 'com.jenzz.appstate:adapter-rxjava2:3.0.1'
}

Тоді було просто додати ці рядки до відповідного місця у вашому коді:

//Note that this uses RxJava 2.x adapter. Check the referenced github site for other ways of using observable
Observable<AppState> appState = RxAppStateMonitor.monitor(myApplication);
//where myApplication is a subclass of android.app.Application
appState.subscribe(new Consumer<AppState>() {
    @Override
    public void accept(@io.reactivex.annotations.NonNull AppState appState) throws Exception {
        switch (appState) {
            case FOREGROUND:
                Log.i("info","App entered foreground");
                break;
            case BACKGROUND:
                Log.i("info","App entered background");
                break;
        }
    }
});

Залежно від того, як ви підписалися на спостережуване, вам, можливо, доведеться скасувати підписку на нього, щоб уникнути витоку пам'яті. Знову більше інформації на сторінці github .


1

Це модифікована версія відповіді @ d60402: https://stackoverflow.com/a/15573121/4747587

Робіть усе, що там згадується. Але замість того, щоб мати Base Activityта робити це в якості батьків для кожної діяльності та переважаючої onResume()таonPause , виконайте наступне:

У класі заявки додайте рядок:

registerActivityLifecycleCallbacks (Application.ActivityLifecycleCallbacks зворотний виклик);

У цьому callbackє всі методи життєвого циклу діяльності, і тепер ви можете їх переосмислити onActivityResumed()та onActivityPaused().

Погляньте на цю історію: https://gist.github.com/thsaravana/1fa576b6af9fc8fff20acfb2ac79fa1b


1

Ви можете досягти цього легко за допомогою ActivityLifecycleCallbacksіComponentCallbacks2 чогось подібного нижче.

Створіть клас, що AppLifeCycleHandlerреалізує вищевказані інтерфейси.

package com.sample.app;

import android.app.Activity;
import android.app.Application;
import android.content.ComponentCallbacks2;
import android.content.res.Configuration;
import android.os.Bundle;

/**
 * Created by Naveen on 17/04/18
 */
public class AppLifeCycleHandler
    implements Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {

  AppLifeCycleCallback appLifeCycleCallback;

  boolean appInForeground;

  public AppLifeCycleHandler(AppLifeCycleCallback appLifeCycleCallback) {
    this.appLifeCycleCallback = appLifeCycleCallback;
  }

  @Override
  public void onActivityResumed(Activity activity) {
    if (!appInForeground) {
      appInForeground = true;
      appLifeCycleCallback.onAppForeground();
    }
  }

  @Override
  public void onTrimMemory(int i) {
    if (i == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
      appInForeground = false;
      appLifeCycleCallback.onAppBackground();
    }
  }

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

  }

  @Override
  public void onActivityStarted(Activity activity) {

  }

  @Override
  public void onActivityPaused(Activity activity) {

  }

  @Override
  public void onActivityStopped(Activity activity) {

  }

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

  }

  @Override
  public void onActivityDestroyed(Activity activity) {

  }

  @Override
  public void onConfigurationChanged(Configuration configuration) {

  }

  @Override
  public void onLowMemory() {

  }

  interface AppLifeCycleCallback {

    void onAppBackground();

    void onAppForeground();
  }
}

У вашому класі, який розширює Applicationреалізацію, AppLifeCycleCallbackщоб отримувати зворотні дзвінки, коли програма перемикається між переднім планом та фоном. Щось подібне нижче.

public class BaseApplication extends Application implements AppLifeCycleHandler.AppLifeCycleCallback{

    @Override
    public void onCreate() {
        super.onCreate();
        AppLifeCycleHandler appLifeCycleHandler = new AppLifeCycleHandler(this);
        registerActivityLifecycleCallbacks(appLifeCycleHandler);
        registerComponentCallbacks(appLifeCycleHandler);
    }

    @Override
    public void onAppBackground() {
        Log.d("LifecycleEvent", "onAppBackground");
    }

    @Override
    public void onAppForeground() {
        Log.d("LifecycleEvent", "onAppForeground");
    }
}

Сподіваюсь, це допомагає.

EDIT Як альтернатива, тепер ви можете використовувати компонент архітектури, що усвідомлює життєвий цикл.


1

Оскільки я не знайшов жодного підходу, який також обробляє обертання, не перевіряючи часові позначки, я подумав, що я також поділяюся тим, як ми це робимо зараз у нашому додатку. Єдиним доповненням до цієї відповіді https://stackoverflow.com/a/42679191/5119746 є те, що ми також враховуємо орієнтацію.

class MyApplication : Application(), Application.ActivityLifecycleCallbacks {

   // Members

   private var mAppIsInBackground = false
   private var mCurrentOrientation: Int? = null
   private var mOrientationWasChanged = false
   private var mResumed = 0
   private var mPaused = 0

Тоді для зворотних викликів у нас спочатку є резюме:

   // ActivityLifecycleCallbacks

   override fun onActivityResumed(activity: Activity?) {

      mResumed++

      if (mAppIsInBackground) {

         // !!! App came from background !!! Insert code

         mAppIsInBackground = false
      }
      mOrientationWasChanged = false
    }

І onActivityStopped:

   override fun onActivityStopped(activity: Activity?) {

       if (mResumed == mPaused && !mOrientationWasChanged) {

       // !!! App moved to background !!! Insert code

        mAppIsInBackground = true
    }

І ось, ось тут додається: Перевірка змін орієнтації:

   override fun onConfigurationChanged(newConfig: Configuration) {

       if (newConfig.orientation != mCurrentOrientation) {
           mCurrentOrientation = newConfig.orientation
           mOrientationWasChanged = true
       }
       super.onConfigurationChanged(newConfig)
   }

Це воно. Сподіваюсь, це комусь допоможе :)


1

Ми можемо розширити це рішення за допомогою LiveData:

class AppForegroundStateLiveData : LiveData<AppForegroundStateLiveData.State>() {

    private var lifecycleListener: LifecycleObserver? = null

    override fun onActive() {
        super.onActive()
        lifecycleListener = AppLifecycleListener().also {
            ProcessLifecycleOwner.get().lifecycle.addObserver(it)
        }
    }

    override fun onInactive() {
        super.onInactive()
        lifecycleListener?.let {
            this.lifecycleListener = null
            ProcessLifecycleOwner.get().lifecycle.removeObserver(it)
        }
    }

    internal inner class AppLifecycleListener : LifecycleObserver {

        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        fun onMoveToForeground() {
            value = State.FOREGROUND
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        fun onMoveToBackground() {
            value = State.BACKGROUND
        }
    }

    enum class State {
        FOREGROUND, BACKGROUND
    }
}

Тепер ми можемо підписатися на цей LiveData та зловити необхідні події. Наприклад:

appForegroundStateLiveData.observeForever { state ->
    when(state) {
        AppForegroundStateLiveData.State.FOREGROUND -> { /* app move to foreground */ }
        AppForegroundStateLiveData.State.BACKGROUND -> { /* app move to background */ }
    }
}

0

Ці відповіді не здаються правильними. Ці методи називаються також тоді, коли починається і закінчується інша діяльність. Що ви можете зробити, це зберегти глобальний прапор (так, глобальні люди погані :), і встановлюйте це як істинне щоразу, коли ви починаєте нову діяльність. Встановіть значення false у onCreate кожної діяльності. Потім у onPause ви перевіряєте цей прапор. Якщо це неправда, ваш додаток переходить на другий план, або він стає вбитим.


Я не говорив про базу даних ... що ти маєш на увазі?
Joris Weimar

Я підтримую вашу відповідь. незважаючи на те, що ми можемо зберегти це значення прапора в базі даних під час виклику паузи, це не гарне рішення ..
Sandeep P
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.