Как определить, когда приложение Android переходит в фоновый режим и возвращается на передний план

382

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

iHorse
источник
2
Может быть, добавить вариант использования к вопросу, потому что он не кажется очевидным, поэтому он не рассматривается в данных ответах. Приложение может запустить другое приложение (например, Галерея), которое все еще будет находиться в том же стеке и отображаться как один из экранов приложения, а затем нажать кнопку «Домой». Ни один из методов, основанных на жизненном цикле приложения (или даже управлении памятью), не может обнаружить это. Они будут вызывать фоновое состояние, когда появляется внешняя активность, а не когда вы нажимаете Home.
Денис К
Это ответ, который вы ищете: stackoverflow.com/a/42679191/2352699
Фред Porciúncula
1
См. Google Solution: stackoverflow.com/questions/3667022/…
user1269737

Ответы:

98

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

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

Более подробную информацию можно найти здесь : Android: решение для определения, когда приложение Android переходит в фоновый режим и возвращается на передний план без getRunningTasks или getRunningAppProcesses .

Girish Nair
источник
174
Однако этот подход вызывает ложные срабатывания, как отмечали другие, поскольку эти методы также вызываются при переходе между действиями в одном приложении.
Джон Леманн
9
Это хуже чем это. Я пробовал, и иногда onResume вызывается, когда телефон заблокирован. Если вы увидите определение onResume в документации, вы обнаружите: имейте в виду, что onResume - не лучший индикатор того, что ваша активность видна пользователю; системное окно, такое как защита клавиатуры, может быть впереди. Используйте onWindowFocusChanged (boolean), чтобы точно знать, что ваша активность видна пользователю (например, чтобы возобновить игру). developer.android.com/reference/android/app/…
J-Rou
2
Решение, опубликованное в ссылке, не использует onResume / onPause, вместо этого используется комбинация onBackPressed, 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 г.) : люди делали два комментария, во-первых, это >=следует использовать вместо того, ==потому что в документации говорится, что вы не должны проверять точные значения . Это хорошо для большинства случаев, но имейте в виду, что если вы заботитесь только о чем-то когда приложение перешло в фоновый режим, вам придется использовать ==, а также объединить его с другим решением (например, обратными вызовами Activity 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будет вызван и флаг снова не будет установлен в true ,

Надеюсь, это поможет.

ОБНОВЛЕНИЕ / ЗАМЕТКИ (апрель 2015 г.) : Прежде чем перейти к копированию и вставке этого кода, обратите внимание, что я обнаружил пару случаев, когда он может быть ненадежным на 100% и должен сочетаться с другими методами для достижения наилучших результатов. В частности, есть два известных случая, когда onTrimMemoryобратный вызов не гарантированно будет выполнен:

  1. Если ваш телефон блокирует экран, когда ваше приложение видно (скажем, ваше устройство блокируется через nn минут), этот обратный вызов не вызывается (или не всегда), потому что экран блокировки находится сверху, но ваше приложение все еще «работает», хотя и закрыто.

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

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

Просто имейте это в виду и получите хорошую команду по тестированию;)

КОНЕЦ ОБНОВЛЕНИЯ

Это может быть поздно, но есть надежный метод в Ice Cream Sandwich (API 14) и выше .

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

Вы в основном получаете вызов метода:

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.
    }
}

Чтобы использовать это, в вашей реализации приложения (у вас есть одна, RIGHT? ), Сделайте что-то вроде:

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) - единственное надежное решение, которое я нашел до сих пор, спасибо Мартин! Для тех, кто заинтересован, смотрите мой ответ.
Рикуль,
3
Я использую этот метод с год назад, и он всегда был для меня надежным. Приятно знать, что другие люди тоже используют его. Я просто использую level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDENметод, который позволяет избежать проблемы в вашем обновлении, пункт 2. Что касается пункта 1, то меня это не беспокоит, поскольку приложение на самом деле не ушло в фоновый режим, поэтому оно должно работать.
Сорианов
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 было бы ложным .

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

d60402
источник
4
Привет, d60402, твой ответ действительно полезен .. большое спасибо за этот ответ ... небольшое замечание .. MyApplication должен упомянуть в теге приложения файла манифеста, например, android: name = "MyApplication", в противном случае приложение вылетает ... просто чтобы помочь кто-то, как я
praveenb
2
знак великого программиста, простое решение одной из самых сложных проблем, с которыми я когда-либо сталкивался.
Аашиш Бхатнагар
2
Отличное решение! Спасибо. Если кто-то получит ошибку «ClassCastException», то вы, возможно, пропустили ее добавление в тег приложения внутри вашего Manifest.xml <application android: name = "your.package.MyApplication"
Wahib Ul Haq
27
Это хорошая и простая реализация. Однако я считаю, что это должно быть реализовано в onStart / onStop, а не в OnPause / onResume. OnPause будет вызываться, даже если я начну диалог, который частично охватывает деятельность. А закрытие диалога фактически вызовет onResume, чтобы оно выглядело так, как будто приложение только что вышло на передний план
Shubhayu
7
Я надеюсь использовать вариант этого решения. Вопрос о диалогах, указанных выше, является проблемой для меня, поэтому я попробовал предложение @ Shubhayu (onStart / onStop). Однако это не помогает, потому что при переходе A-> B onStart () из Activity B вызывается перед onStop () из Activity A.
Тревор
150

Изменить: новые компоненты архитектуры принесли что-то многообещающее: ProcessLifecycleOwner , см . Ответ @ vokilam


Фактическое решение в соответствии с докладом Google I / O :

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
Это работает отлично! Я уже пробовал так много странных решений, которые имели так много недостатков ... большое спасибо! Я искал это некоторое время.
Эггакин Бэконуокер,
7
Это работает для нескольких действий, но для одного - onrotate укажет обо всех действиях, ушедших или в фоновом режиме
deadfish
2
@ Шири, вы правы, но это часть этого решения, так что нужно беспокоиться. Если Firebase полагается на это, я думаю, что мое посредственное приложение тоже может :) Отличный ответ Кстати.
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"
vokilam
источник
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
Это отличное решение, потому что оно работает в модулях без ссылки на Activity!
Макс
Это не работает при сбое приложения. Есть ли какое-либо решение, чтобы получить событие
сбоя
Отличное решение. Спас мой день.
Солнечный
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 () вашего класса приложения

public class MyApp extends android.app.Application {

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

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

    }

}
rickul
источник
Можете ли вы показать, как вы используете это в приложении, я вызываю это из класса приложения или где-то еще?
JPM
это прекрасно, спасибо !! До сих пор
отлично
Этот пример, если неполный. Что такое registerActivityLifecycleCallbacks?
Номан,
это метод в классе android.app.Application
Рикул
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
для меня это не работает ... или, по крайней мере, оно запускает событие, когда вы вращаете устройство тоже (что-то вроде ложного срабатывания imho).
Ноя
Очень простое и эффективное решение! Но я не уверен, что он работает с частично прозрачными действиями, которые позволяют увидеть некоторые части предыдущего действия. Из документов 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
Ноя
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
        }
    }
Harpreet
источник
1
Ибо FragmentActivityвы тоже можете захотеть добавить level == ComponentCallbacks2.TRIM_MEMORY_COMPLETEтоже.
Сружан Симха
2
Большое спасибо за указание на этот метод, мне нужно показывать Pin Pinog всякий раз, когда пользователь возобновляет действие для фона, использовал этот метод, чтобы записать значение pref и проверил это значение в baseActivity.
Сэм
18

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

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

Помните, что если пользователь нажимает кнопку «Домой» на экране настроек, onUserLeaveHint будет вызываться в ваших действиях с настройками, а когда они возвращаются, onResume будет вызываться в ваших действиях с настройками. Если у вас есть только этот код обнаружения в вашей основной деятельности, вы пропустите этот вариант использования. Чтобы этот код был во всех ваших действиях без дублирования кода, создайте абстрактный класс действий, расширяющий Activity, и вставьте в него свой общий код. Тогда каждое ваше занятие может расширять это абстрактное занятие.

Например:

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 {
    ...
}
OldSchool4664
источник
19
onUserLeaveHint также вызывается при переходе к другому виду деятельности
Jonas Stawski
3
onUserLeaveHint не вызывается, когда, например, поступает телефонный звонок, и вызывающая активность становится активной, так что это также имеет крайний случай - могут быть и другие случаи, так как вы можете добавить флаг в намерение подавить вызов onUserLeaveHint. developer.android.com/reference/android/content/…
Groxx
1
Кроме того, onResume не работает хорошо. Я пробовал, и иногда onResume вызывается, когда телефон заблокирован. Если вы увидите определение onResume в документации, вы обнаружите: имейте в виду, что onResume - не лучший индикатор того, что ваша активность видна пользователю; системное окно, такое как защита клавиатуры, может быть впереди. Используйте onWindowFocusChanged (boolean), чтобы точно знать, что ваша активность видна пользователю (например, чтобы возобновить игру). developer.android.com/reference/android/app/…
J-Rou
Это решение не помогает определить передний план / фон, если есть несколько действий. Пожалуйста,
Радж Триведи
14

ActivityLifecycleCallbacks может быть интересен, но он недостаточно хорошо документирован.

Однако, если вы вызываете registerActivityLifecycleCallbacks (), вы сможете получить обратные вызовы для случаев, когда действия создаются, уничтожаются и т. Д. Вы можете вызвать getComponentName () для действия.

Reno
источник
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

matdev
источник
и добавьте это в манифест, например: <application android: name = ". AnotherApp">
Дан Алботяну,
9

В вашем Приложении добавьте обратный вызов и проверьте активность root следующим образом:

@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 app-foreground-background-listen

Создайте 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 в качестве суперкласса всей вашей Activity, например MainActivity расширяет BaseActivity, и onAppStart будет вызываться при запуске приложения, а onAppPause () - при переходе приложения в фоновый режим с любого экрана.

Киран Богра
источник
@kiran boghra: есть ли ложные срабатывания в вашем решении?
Хариш Вишвакарма
В этом случае можно использовать функции onStart () и onStop (). который рассказывает вам о вашем приложении
Pir Fahim Shah
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()вашем классе Application вы вызываете это:

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 переменные, как показано ниже в классе «App».

    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
        }
    }

Как это работает:

Это небольшая хитрость, связанная с тем, как методы жизненного цикла вызываются последовательно. Позвольте мне пройти сценарий.

Предположим, что пользователь запускает приложение и запускает Launcher Activity A. Жизненный цикл звонков будет,

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) (приложение входит в фоновый режим)

В случае, если пользователь нажимает кнопку «Домой» в «Деятельности B» вместо кнопки «Назад», он все равно останется прежним, и ActivityReferences будет 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();

}

Folyd
источник
3

Ты можешь использовать:

защищенный void onRestart ()

Различаться между новыми запусками и перезапусками.

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

AYBABTU
источник
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;
}
Скай Келси
источник
К вашему сведению, вызов этого в onStart () вместо этого позволит избежать его вызова, когда выдается простой диалог, например, из-за срабатывания тревоги.
Скай Келси
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

Erfan
источник
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();
  }
}

где SingletonApplicationрасширение Applicationкласса:

public class SingletonApplication extends Application {
  public boolean isInBackground = false;
  public long lastForegroundTransition = 0;
}
injecteer
источник
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().

Взгляните на этот Gist: 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");
    }
}

Надеюсь это поможет.

РЕДАКТИРОВАТЬ В качестве альтернативы теперь вы можете использовать компонент архитектуры с учетом жизненного цикла.

Навин Т.П.
источник
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
    }

И на ActiveStopped:

   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

Эти ответы не кажутся правильными. Эти методы также вызываются, когда начинается и заканчивается другое действие. Что вы можете сделать, так это сохранить глобальный флаг (да, глобальные переменные плохие :) и установить его в true каждый раз, когда вы начинаете новое действие. Установите значение false в onCreate каждого действия. Затем в onPause вы проверяете этот флаг. Если оно ложно, ваше приложение уходит в фоновый режим или его убивают.

Йорис Веймар
источник
Я не говорил о базе данных ... что ты имеешь в виду?
Йорис Веймар
Я поддерживаю ваш ответ. даже если мы можем сохранить это значение флага в базе данных при вызове паузы, это не очень хорошее решение ..
Sandeep P