Wykonuję powiadomienie na pasku stanu w mojej aplikacji na Androida, które jest wyzwalane przez c2dm. Nie chcę wyświetlać powiadomienia, jeśli aplikacja jest uruchomiona. Jak określić, czy aplikacja działa i znajduje się na pierwszym planie?
Wykonuję powiadomienie na pasku stanu w mojej aplikacji na Androida, które jest wyzwalane przez c2dm. Nie chcę wyświetlać powiadomienia, jeśli aplikacja jest uruchomiona. Jak określić, czy aplikacja działa i znajduje się na pierwszym planie?
Odpowiedzi:
Utwórz zmienną globalną, taką jak private boolean mIsInForegroundMode;i przypisz falsewartość w onPause()i truewartość w onResume().
Przykładowy kod:
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;
}
Alternatywnie możesz sprawdzić, ActivityManagerjakie zadania są uruchomione według getRunningTasksmetody. Następnie sprawdź z pierwszym zadaniem (zadanie na pierwszym planie) na zwróconej Liście zadań, jeśli jest to Twoje zadanie.
Oto przykład kodu:
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
}
}
I nie zapomnij dodać GET_TASKSuprawnienia w pliku manifest.xml , aby móc uruchomić getRunningTasks()metodę w powyższym kodzie:
<uses-permission android:name="android.permission.GET_TASKS" />
p / s: Jeśli zgadzasz się w ten sposób, pamiętaj, że to uprawnienie jest teraz przestarzałe.
toString()ciągu zwróconego przez getPackageName()jest zbędne. Ponieważ interesuje nas tylko pierwsze zwrócone zadanie getRunningTasks(), możemy przejść 1zamiast Integer.MAX_VALUE.
To dość stary post, ale nadal dość aktualny. Powyższe przyjęte rozwiązanie może działać, ale jest błędne. Jak napisała Dianne Hackborn:
Te interfejsy API nie służą aplikacjom, na których mogą opierać swój przepływ interfejsu użytkownika, ale służą do wykonywania takich czynności, jak pokazywanie użytkownikowi uruchomionych aplikacji, menedżera zadań itp.
Tak, jest lista tych rzeczy przechowywana w pamięci. Jest to jednak wyłączone w innym procesie, zarządzanym przez wątki działające niezależnie od twojego i nie jest to coś, na co możesz liczyć (a) dostrzec na czas, aby podjąć właściwą decyzję lub (b) mieć spójny obraz do czasu powrotu. Ponadto decyzja o tym, do jakiej „następnej” czynności należy przejść, jest zawsze podejmowana w miejscu, w którym ma nastąpić zmiana, i dopiero w tym dokładnym punkcie (gdzie stan aktywności jest na krótko zablokowany, aby dokonać zmiany) właściwie wiem, co będzie dalej.
Nie ma gwarancji, że implementacja i zachowanie globalne pozostaną takie same w przyszłości.
Prawidłowym rozwiązaniem jest wdrożenie: ActivityLifeCycleCallbacks .
Zasadniczo wymaga to klasy aplikacji i można tam ustawić procedurę obsługi, aby zidentyfikować stan działań w aplikacji.
onPausei onResumemetody zastosowanej w zaakceptowanej odpowiedzi?
Jak mówi Vinay, prawdopodobnie najlepszym rozwiązaniem (do obsługi nowszych wersji Androida, 14+) jest użycie ActivityLifecycleCallbacksw Applicationimplementacji klasy.
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 --;
}
}
A w onCreate()metodzie aplikacji :
registerActivityLifecycleCallbacks(new ApplicationLifecycleManager());
Następnie ApplicationLifecycleManager.isAppVisible()lub ApplicationLifecycleManager.isAppInForeground()byłby używany do poznania pożądanego stanu.
Od API 16 możesz to zrobić w ten sposób:
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();
}
Lekko poprawiona wersja rozwiązania Gadenkana . Umieść to dowolne działanie lub może klasę bazową dla wszystkich swoich działań.
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());
}
Aby móc dzwonić getRunningTasks(), musisz dodać to w AndroidManifest.xml:
<uses-permission android:name="android.permission.GET_TASKS"/>
Zwróć jednak uwagę na to, co ActivityManager.getRunningTasks() mówi Javadoc:
Uwaga: ta metoda jest przeznaczona tylko do debugowania i prezentowania interfejsów użytkownika do zarządzania zadaniami. Nie powinno to być nigdy używane w przypadku podstawowej logiki aplikacji, takiej jak podejmowanie decyzji dotyczących różnych zachowań na podstawie informacji znajdujących się tutaj. Takie zastosowania nie są obsługiwane i prawdopodobnie ulegną awarii w przyszłości.
Zauważ, że getRunningTasks()został wycofany na poziomie API 21 !
Od
LOLLIPOPtej pory ta metoda nie jest już dostępna dla aplikacji innych firm: wprowadzenie ostatnich zorientowanych na dokumenty oznacza, że może ona spowodować wyciek informacji o osobie do dzwoniącego. W celu zapewnienia kompatybilności wstecznej nadal zwróci niewielki podzbiór swoich danych: przynajmniej własne zadania wywołującego i prawdopodobnie inne zadania, takie jak dom, o których wiadomo, że nie są wrażliwe.
Więc to, co napisałem wcześniej, jest jeszcze bardziej istotne:
W wielu przypadkach prawdopodobnie możesz znaleźć lepsze rozwiązanie. Na przykład robienie czegoś w onPause()i onResume()być może w BaseActivity dla wszystkich działań.
(W naszym przypadku nie chcieliśmy, aby działanie alarmowe w trybie offline było uruchamiane, jeśli nie jesteśmy na pierwszym planie, więc w BaseActivity onPause()po prostu wypisujemy się z RxJava Subscriptionnasłuchującego sygnału „poszedł offline”.)
W odpowiedzi na odpowiedź Gadenkana potrzebowałem czegoś takiego, żebym mógł stwierdzić, czy moja aplikacja nie działa na pierwszym planie, ale potrzebowałem czegoś, co było szerokie na aplikację i nie wymagało ustawiania / wyłączania flag w całej aplikacji.
Kod Gadenkana trafił w sedno, ale nie był w moim własnym stylu i uważałem, że może być uporządkowany, więc w mojej aplikacji jest do tego skondensowany.
if (!context.getPackageName().equalsIgnoreCase(((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getRunningTasks(1).get(0).topActivity.getPackageName()))
{
// App is not in the foreground
}
(Uwaga dodatkowa: możesz po prostu usunąć!, Jeśli chcesz, aby czek działał w drugą stronę)
Chociaż przy takim podejściu potrzebujesz GET_TASKSpozwolenia.
Począwszy od wersji 26 biblioteki obsługi, możesz użyć ProcessLifecycleOwner do określenia bieżącego stanu aplikacji, po prostu dodaj ją do swoich zależności, jak opisano tutaj , na przykład:
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
}
Teraz możesz zapytać, ProcessLifecycleOwnerkiedy chcesz sprawdzić stan aplikacji, na przykład, aby sprawdzić, czy aplikacja działa na pierwszym planie, wystarczy zrobić to:
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'w gradle projektu.
Na podstawie różnych odpowiedzi i komentarzy, oto bardziej wbudowana wersja, którą możesz dodać do klasy pomocniczej:
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());
}
Jak wspomniano w innych odpowiedziach, musisz dodać następujące uprawnienia do swojego AndroidManifest.xml.
<uses-permission android:name="android.permission.GET_TASKS"/>
Chciałbym dodać, że bezpieczniejszym sposobem na zrobienie tego - niż sprawdzanie, czy Twoja aplikacja działa w tle przed utworzeniem powiadomienia - jest po prostu wyłączenie i włączenie odbiornika transmisji onPause () i onResume ().
Ta metoda zapewnia większą kontrolę nad rzeczywistą logiką aplikacji i prawdopodobnie nie ulegnie zmianie w przyszłości.
@Override
protected void onPause() {
unregisterReceiver(mHandleMessageReceiver);
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
registerReceiver(mHandleMessageReceiver, new IntentFilter(DISPLAY_MESSAGE_ACTION));
}
Znalazłem prostszy i dokładniejszy sposób sprawdzenia, czy aplikacja działa na pierwszym planie, czy w tle, mapując działania na wartości logiczne.
Sprawdź całą treść tutaj
Oto kod ładnego prostego rozwiązania opisanego powyżej przez @ user2690455. Chociaż wygląda trochę rozwlekle, zobaczysz, że ogólnie jest dość lekki
W moim przypadku używamy również AppCompatActivity, więc musiałem mieć 2 klasy bazowe.
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;
}
}
Jest to przydatne tylko wtedy, gdy chcesz wykonać jakąś czynność tuż po rozpoczęciu działania i tam, gdzie chcesz sprawdzić, czy aplikacja jest na pierwszym planie, czy w tle.
Zamiast korzystać z Menedżera aktywności, istnieje prosta sztuczka, którą można wykonać za pomocą kodu. Jeśli uważnie obserwujesz cykl aktywności, przepływ między dwoma działaniami i pierwszym planem w tle jest następujący. Załóżmy, że A i B to dwie czynności.
Kiedy następuje przejście z A do B: 1. wywoływana jest funkcja onPause () z A 2. Wywoływana jest metoda onResume () z B 3. Wywoływana jest funkcja onStop () z A, gdy B jest w pełni wznowiona
Kiedy aplikacja przechodzi w tło: 1. wywoływana jest funkcja onPause () z A 2. Wywoływana jest metoda onStop () z A.
Możesz wykryć zdarzenie w tle, po prostu umieszczając flagę w działaniu.
Utwórz abstrakcyjne działanie i rozszerz je na inne, tak aby nie trzeba było kopiować i wklejać kodu dla wszystkich innych działań, gdziekolwiek potrzebujesz zdarzenia w tle.
W aktywności abstrakcyjnej utwórz flagę isAppInBackground.
W metodzie onCreate (): isAppInBackground = false;
W metodzie onPause (): isAppInBackground = false;
W metodzie onStop (): isAppInBackground = true;
Musisz tylko sprawdzić w swojej onResume (), czy isAppInBackground jest prawdziwe. n po sprawdzeniu flagi ustaw ponownie isAppInBackground = false
Dla przejścia między dwoma działaniami, ponieważ onSTop () of first będzie zawsze wywoływany po wznowieniu drugiego działania, flag nigdy nie będzie true, a gdy aplikacja jest w tle, onStop () aktywności zostanie wywołana natychmiast po onPause i dlatego flaga będzie miała wartość true, gdy otworzysz aplikację później.
W tym podejściu jest jeszcze jeden scenariusz. Jeśli którykolwiek z ekranów aplikacji jest już otwarty, a telefon komórkowy zostanie przełączony w stan bezczynności, po pewnym czasie telefon przejdzie w tryb uśpienia, a po odblokowaniu telefonu komórkowego zostanie potraktowany w tle.
Oto metoda, której używam (i metoda pomocnicza):
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;
}
}
Nie ma do tego globalnego wywołania zwrotnego, ale dla każdego działania jest to onStop (). Nie musisz zadzierać z atomowym int. Wystarczy mieć globalną liczbę int z liczbą rozpoczętych działań, w każdym działaniu zwiększaj ją w onStart () i zmniejszaj w onStop ().
Postępuj zgodnie z tym
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;
}
Wspomniane wcześniej podejścia nie są optymalne. Podejście oparte na zadaniach wymaga pozwolenia, które może nie być pożądane, a podejście „logiczne” jest podatne na jednoczesne błędy związane z modyfikacjami.
Podejście, które stosuję i które (moim zdaniem) działa całkiem dobrze w większości przypadków:
Miej klasę „MainApplication”, aby śledzić liczbę działań w 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();
}
}
}
I utwórz podstawową klasę Activity, którą rozszerzyłyby inne działania:
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();
}
}