Я пытаюсь написать приложение, которое делает что-то конкретное, когда через некоторое время оно возвращается на передний план. Есть ли способ определить, когда приложение отправляется в фоновый режим или выводится на передний план?
Я пытаюсь написать приложение, которое делает что-то конкретное, когда через некоторое время оно возвращается на передний план. Есть ли способ определить, когда приложение отправляется в фоновый режим или выводится на передний план?
Ответы:
onPause()
И onResume()
методы вызываются , когда приложение доводится до фона и на первый план снова. Однако они также вызываются при первом запуске приложения и до его закрытия. Вы можете прочитать больше в деятельности .
Не существует прямого подхода к получению статуса приложения в фоновом режиме или на переднем плане, но даже я столкнулся с этой проблемой и нашел решение с помощью onWindowFocusChanged
и onStop
.
Более подробную информацию можно найти здесь : Android: решение для определения, когда приложение Android переходит в фоновый режим и возвращается на передний план без getRunningTasks или getRunningAppProcesses .
Март 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
обратный вызов не гарантированно будет выполнен:
Если ваш телефон блокирует экран, когда ваше приложение видно (скажем, ваше устройство блокируется через nn минут), этот обратный вызов не вызывается (или не всегда), потому что экран блокировки находится сверху, но ваше приложение все еще «работает», хотя и закрыто.
Если у вашего устройства относительно мало памяти (и он испытывает недостаток памяти), операционная система, похоже, игнорирует этот вызов и сразу переходит к более критическим уровням.
Теперь, в зависимости от того, насколько важно для вас знать, когда ваше приложение перешло в фоновый режим, вам может понадобиться, а может и не понадобиться расширять это решение вместе с отслеживанием жизненного цикла активности и так далее.
Просто имейте это в виду и получите хорошую команду по тестированию;)
КОНЕЦ ОБНОВЛЕНИЯ
Это может быть поздно, но есть надежный метод в 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);
И это все.
level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
метод, который позволяет избежать проблемы в вашем обновлении, пункт 2. Что касается пункта 1, то меня это не беспокоит, поскольку приложение на самом деле не ушло в фоновый режим, поэтому оно должно работать.
Вот как мне удалось это решить. Он работает исходя из того, что использование временной привязки между переходами активности, скорее всего, предоставит достаточные доказательства того, что приложение было «фоновым» или нет.
Во-первых, я использовал экземпляр 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 .
Изменить: новые компоненты архитектуры принесли что-то многообещающее: ProcessLifecycleOwner , см . Ответ @ vokilam
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
}
}
}
Да. Я знаю, трудно поверить, что это простое решение работает, поскольку у нас здесь так много странных решений.
Но есть надежда.
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"
implementation "android.arch.lifecycle:extensions:1.0.0"
и annotationProcessor "android.arch.lifecycle:compiler:1.0.0"
из репозитория Google (то есть google()
)
Основываясь на ответе Мартина Марконцини (спасибо!), Я наконец нашел надежное (и очень простое) решение.
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);
}
}
Мы используем этот метод. Это выглядит слишком просто для работы, но оно было хорошо протестировано в нашем приложении и фактически работает на удивление хорошо во всех случаях, включая переход на домашний экран с помощью кнопки «домой», с помощью кнопки «возврат» или после блокировки экрана. Попробуйте.
Идея в том, что на переднем плане 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 () в более поздних версиях кода. Кроме того, я добавляю супер звонки, которые отсутствовали в моем первоначальном посте, потому что это была скорее концепция, чем рабочий код.
onStop is called when the activity is no longer visible to the user
.
Если ваше приложение состоит из нескольких активностей и / или сложенных активностей, таких как виджет панели вкладок, то переопределение 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, приложение переводится в фоновый режим, если выполняются следующие условия:
Когда вы можете обнаружить, что приложение перешло в фоновый режим, легко обнаружить, когда оно возвращается на передний план.
Создайте класс, который расширяется 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
}
}
FragmentActivity
вы тоже можете захотеть добавить level == ComponentCallbacks2.TRIM_MEMORY_COMPLETE
тоже.
Попробуйте использовать 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 {
...
}
ActivityLifecycleCallbacks может быть интересен, но он недостаточно хорошо документирован.
Однако, если вы вызываете registerActivityLifecycleCallbacks (), вы сможете получить обратные вызовы для случаев, когда действия создаются, уничтожаются и т. Д. Вы можете вызвать getComponentName () для действия.
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
В вашем Приложении добавьте обратный вызов и проверьте активность 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();
}
}
});
}
Я создал проект на 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 () - при переходе приложения в фоновый режим с любого экрана.
Это довольно легко с 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
Вы можете использовать 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
вашего приложения , которые происходят , когда речь идет в фоновом режиме.
Не существует простых методов жизненного цикла, которые бы указывали, когда все приложение переходит в фоновый режим.
Я сделал это простым способом. Следуйте приведенным ниже инструкциям для определения фона приложения / фазы переднего плана.
С небольшим обходным путем, это возможно. Здесь на помощь приходит ActivityLifecycleCallbacks . Позвольте мне пройти через шаг за шагом.
Сначала создайте класс, который расширяет приложение android.app.Application и реализует интерфейс ActivityLifecycleCallbacks . В Application.onCreate () зарегистрируйте обратный вызов.
public class App extends Application implements
Application.ActivityLifecycleCallbacks {
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(this);
}
}
Зарегистрируйте класс «App» в манифесте, как показано ниже <application android:name=".App"
.
Когда приложение находится на переднем плане, будет по крайней мере одна активность в начальном состоянии, а когда приложение находится в фоновом режиме, активности не будет.
Объявите 2 переменные, как показано ниже в классе «App».
private int activityReferences = 0;
private boolean isActivityChangingConfigurations = false;
activityReferences
будет вести подсчет количества действий в запущенном состоянии. isActivityChangingConfigurations
флаг, указывающий, проходит ли текущее действие изменение конфигурации, например, переключатель ориентации.
Используя следующий код, вы можете определить, выходит ли приложение на передний план.
@Override
public void onActivityStarted(Activity activity) {
if (++activityReferences == 1 && !isActivityChangingConfigurations) {
// App enters foreground
}
}
Это как определить, работает ли приложение в фоновом режиме.
@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
чтобы избежать сценария, когда действие проходит через изменения конфигурации.
Я нашел хороший метод для определения приложения, будь то ввод переднего плана или фона. Вот мой код . Надеюсь, это поможет вам.
/**
* 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();
}
Редактировать 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;
}
Правильный ответ здесь
Создайте класс с именем 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
Мое решение было вдохновлено ответом @ 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;
}
Я использовал это с 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) {
}
});
}
}
я знаю, что немного поздно, но я думаю, что у всех этих ответов есть некоторые проблемы, в то время как я сделал это как ниже, и это работает отлично.
создайте обратный вызов жизненного цикла действия следующим образом:
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());
}
Похоже, это один из самых сложных вопросов в 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 .
Это измененная версия ответа @ d60402: https://stackoverflow.com/a/15573121/4747587
Делай все что там упомянуто. Но вместо того, чтобы иметь Base Activity
и делать это как родитель для каждого действия и переопределения onResume()
и onPause
, сделайте следующее:
В своем классе приложения добавьте строку:
registerActivityLifecycleCallbacks (обратный вызов Application.ActivityLifecycleCallbacks);
Здесь callback
есть все методы жизненного цикла активности, и теперь вы можете переопределить onActivityResumed()
и onActivityPaused()
.
Взгляните на этот Gist: https://gist.github.com/thsaravana/1fa576b6af9fc8fff20acfb2ac79fa1b
Вы можете достичь этого легко с помощью 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");
}
}
Надеюсь это поможет.
РЕДАКТИРОВАТЬ В качестве альтернативы теперь вы можете использовать компонент архитектуры с учетом жизненного цикла.
Поскольку я не нашел никакого подхода, который также обрабатывает вращение без проверки меток времени, я подумал, что я также поделюсь тем, как мы сейчас делаем это в нашем приложении. Единственное дополнение к этому ответу 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)
}
Вот и все. Надеюсь, это поможет кому-то :)
Мы можем расширить это решение, используя 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 */ }
}
}
Эти ответы не кажутся правильными. Эти методы также вызываются, когда начинается и заканчивается другое действие. Что вы можете сделать, так это сохранить глобальный флаг (да, глобальные переменные плохие :) и установить его в true каждый раз, когда вы начинаете новое действие. Установите значение false в onCreate каждого действия. Затем в onPause вы проверяете этот флаг. Если оно ложно, ваше приложение уходит в фоновый режим или его убивают.