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?
android
android-activity
Andrew Thomas
źródło
źródło
Odpowiedzi:
Utwórz zmienną globalną, taką jak
private boolean mIsInForegroundMode;
i przypiszfalse
wartość wonPause()
itrue
wartość wonResume()
.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; }
źródło
Alternatywnie możesz sprawdzić,
ActivityManager
jakie zadania są uruchomione wedługgetRunningTasks
metody. 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_TASKS
uprawnienia 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.
źródło
toString()
ciągu zwróconego przezgetPackageName()
jest zbędne. Ponieważ interesuje nas tylko pierwsze zwrócone zadaniegetRunningTasks()
, możemy przejść1
zamiastInteger.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.
źródło
onPause
ionResume
metody zastosowanej w zaakceptowanej odpowiedzi?Jak mówi Vinay, prawdopodobnie najlepszym rozwiązaniem (do obsługi nowszych wersji Androida, 14+) jest użycie
ActivityLifecycleCallbacks
wApplication
implementacji 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()
lubApplicationLifecycleManager.isAppInForeground()
byłby używany do poznania pożądanego stanu.źródło
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(); }
źródło
FYI, jeśli używasz rozwiązania Gadenkan (co jest świetne!), Nie zapomnij dodać
<uses-permission android:name="android.permission.GET_TASKS" />
do manifestu.
źródło
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 wAndroidManifest.xml
:<uses-permission android:name="android.permission.GET_TASKS"/>
Zwróć jednak uwagę na to, co
ActivityManager.getRunningTasks()
mówi Javadoc:Aktualizacja (luty 2015)
Zauważ, że
getRunningTasks()
został wycofany na poziomie API 21 !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()
ionResume()
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 RxJavaSubscription
nasłuchującego sygnału „poszedł offline”.)źródło
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_TASKS
pozwolenia.źródło
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ć,
ProcessLifecycleOwner
kiedy 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
źródło
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"/>
źródło
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)); }
źródło
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
źródło
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; } }
źródło
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.
źródło
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; } }
źródło
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
źródło
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; }
źródło
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(); } }
źródło