Я роблю сповіщення в рядку стану у своєму додатку для Android, яке ініціює c2dm. Я не хочу відображати сповіщення, якщо програма запущена. Як визначити, чи запущена програма, і чи є вона на передньому плані?
Я роблю сповіщення в рядку стану у своєму додатку для Android, яке ініціює c2dm. Я не хочу відображати сповіщення, якщо програма запущена. Як визначити, чи запущена програма, і чи є вона на передньому плані?
Відповіді:
Зробіть глобальну змінну, як private boolean mIsInForegroundMode;
і призначте false
значення в onPause()
і true
значення в onResume()
.
Зразок коду:
private boolean mIsInForegroundMode;
@Override
protected void onPause() {
super.onPause();
mIsInForegroundMode = false;
}
@Override
protected void onResume() {
super.onResume();
mIsInForegroundMode = true;
}
// Some function.
public boolean isInForeground() {
return mIsInForegroundMode;
}
Крім того, ви можете перевірити, ActivityManager
які завдання виконуються за getRunningTasks
методом. Потім зверніться до першого завдання (завдання на передньому плані) у списку завдань, що повернувся, якщо це ваше завдання.
Ось приклад коду:
public Notification buildNotification(String arg0, Map<String, String> arg1) {
ActivityManager activityManager = (ActivityManager) appContext.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> services = activityManager
.getRunningTasks(Integer.MAX_VALUE);
boolean isActivityFound = false;
if (services.get(0).topActivity.getPackageName().toString()
.equalsIgnoreCase(appContext.getPackageName().toString())) {
isActivityFound = true;
}
if (isActivityFound) {
return null;
} else {
// write your code to build a notification.
// return the notification you built here
}
}
І не забудьте додати GET_TASKS
дозвіл у файл manifest.xml , щоб мати можливість запустити getRunningTasks()
метод у наведеному вище коді:
<uses-permission android:name="android.permission.GET_TASKS" />
п / с: Якщо ви згодні з цим, зверніть увагу, що цей дозвіл зараз застарілий.
toString()
рядка, що повертається, getPackageName()
зайвий. Крім того, оскільки нас цікавить лише перше завдання, яке повертає getRunningTasks()
, ми можемо пройти 1
замість Integer.MAX_VALUE
.
Це досить стара публікація, але все ще досить актуальна. Вище прийняте рішення може спрацювати, але є помилковим. Як писала Діанн Хакборн:
Ці API не існують для додатків, щоб базувати свій потік інтерфейсу користувача, а для того, щоб показувати користувачеві запущені програми, диспетчеру завдань або тому подібному.
Так, існує список, який зберігається в пам'яті для цих речей. Однак це вимикається в іншому процесі, яким керують потоки, що працюють окремо від вашого, а не те, на що ви можете розраховувати (а) побачити вчасно, щоб прийняти правильне рішення, або (б) мати послідовну картину до моменту повернення. Плюс рішення про те, до якого "наступного" заходу слід переходити, завжди приймається в точці, де має відбутися перемикання, і лише тоді, коли буде здійснено перемикання на той самий момент (коли стан активності буде ненадовго заблокований) насправді знати для таких, якою буде наступна річ.
І впровадження та глобальна поведінка тут не гарантовано залишаться незмінними в майбутньому.
Правильним рішенням є реалізація: ActivityLifeCycleCallbacks .
Для цього в основному потрібен клас програми, і обробник може бути встановлений там, щоб визначити стан ваших дій у програмі.
onPause
та onResume
методу, використаного у прийнятій відповіді?
Як каже Віней, напевно найкращим рішенням (для підтримки новіших версій Android, 14+) є використання ActivityLifecycleCallbacks
в реалізації Application
класу.
package com.telcel.contenedor.appdelegate;
import android.app.Activity;
import android.app.Application.ActivityLifecycleCallbacks;
import android.os.Bundle;
/** Determines global app lifecycle states.
*
* The following is the reference of activities states:
*
* The <b>visible</b> lifetime of an activity happens between a call to onStart()
* until a corresponding call to onStop(). During this time the user can see the
* activity on-screen, though it may not be in the foreground and interacting with
* the user. The onStart() and onStop() methods can be called multiple times, as
* the activity becomes visible and hidden to the user.
*
* The <b>foreground</b> lifetime of an activity happens between a call to onResume()
* until a corresponding call to onPause(). During this time the activity is in front
* of all other activities and interacting with the user. An activity can frequently
* go between the resumed and paused states -- for example when the device goes to
* sleep, when an activity result is delivered, when a new intent is delivered --
* so the code in these methods should be fairly lightweight.
*
* */
public class ApplicationLifecycleManager implements ActivityLifecycleCallbacks {
/** Manages the state of opened vs closed activities, should be 0 or 1.
* It will be 2 if this value is checked between activity B onStart() and
* activity A onStop().
* It could be greater if the top activities are not fullscreen or have
* transparent backgrounds.
*/
private static int visibleActivityCount = 0;
/** Manages the state of opened vs closed activities, should be 0 or 1
* because only one can be in foreground at a time. It will be 2 if this
* value is checked between activity B onResume() and activity A onPause().
*/
private static int foregroundActivityCount = 0;
/** Returns true if app has foreground */
public static boolean isAppInForeground(){
return foregroundActivityCount > 0;
}
/** Returns true if any activity of app is visible (or device is sleep when
* an activity was visible) */
public static boolean isAppVisible(){
return visibleActivityCount > 0;
}
public void onActivityCreated(Activity activity, Bundle bundle) {
}
public void onActivityDestroyed(Activity activity) {
}
public void onActivityResumed(Activity activity) {
foregroundActivityCount ++;
}
public void onActivityPaused(Activity activity) {
foregroundActivityCount --;
}
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
public void onActivityStarted(Activity activity) {
visibleActivityCount ++;
}
public void onActivityStopped(Activity activity) {
visibleActivityCount --;
}
}
А в onCreate()
методі застосування :
registerActivityLifecycleCallbacks(new ApplicationLifecycleManager());
Тоді ApplicationLifecycleManager.isAppVisible()
або ApplicationLifecycleManager.isAppInForeground()
буде використовуватися для знання бажаного стану.
З API 16 ви можете зробити це так:
static boolean shouldShowNotification(Context context) {
RunningAppProcessInfo myProcess = new RunningAppProcessInfo();
ActivityManager.getMyMemoryState(myProcess);
if (myProcess.importance != RunningAppProcessInfo.IMPORTANCE_FOREGROUND)
return true;
KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
// app is in foreground, but if screen is locked show notification anyway
return km.inKeyguardRestrictedInputMode();
}
Трохи очищена версія рішення Гаденкана . Поставте це будь-яке заняття, або, можливо, базовий клас для всіх ваших занять.
protected boolean isRunningInForeground() {
ActivityManager manager =
(ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> tasks = manager.getRunningTasks(1);
if (tasks.isEmpty()) {
return false;
}
String topActivityName = tasks.get(0).topActivity.getPackageName();
return topActivityName.equalsIgnoreCase(getPackageName());
}
Щоб мати змогу зателефонувати getRunningTasks()
, вам потрібно додати це у своєму AndroidManifest.xml
:
<uses-permission android:name="android.permission.GET_TASKS"/>
Зверніть увагу, що ActivityManager.getRunningTasks()
Javadoc каже:
Примітка: цей метод призначений лише для налагодження та представлення користувацьких інтерфейсів управління завданнями. Це ніколи не повинно використовуватися для основної логіки в програмі, наприклад, для вибору між різними способами поведінки на основі інформації, що міститься тут. Подібне використання не підтримується, і, ймовірно, у майбутньому буде порушено.
Зверніть увагу, що getRunningTasks()
це застаріло на рівні API 21 !
На
LOLLIPOP
даний момент цей метод більше не доступний для сторонніх додатків: введення документально орієнтованих останніх означає, що він може передавати інформацію про особу, що телефонує. Для зворотної сумісності він все одно поверне невелику підмножину своїх даних: принаймні власні завдання абонента та, можливо, деякі інші завдання, такі як домашній, які, як відомо, не чутливі.
Тож те, що я писав раніше, є ще більш актуальним:
У багатьох випадках ви, мабуть, можете придумати краще рішення. Наприклад, робити що - то onPause()
і onResume()
, можливо , в BaseActivity для всієї вашої діяльності.
(У нашому випадку ми не хотіли б запускати офлайн-сповіщення, якщо ми не знаходимось на передньому плані, тому в BaseActivity onPause()
ми просто відмовляємося від підписки на RxJava, Subscription
прослуховуючи сигнал "вийшов з мережі".)
Наслідуючи відповідь Гаденкана, мені потрібно було щось подібне, щоб я міг зрозуміти, чи не працює моя програма на передньому плані, але мені потрібно було щось, що було б широким і не вимагало від мене встановлення / зняття прапорів у всій моїй програмі.
Код Гаденкана в значній мірі вдарив по цвяху, але це було не в моєму власному стилі, і я відчував, що може бути акуратніше, тому в моєму додатку це стисло до цього.
if (!context.getPackageName().equalsIgnoreCase(((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getRunningTasks(1).get(0).topActivity.getPackageName()))
{
// App is not in the foreground
}
(Додаткова примітка: Ви можете просто видалити!, Якщо хочете, щоб чек працював навпаки)
Хоча при такому підході потрібен GET_TASKS
дозвіл.
Запустивши бібліотеку підтримки версії 26, ви можете використовувати ProcessLifecycleOwner для визначення поточного стану програми, просто додайте її до своїх залежностей, як описано тут , наприклад:
dependencies {
def lifecycle_version = "1.1.1"
// ViewModel and LiveData
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
// alternatively - Lifecycles only (no ViewModel or LiveData).
// Support library depends on this lightweight import
implementation "android.arch.lifecycle:runtime:$lifecycle_version"
annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version" // use kapt for Kotlin
}
, Тепер ви можете запитувати, ProcessLifecycleOwner
коли ви хочете перевірити стан програми, наприклад, щоб перевірити, чи працює програма на передньому плані, вам просто потрібно зробити це:
boolean isAppInForeground = ProcessLifecycleOwner.get().getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED);
if(!isAppInForeground)
//Show Notification in status bar
implementation 'androidx.lifecycle:lifecycle-process:2.2.0'
у своєму проекті gradle.
На основі різних відповідей та коментарів, ось більш вкладена версія, яку ви можете додати до допоміжного класу:
public static boolean isAppInForeground(Context context) {
List<RunningTaskInfo> task =
((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE))
.getRunningTasks(1);
if (task.isEmpty()) {
return false;
}
return task
.get(0)
.topActivity
.getPackageName()
.equalsIgnoreCase(context.getPackageName());
}
Як зазначалося в інших відповідях, вам потрібно додати наступний дозвіл до вашого AndroidManifest.xml
.
<uses-permission android:name="android.permission.GET_TASKS"/>
Я хотів би додати, що більш безпечним способом зробити це - ніж перевіряти, чи ваш додаток у фоновому режимі перед створенням сповіщення - є просто вимкнути та ввімкнути Broadcast Receiver onPause () та onResume () відповідно.
Цей метод надає вам більше контролю над фактичною логікою програми і, швидше за все, не зміниться в майбутньому.
@Override
protected void onPause() {
unregisterReceiver(mHandleMessageReceiver);
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
registerReceiver(mHandleMessageReceiver, new IntentFilter(DISPLAY_MESSAGE_ACTION));
}
Я знайшов більш простий і точний спосіб перевірити, чи додаток знаходиться на передньому плані чи на задньому плані, зіставляючи дії з логічним значенням.
Перевірте повну суть тут
Ось код для простого простого рішення, описаного вище @ user2690455. Хоча це виглядає дещо багатослівно, ви загалом побачите, що насправді він досить легкий
У моєму випадку ми також використовуємо AppCompatActivity, тому мені довелося мати 2 базових класи.
public class BaseActivity extends Activity {
/**
* Let field be set only in base class
* All callers must use accessors,
* and then it's not up to them to manage state.
*
* Making it static since ..
* 1. It needs to be used across two base classes
* 2. It's a singleton state in the app
*/
private static boolean IS_APP_IN_BACKGROUND = false;
@Override
protected void onResume() {
super.onResume();
BaseActivity.onResumeAppTracking(this);
BaseActivity.setAppInBackgroundFalse();
}
@Override
protected void onStop() {
super.onStop();
BaseActivity.setAppInBackgroundTrue();
}
@Override
protected void onPause() {
super.onPause();
BaseActivity.setAppInBackgroundFalse();
}
protected static void onResumeAppTracking(Activity activity) {
if (BaseActivity.isAppInBackground()) {
// do requirements for returning app to foreground
}
}
protected static void setAppInBackgroundFalse() {
IS_APP_IN_BACKGROUND = false;
}
protected static void setAppInBackgroundTrue() {
IS_APP_IN_BACKGROUND = true;
}
protected static boolean isAppInBackground() {
return IS_APP_IN_BACKGROUND;
}
}
Це корисно лише тоді, коли ви хочете виконати якусь дію саме тоді, коли ваша діяльність починається, і там, де ви хочете перевірити, чи програма на передньому плані чи у фоновому режимі.
Замість використання Менеджера активностей існує простий прийом, який Ви можете зробити за допомогою коду. Якщо ви уважно спостерігаєте за циклом діяльності, потік між двома видами діяльності та переднім планом на задній план виглядає наступним чином. Припустимо, що А і В - це дві діяльності.
При переході від A до B: 1. викликається onPause () A 2. onResume () B називається 3. Викликається onStop () A, коли B повністю відновлюється
Коли програма переходить у фоновий режим: 1. називається onPause () A, 2. викликається onStop () A
Ви можете виявити свою фонову подію, просто ввімкнувши прапор.
Зробіть абстрактне заняття та розширте його від інших видів діяльності, щоб вам не довелося копіювати код для всіх інших видів діяльності, де б вам не потрібно було фонової події.
В абстрактній діяльності створюйте прапор isAppInBackground.
У методі onCreate (): isAppInBackground = false;
У методі onPause (): isAppInBackground = false;
У методі onStop (): isAppInBackground = true;
Вам просто потрібно перевірити у своєму onResume (), чи isAppInBackground відповідає дійсності. n після перевірки прапора, а потім знову встановіть isAppInBackground = false
Для переходу між двома діями, оскільки onSTop () першого завжди викликається після відновлення другої активності, прапор ніколи не буде істинним, а коли програма перебуває у фоновому режимі, onStop () буде викликатися відразу після onPause, а отже, прапор буде істинним, коли ви відкриєте програму пізніше.
Хоча у цього підходу є ще один сценарій. Якщо будь-який екран вашого додатка вже відкритий, і ви ставите мобільний в режим очікування, то через деякий час мобільний перейде в режим сну, а коли ви розблокуєте мобільний, це буде оброблено у фоновому режимі.
Ось метод, який я використовую (і допоміжний метод):
private boolean checkIfAppIsRunningInForeground() {
ActivityManager activityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
for(ActivityManager.RunningAppProcessInfo appProcessInfo : activityManager.getRunningAppProcesses()) {
if(appProcessInfo.processName.contains(this.getPackageName())) {
return checkIfAppIsRunningInForegroundByAppImportance(appProcessInfo.importance);
}
}
return false;
}
private boolean checkIfAppIsRunningInForegroundByAppImportance(int appImportance) {
switch (appImportance) {
//user is aware of app
case ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND:
case ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE:
return true;
//user is not aware of app
case ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND:
case ActivityManager.RunningAppProcessInfo.IMPORTANCE_EMPTY:
case ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE:
case ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE:
default:
return false;
}
}
Для цього не існує загального зворотного виклику, але для кожної діяльності це onStop (). Не потрібно возитися з атомним int. Просто встановіть глобальний номер int із кількістю розпочатих дій, у кожному з дій збільшуйте його в onStart () та зменшуйте в onStop ().
Слідуйте за цим
public static boolean isAppRunning(Context context) {
// check with the first task(task in the foreground)
// in the returned list of tasks
ActivityManager activityManager = (ActivityManager)
context.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> services =
activityManager.getRunningTasks(Integer.MAX_VALUE);
if
(services.get(0).topActivity.getPackageName().toString().equalsIgnoreCase(context.getPackageName().toString()))
{
return true;
}
return false;
}
Попередні підходи, згадані тут, не є оптимальними. Підхід на основі завдань вимагає дозволу, який може бути небажаним, і "логічний" підхід схильний до одночасних модифікацій.
Підхід, який я використовую і який (я вважаю) працює досить добре в більшості випадків:
Майте клас "MainApplication" для відстеження підрахунку активності в AtomicInteger :
import android.app.Application;
import java.util.concurrent.atomic.AtomicInteger;
public class MainApplication extends Application {
static class ActivityCounter {
private static AtomicInteger ACTIVITY_COUNT = new AtomicInteger(0);
public static boolean isAppActive() {
return ACTIVITY_COUNT.get() > 0;
}
public static void activityStarted() {
ACTIVITY_COUNT.incrementAndGet();
}
public static void activityStopped() {
ACTIVITY_COUNT.decrementAndGet();
}
}
}
І створити базовий клас Activity, який би поширювались на інші дії:
import android.app.Activity;
import android.support.annotation.CallSuper;
public class TestActivity extends Activity {
@Override
@CallSuper
protected void onStart() {
MainApplication.ActivityCounter.activityStarted();
super.onStart();
}
@Override
@CallSuper
protected void onStop() {
MainApplication.ActivityCounter.activityStopped();
super.onStop();
}
}