Я делаю уведомление в строке состояния в своем приложении для 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" />
p / s: Если вы согласны с этим, обратите внимание, что это разрешение теперь устарело.
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();
}
Немного доработанная версия решения Гаденкана . Поместите в него любое Activity или, возможно, базовый класс для всех ваших Activity.
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
данный момент этот метод больше не доступен для сторонних приложений: внедрение ориентированных на документы последних означает, что он может передавать информацию о личности вызывающему. Для обратной совместимости он по-прежнему будет возвращать небольшое подмножество своих данных: по крайней мере, собственные задачи вызывающего абонента и, возможно, некоторые другие задачи, такие как home, которые, как известно, не являются конфиденциальными.
Так что то, что я написал ранее, даже более актуально:
Во многих случаях вы, вероятно, сможете найти лучшее решение. Например, выполнение чего-либо в 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"/>
Я хотел бы добавить, что более безопасный способ сделать это, чем проверка того, находится ли ваше приложение в фоновом режиме перед созданием уведомления, - это просто отключить и включить широковещательный приемник 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 - два вида деятельности.
При переходе от A к B: 1. вызывается onPause () для A 2. вызывается onResume () для B 3. onStop () для A вызывается, когда B полностью возобновляется
Когда приложение переходит в фоновый режим: 1. вызывается onPause () из A 2. вызывается onStop () из A
Вы можете обнаружить фоновое событие, просто установив флажок в Activity.
Сделайте абстрактное действие и расширьте его из других ваших действий, чтобы вам не пришлось копировать и вставлять код для всех других действий, где вам нужно фоновое событие.
В абстрактной активности создать флаг 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();
}
}