Я намагаюся написати додаток, яке робить щось конкретне, коли через деякий час його повернуть на перший план. Чи є спосіб виявити, коли додаток надсилається на другий план або виноситься на перший план?
Я намагаюся написати додаток, яке робить щось конкретне, коли через деякий час його повернуть на перший план. Чи є спосіб виявити, коли додаток надсилається на другий план або виноситься на перший план?
Відповіді:
Методи onPause()
і onResume()
методи називаються, коли додаток буде виведено на другий план і знову на перший план. Однак вони також називаються, коли програма запускається вперше і до її вбиття. Більше можна прочитати у розділі Діяльність .
Немає жодного прямого підходу до отримання статусу програми, перебуваючи на задньому плані чи на передньому плані, але навіть я зіткнувся з цією проблемою і знайшов рішення за допомогою onWindowFocusChanged
та onStop
.
Докладніші відомості можна знайти тут Android: рішення, щоб визначити, коли додаток для Android виходить на другий план і повертається на передній план без getRunningTasks або getRunningAppProcess .
Березень 2018 року ОНОВЛЕННЯ : Зараз є краще рішення. Див. Розділ ProcessLifecycleOwner . Вам потрібно буде використовувати нові архітектурні компоненти 1.1.0 (найновіший на даний момент), але це спеціально розроблено для цього.
У цій відповіді є простий зразок, але я написав зразок програми та допис у блозі .
З тих пір, як я писав це ще у 2014 році, виникали різні рішення. Деякі працювали, деякі вважали, що працюють , але мали недоліки (включаючи мої!), І ми, як громада (Android), навчилися жити з наслідками і писали обхідні шляхи для особливих випадків.
Ніколи не припускайте, що один фрагмент коду - це рішення, яке ви шукаєте, це навряд чи так; ще краще, спробуйте зрозуміти, що це робить і чому це робить.
MemoryBoss
Клас ніколи не був на насправді використовується мною як тут написано, це був просто шматок псевдо - код , який стався з роботою.
Якщо ви не маєте поважних причин, щоб ви не використовували нові компоненти архітектури (а їх є деякі, особливо якщо ви орієнтуєтесь на супер-старі apis), тоді продовжуйте їх та використовуйте. Вони далекі від досконалості, але жодного з них не було ComponentCallbacks2
.
ОНОВЛЕННЯ / ПРИМІТКИ (листопад 2015 р.) : Люди зробили два коментарі, по-перше, це >=
слід використовувати замість того, ==
що в документації зазначено, що ви не повинні перевіряти точні значення . Це добре для більшості випадків, але майте на увазі , що якщо ви тільки піклуватися про виконання дещо - що , коли додаток пішов на задній план, ви повинні будете використовувати == і також об'єднати його з іншим розчином (наприклад , активність Lifecycle зворотних викликів), або може не отримати бажаного ефекту. Приклад (і це сталося зі мною) полягає в тому, що якщо ви хочете заблокувативаш додаток із екраном пароля, коли він переходить на задній план (наприклад, 1Password, якщо ви знайомі з ним), ви можете випадково заблокувати додаток, якщо у вас не вистачає пам’яті і раптом тестуєте >= TRIM_MEMORY
, оскільки Android викликає LOW MEMORY
дзвінок, і це вищий за твій. Тому будьте уважні, як / що ви тестуєте.
Крім того, деякі люди запитували про те, як виявити, коли ви повертаєтесь.
Найпростіший спосіб, який я можу придумати, пояснюється нижче, але оскільки деякі люди з ним незнайомі, я додаю тут трохи псевдокоду. Припустимо, що у вас є YourApplication
і MemoryBoss
класи у ваших class BaseActivity extends Activity
(вам потрібно створити його, якщо у вас його немає).
@Override
protected void onStart() {
super.onStart();
if (mApplication.wasInBackground()) {
// HERE YOU CALL THE CODE YOU WANT TO HAPPEN ONLY ONCE WHEN YOUR APP WAS RESUMED FROM BACKGROUND
mApplication.setWasInBackground(false);
}
}
Я рекомендую OnStart, оскільки діалоги можуть призупиняти діяльність, тому, я думаю, ви не хочете, щоб ваша програма думала, що "вона пішла на другий план", якщо все, що ви робили, було відображати діалогове вікно на весь екран, але пробіг може змінюватися.
І це все. Код у блоці if буде виконаний лише один раз , навіть якщо ви перейдете до іншої діяльності, новий (що також extends BaseActivity
) буде повідомляти wasInBackground
, false
так що він не буде виконувати код, поки не onMemoryTrimmed
буде викликаний і прапор знову встановлений на істинний .
Сподіваюся, що це допомагає.
ОНОВЛЕННЯ / ПРИМІТКИ (квітень 2015 р.) : Перш ніж скористатись Копіюванням та вставкою цього коду, зауважте, що я знайшов пару випадків, коли він може бути не на 100% надійним і його потрібно поєднувати з іншими методами для досягнення найкращих результатів. Зокрема, є два відомі випадки, коли onTrimMemory
зворотний виклик не гарантується виконанням:
Якщо ваш телефон блокує екран, коли ваш додаток видно (скажімо, ваш пристрій блокується через nn хвилин), цей зворотний дзвінок не викликається (або не завжди), оскільки екран блокування знаходиться лише на вершині, але ваш додаток все ще "працює", хоч і накрито.
Якщо у Вашого пристрою порівняно мало пам’яті (і під напругою пам’яті), Операційна система, здається, ігнорує цей виклик і переходить безпосередньо до більш критичних рівнів.
Тепер, залежно від того, наскільки важливо вам знати, коли ваш додаток пішов на другий план, вам може, а може і не потрібно, поширювати це рішення разом із відстеженням життєвого циклу діяльності та іншого.
Просто пам’ятайте про вищезазначене та майте хорошу команду з якості;)
Кінець оновлення
Це може бути пізно, але в сендвіч-морозиві (API 14) та вище є надійний метод .
Виявляється, коли у вашої програми немає більш видимого інтерфейсу користувача, спрацьовує зворотний виклик. Зворотний виклик, який можна реалізувати у користувацькому класі, називається ComponentCallbacks2 (так, з двома). Цей зворотний дзвінок доступний лише в API рівня 14 (сендвіч з морозивом) і вище.
Ви в основному отримуєте виклик методу:
public abstract void onTrimMemory (int level)
Рівень 20 або більше конкретно
public static final int TRIM_MEMORY_UI_HIDDEN
Я тестував це, і це завжди працює, тому що рівень 20 - це лише "пропозиція", яку ви, можливо, захочете випустити деякі ресурси, оскільки ваш додаток більше не видно.
Щоб процитувати офіційні документи:
Рівень для onTrimMemory (int): процес демонстрував користувальницький інтерфейс, і він більше не робить цього. Великі асигнування з користувальницьким інтерфейсом повинні бути випущені в цей момент, щоб забезпечити краще управління пам'яттю.
Звичайно, ви повинні здійснити це насправді робити те , що він говорить (чистка пам'ять , яка не була використана в певний час, очистити деякі колекції, які сиділи невикористаними, і т.д. можливості нескінченні (див офіційних документів для інших можливих інших цікавих місць критичні рівні).
Але, що цікаво, це те, що ОС говорить вам: ТОЙ, ваш додаток відійшов на другий план!
Це саме те, що ви хотіли знати в першу чергу.
Як визначити, коли ти повернувся?
Ну це просто, я впевнений, що у вас є "BaseActivity", щоб ви могли використовувати свою функцію onResume (), щоб позначити факт повернення. Тому що єдиний раз, коли ти скажеш, що ти не повернувся, - це коли ти насправді отримуєш дзвінок вищевказаного onTrimMemory
методу.
Це працює. Ви не отримуєте помилкових позитивних результатів. Якщо діяльність поновлюється, ви повертаєтесь у 100% разів. Якщо користувач знову повернеться до спини, ви отримаєте ще один onTrimMemory()
дзвінок.
Потрібно підписати свою діяльність (а ще краще - спеціальний клас).
Найпростіший спосіб гарантувати, що ви завжди отримуєте це, створити такий простий клас, як цей:
public class MemoryBoss implements ComponentCallbacks2 {
@Override
public void onConfigurationChanged(final Configuration newConfig) {
}
@Override
public void onLowMemory() {
}
@Override
public void onTrimMemory(final int level) {
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// We're in the Background
}
// you might as well implement some memory cleanup here and be a nice Android dev.
}
}
Для того, щоб використовувати це, у своїй реалізації програми (у вас є ПРАВА? ) Зробіть щось на кшталт:
MemoryBoss mMemoryBoss;
@Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
mMemoryBoss = new MemoryBoss();
registerComponentCallbacks(mMemoryBoss);
}
}
Якщо ви створюєте, Interface
ви можете додати else
до цього if
і реалізувати ComponentCallbacks
(без 2), що використовується в чомусь нижче API 14. Цей зворотний виклик має лише onLowMemory()
метод і не викликається, коли ви переходите на другий план , але ви повинні використовувати його для обрізання пам'яті .
Тепер запустіть свою програму та натисніть додому. Ваш onTrimMemory(final int level)
метод слід викликати (підказка: додайте журнал).
Останній крок - скасувати реєстрацію від зворотного дзвінка. Можливо, найкраще місце займає onTerminate()
метод вашої програми, але цей метод не викликається на реальному пристрої:
/** * This method is for use in emulated process environments. It will * never be called on a production Android device, where processes are * removed by simply killing them; no user code (including this callback) * is executed when doing so. */
Тому, якщо у вас справді не виникає ситуація, коли ви більше не хочете реєструватися, ви можете ігнорувати це, оскільки ваш процес вмирає на рівні ОС.
Якщо ви вирішите скасувати реєстрацію в якийсь момент (якщо ви, наприклад, надаєте механізм відключення, щоб ваш додаток очистився і помер), ви можете зробити:
unregisterComponentCallbacks(mMemoryBoss);
І це все.
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 було б помилково .
З іншого боку, коли активність виходить на передній план від програми запуску, пробудження пристрою, завершення телефонного дзвінка тощо, більш ніж ймовірно, що завдання таймера, виконане до цієї події, і таким чином, InBackground було встановлено на істинне .
Редагувати: нові компоненти архітектури принесли щось багатообіцяюче: 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 () класу Application
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, а коли він повернеться, Reseume буде викликано у вашій діяльності з налаштуваннями. Якщо у вас є основний код виявлення у вашій основній діяльності, ви пропустите цей випадок використання. Щоб цей код був у всіх ваших заходах без дублювання коду, майте абстрактний клас активності, який розширює Діяльність, і додайте до нього ваш загальний код. Тоді кожна діяльність у вас може розширити цю абстрактну діяльність.
Наприклад:
public abstract AbstractActivity extends Activity {
private static boolean inBackground = false;
@Override
public void onResume() {
if (inBackground) {
// You just came from the background
inBackground = false;
}
else {
// You just returned from another activity within your own app
}
}
@Override
public void onUserLeaveHint() {
inBackground = true;
}
}
public abstract MainActivity extends AbstractActivity {
...
}
public abstract SettingsActivity extends AbstractActivity {
...
}
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
У свою програму додайте зворотний виклик та перевіряйте кореневу активність таким чином:
@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 -foreground-background-liste
Створіть у своїй програмі BaseActivity для всієї активності.
public class BaseActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
public static boolean isAppInFg = false;
public static boolean isScrInFg = false;
public static boolean isChangeScrFg = false;
@Override
protected void onStart() {
if (!isAppInFg) {
isAppInFg = true;
isChangeScrFg = false;
onAppStart();
}
else {
isChangeScrFg = true;
}
isScrInFg = true;
super.onStart();
}
@Override
protected void onStop() {
super.onStop();
if (!isScrInFg || !isChangeScrFg) {
isAppInFg = false;
onAppPause();
}
isScrInFg = false;
}
public void onAppStart() {
// Remove this toast
Toast.makeText(getApplicationContext(), "App in foreground", Toast.LENGTH_LONG).show();
// Your code
}
public void onAppPause() {
// Remove this toast
Toast.makeText(getApplicationContext(), "App in background", Toast.LENGTH_LONG).show();
// Your code
}
}
Тепер використовуйте цю BaseActivity як супер клас всієї вашої діяльності, як-от MainActivity розширює BaseActivity, а onAppStart буде викликано, коли ви запускаєте програму, а onAppPause () буде викликатись, коли програма перейде на задній план з будь-якого екрана.
Це досить просто за допомогою 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()
класі вашого програми ви називаєте це:
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 змінні, як показано нижче, у класі "Додаток".
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
}
}
Як це працює:
Це невеликий трюк, який виконується способом послідовного виклику методів життєвого циклу. Дозвольте мені проаналізувати сценарій.
Припустимо, що користувач запускає додаток і запускається активність A Launcher. Дзвінки з життєвого циклу будуть,
A.onCreate ()
A.onStart () (++ activityReferences == 1) (додаток виходить на передній план)
A.onResume ()
Тепер активність A починається з діяльності B.
A.onPause ()
B.onCreate ()
B.onStart () (++ ActivityReferences == 2)
B.onResume ()
A.onStop () (--activityReferences == 1)
Потім користувач повертається назад із діяльності B,
B.onPause ()
A.onStart () (++ ActivityReferences == 2)
A.onResume ()
B.onStop () (--activityReferences == 1)
B.onDestroy ()
Потім користувач натискає кнопку "Головна",
A.onPause ()
A.onStop () (--activityReferences == 0) (Додаток переходить у фоновий режим)
У випадку, якщо користувач натисне кнопку "Домашня сторінка" від "Діяння В" замість кнопки "Назад", вона все одно буде однаковою, і буде "Референції" 0
. Отже, ми можемо виявити, як додаток переходить до фону.
Отже, яка роль isActivityChangingConfigurations
? Припустімо, у вищенаведеному сценарії діяльність B змінює орієнтацію. Послідовність зворотного дзвінка буде,
B.onPause ()
B.onStop () (--activityReferences == 0) (Додаток переходить у фон ??)
B.onDestroy ()
B.onCreate ()
B.onStart () (++ activityReferences == 1) (Додаток виходить на передній план ??)
B.onResume ()
Тому ми маємо додаткову перевірку, isActivityChangingConfigurations
щоб уникнути сценарію, коли Активність відбувається через зміни Конфігурації.
Я знайшов хороший метод виявити додаток, будь то передній план чи фон. Ось мій код . Сподіваюся, це допоможе тобі.
/**
* 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();
}
}
де the 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()
.
Погляньте на цю історію: 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");
}
}
Сподіваюсь, це допомагає.
EDIT Як альтернатива, тепер ви можете використовувати компонент архітектури, що усвідомлює життєвий цикл.
Оскільки я не знайшов жодного підходу, який також обробляє обертання, не перевіряючи часові позначки, я подумав, що я також поділяюся тим, як ми це робимо зараз у нашому додатку. Єдиним доповненням до цієї відповіді https://stackoverflow.com/a/42679191/5119746 є те, що ми також враховуємо орієнтацію.
class MyApplication : Application(), Application.ActivityLifecycleCallbacks {
// Members
private var mAppIsInBackground = false
private var mCurrentOrientation: Int? = null
private var mOrientationWasChanged = false
private var mResumed = 0
private var mPaused = 0
Тоді для зворотних викликів у нас спочатку є резюме:
// ActivityLifecycleCallbacks
override fun onActivityResumed(activity: Activity?) {
mResumed++
if (mAppIsInBackground) {
// !!! App came from background !!! Insert code
mAppIsInBackground = false
}
mOrientationWasChanged = false
}
І onActivityStopped:
override fun onActivityStopped(activity: Activity?) {
if (mResumed == mPaused && !mOrientationWasChanged) {
// !!! App moved to background !!! Insert code
mAppIsInBackground = true
}
І ось, ось тут додається: Перевірка змін орієнтації:
override fun onConfigurationChanged(newConfig: Configuration) {
if (newConfig.orientation != mCurrentOrientation) {
mCurrentOrientation = newConfig.orientation
mOrientationWasChanged = true
}
super.onConfigurationChanged(newConfig)
}
Це воно. Сподіваюсь, це комусь допоможе :)
Ми можемо розширити це рішення за допомогою 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 */ }
}
}
Ці відповіді не здаються правильними. Ці методи називаються також тоді, коли починається і закінчується інша діяльність. Що ви можете зробити, це зберегти глобальний прапор (так, глобальні люди погані :), і встановлюйте це як істинне щоразу, коли ви починаєте нову діяльність. Встановіть значення false у onCreate кожної діяльності. Потім у onPause ви перевіряєте цей прапор. Якщо це неправда, ваш додаток переходить на другий план, або він стає вбитим.