Określanie bieżącej aplikacji na pierwszym planie na podstawie zadania lub usługi w tle


112

Chciałbym mieć jedną aplikację działającą w tle, która wie, kiedy jest uruchomiona którakolwiek z wbudowanych aplikacji (wiadomości, kontakty itp.).

Więc moje pytania to:

  1. Jak uruchomić aplikację w tle.

  2. Skąd moja aplikacja działająca w tle może wiedzieć, jaka aplikacja jest aktualnie uruchomiona na pierwszym planie.

Mile widziane będą odpowiedzi osób z doświadczeniem.


Nie sądzę, żebyś dostarczył wystarczającego wyjaśnienia tego, co próbujesz zrobić. Co próbuje zrobić Twoja aplikacja działająca w tle? W jaki sposób powinien mieć możliwość interakcji z użytkownikiem? Dlaczego musisz wiedzieć, jaka jest aktualna aplikacja na pierwszym planie? Itd.
Charles Duffy


1
do wykrywania aplikacji pierwszego planu możesz użyć github.com/ricvalerio/foregroundappchecker
rvalerio

Odpowiedzi:


104

W odniesieniu do „2. Skąd moja aplikacja w tle może wiedzieć, jaka aplikacja jest aktualnie uruchomiona na pierwszym planie”.

NIE używaj tej getRunningAppProcesses()metody, ponieważ zwraca to wszelkiego rodzaju śmieci systemowe z mojego doświadczenia, a otrzymasz wiele wyników, które mają RunningAppProcessInfo.IMPORTANCE_FOREGROUND. Użyj getRunningTasks()zamiast tego

Oto kod, którego używam w mojej usłudze, aby zidentyfikować bieżącą aplikację na pierwszym planie, jest naprawdę łatwy:

ActivityManager am = (ActivityManager) AppService.this.getSystemService(ACTIVITY_SERVICE);
// The first in the list of RunningTasks is always the foreground task.
RunningTaskInfo foregroundTaskInfo = am.getRunningTasks(1).get(0);

To wszystko, możesz łatwo uzyskać dostęp do szczegółów aplikacji / aktywności na pierwszym planie:

String foregroundTaskPackageName = foregroundTaskInfo .topActivity.getPackageName();
PackageManager pm = AppService.this.getPackageManager();
PackageInfo foregroundAppPackageInfo = pm.getPackageInfo(foregroundTaskPackageName, 0);
String foregroundTaskAppName = foregroundAppPackageInfo.applicationInfo.loadLabel(pm).toString();

Wymaga to dodatkowego zezwolenia w działaniu menifestu i działa doskonale.

<uses-permission android:name="android.permission.GET_TASKS" />

1
Powinno to być oznaczone jako poprawna odpowiedź. Wymagane pozwolenie: android.permission.GET_TASKS jest jedyną bardzo drobną wadą innych metod, które mogą tego uniknąć.
brandall

3
EDYTUJ POWYŻEJ: Uwaga: ta metoda jest przeznaczona tylko do debugowania i prezentowania interfejsów użytkownika do zarządzania zadaniami. <- Właśnie zauważyłem to w dokumencie Java. Więc ta metoda może ulec zmianie bez powiadomienia w przyszłości.
brandall

47
Uwaga: getRunningTasks()jest przestarzały w API 21 (Lollipop) - developer.android.com/reference/android/app/ ...
dtyler

@dtyler, w takim razie OK. Czy zamiast tego jest jakaś inna metoda? Chociaż Google nie chce, aby aplikacje innych firm uzyskiwały poufne informacje, mam klucz platformy urządzenia. Czy jest jakaś inna metoda, która robi to samo, jeśli mam uprawnienia systemowe?
Yeung,

Jest sposób na użycie "API statystyki użytkowania" wprowadzonego w 21
KunalK

38

Musiałem znaleźć właściwe rozwiązanie na własnej skórze. poniższy kod jest częścią cyanogenmod7 (modyfikacje tabletu) i jest testowany na Androidzie 2.3.3 / gingerbread.

metody:

  • getForegroundApp - zwraca aplikację pierwszoplanową.
  • getActivityForApp - zwraca aktywność znalezionej aplikacji.
  • isStillActive - określa, czy wcześniej znaleziona aplikacja jest nadal aktywną aplikacją.
  • isRunningService - funkcja pomocnicza dla getForegroundApp

mam nadzieję, że to rozwiąże ten problem w całym zakresie (:

private RunningAppProcessInfo getForegroundApp() {
    RunningAppProcessInfo result=null, info=null;

    if(mActivityManager==null)
        mActivityManager = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
    List <RunningAppProcessInfo> l = mActivityManager.getRunningAppProcesses();
    Iterator <RunningAppProcessInfo> i = l.iterator();
    while(i.hasNext()){
        info = i.next();
        if(info.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND
                && !isRunningService(info.processName)){
            result=info;
            break;
        }
    }
    return result;
}

private ComponentName getActivityForApp(RunningAppProcessInfo target){
    ComponentName result=null;
    ActivityManager.RunningTaskInfo info;

    if(target==null)
        return null;

    if(mActivityManager==null)
        mActivityManager = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
    List <ActivityManager.RunningTaskInfo> l = mActivityManager.getRunningTasks(9999);
    Iterator <ActivityManager.RunningTaskInfo> i = l.iterator();

    while(i.hasNext()){
        info=i.next();
        if(info.baseActivity.getPackageName().equals(target.processName)){
            result=info.topActivity;
            break;
        }
    }

    return result;
}

private boolean isStillActive(RunningAppProcessInfo process, ComponentName activity)
{
    // activity can be null in cases, where one app starts another. for example, astro
    // starting rock player when a move file was clicked. we dont have an activity then,
    // but the package exits as soon as back is hit. so we can ignore the activity
    // in this case
    if(process==null)
        return false;

    RunningAppProcessInfo currentFg=getForegroundApp();
    ComponentName currentActivity=getActivityForApp(currentFg);

    if(currentFg!=null && currentFg.processName.equals(process.processName) &&
            (activity==null || currentActivity.compareTo(activity)==0))
        return true;

    Slog.i(TAG, "isStillActive returns false - CallerProcess: " + process.processName + " CurrentProcess: "
            + (currentFg==null ? "null" : currentFg.processName) + " CallerActivity:" + (activity==null ? "null" : activity.toString())
            + " CurrentActivity: " + (currentActivity==null ? "null" : currentActivity.toString()));
    return false;
}

private boolean isRunningService(String processname){
    if(processname==null || processname.isEmpty())
        return false;

    RunningServiceInfo service;

    if(mActivityManager==null)
        mActivityManager = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
    List <RunningServiceInfo> l = mActivityManager.getRunningServices(9999);
    Iterator <RunningServiceInfo> i = l.iterator();
    while(i.hasNext()){
        service = i.next();
        if(service.process.equals(processname))
            return true;
    }

    return false;
}

1
jeśli nie masz nic przeciwko pytaniu, dlaczego potrzebujesz metod, aby sprawdzić, czy usługa działa, czy jest nadal aktywna, i aby uzyskać aktywność tylko po to, aby uzyskać aplikację na pierwszym planie?

2
przepraszam, ale to nie działa. Niektóre aplikacje są filtrowane bez powodu, myślę, że dzieje się tak, gdy uruchamiają jakąś usługę. Tak więc RunService je wyrzuca.
seb

15
Niestety metoda getRunningTasks () jest przestarzała od czasu systemu Android L (API 20). Od wersji L 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.
Sam Lu

Czy ktoś wie, jak takie podejście jest lepsze niż zwykłe działanie mActivityManager.getRunningTasks(1).get(0).topActivity?
Sam

35

Wypróbuj następujący kod:

ActivityManager activityManager = (ActivityManager) newContext.getSystemService( Context.ACTIVITY_SERVICE );
List<RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
for(RunningAppProcessInfo appProcess : appProcesses){
    if(appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND){
        Log.i("Foreground App", appProcess.processName);
    }
}

Nazwa procesu to nazwa pakietu aplikacji działającej na pierwszym planie. Porównaj go z nazwą pakietu swojej aplikacji. Jeśli jest taki sam, oznacza to, że aplikacja działa na pierwszym planie.

Mam nadzieję, że to odpowiada na twoje pytanie.


7
Począwszy od wersji L systemu Android jest to jedyne rozwiązanie, które działa, ponieważ metody używane w innych rozwiązaniach stały się przestarzałe.
androidGuy

4
Jednak to nie działa w przypadku, gdy aplikacja jest na pierwszym planie, a ekran jest zablokowany
Krit

Poprawna odpowiedź powinna być zaznaczona
A_rmas

1
Czy wymaga dodatkowej zgody, takiej jak zaakceptowana odpowiedź?
wonsuc

1
To nie jest do końca poprawne. Nazwa procesu nie zawsze jest taka sama, jak nazwa odpowiedniego pakietu aplikacji.
Sam,

25

Od Lollipopa to się zmieniło. Proszę znaleźć poniższy kod, zanim ten użytkownik będzie musiał przejść Ustawienia -> Bezpieczeństwo -> (Przewiń w dół do ostatniego) Aplikacje z dostępem do użytkowania -> Nadaj uprawnienia naszej aplikacji

private void printForegroundTask() {
    String currentApp = "NULL";
    if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
        UsageStatsManager usm = (UsageStatsManager) this.getSystemService(Context.USAGE_STATS_SERVICE);
        long time = System.currentTimeMillis();
        List<UsageStats> appList = usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY,  time - 1000*1000, time);
        if (appList != null && appList.size() > 0) {
            SortedMap<Long, UsageStats> mySortedMap = new TreeMap<Long, UsageStats>();
            for (UsageStats usageStats : appList) {
                mySortedMap.put(usageStats.getLastTimeUsed(), usageStats);
            }
            if (mySortedMap != null && !mySortedMap.isEmpty()) {
                currentApp = mySortedMap.get(mySortedMap.lastKey()).getPackageName();
            }
        }
    } else {
        ActivityManager am = (ActivityManager)this.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> tasks = am.getRunningAppProcesses();
        currentApp = tasks.get(0).processName;
    }

    Log.e(TAG, "Current App in foreground is: " + currentApp);
}

1
czy te statystyki użytkowania nie mogą być ustawione programowo?
rubmz

@rubmz Czy masz jakieś rozwiązanie w tej sprawie?
VVB

1
w moim doogee z Androidem 5.1 nie ma opcji „Nadaj uprawnienia naszej aplikacji”
djdance

To nie daje w 100% dokładnych wyników ... tak jest przez większość czasu
CamHart

1
To nie pokaże ostatniej aplikacji na pierwszym planie, ale może ostatnią „używaną” w pewien sposób - jeśli opuścisz działanie i przejdziesz do innego, pociągnij menu uruchamiania z góry i zwolnij je, będziesz w trybie „ zła ”aktywność, dopóki nie przejdziesz do innej. Nie ma znaczenia, czy przewijasz ekran, czy nie, zgłasza niewłaściwą aktywność, dopóki nie uruchomisz kolejnej.
Azurlake

9

W przypadkach, gdy musimy sprawdzić z naszej własnej usługi / wątku w tle, czy nasza aplikacja jest na pierwszym planie, czy nie. Tak to zaimplementowałem i działa dobrze:

public class TestApplication extends Application implements Application.ActivityLifecycleCallbacks {

    public static WeakReference<Activity> foregroundActivityRef = null;

    @Override
    public void onActivityStarted(Activity activity) {
        foregroundActivityRef = new WeakReference<>(activity);
    }

    @Override
    public void onActivityStopped(Activity activity) {
        if (foregroundActivityRef != null && foregroundActivityRef.get() == activity) {
            foregroundActivityRef = null;
        }
    }

    // IMPLEMENT OTHER CALLBACK METHODS
}

Teraz, aby sprawdzić z innych klas, czy aplikacja jest na pierwszym planie, czy nie, po prostu zadzwoń:

if(TestApplication.foregroundActivityRef!=null){
    // APP IS IN FOREGROUND!
    // We can also get the activity that is currently visible!
}

Aktualizacja (jak wskazała SHS ):

Nie zapomnij zarejestrować się do wywołań zwrotnych w onCreatemetodzie klasy aplikacji .

@Override
public void onCreate() {
    ...
    registerActivityLifecycleCallbacks(this);
}

2
Właśnie tego szukałem od dwóch godzin. Dziękuję Ci! :)
waseefakhtar

1
Musimy wywołać „registerActivityLifecycleCallbacks (this)”; inside Metoda przesłonięcia „onCreate ()” klasy Application (TestApplication). Spowoduje to uruchomienie wywołań zwrotnych w naszej klasie aplikacji.
SHS

@SarthakMittal, Jeśli urządzenie przejdzie w tryb czuwania lub gdy je zablokujemy, zostanie wywołane onActivityStopped (). Na podstawie twojego kodu będzie to wskazywać, że aplikacja działa w tle. Ale tak naprawdę jest na pierwszym planie.
Ayaz Alifov

@AyazAlifov Jeśli telefon jest w trybie gotowości lub telefon jest zablokowany, nie uważam go za na pierwszym planie, ale znowu zależy to od preferencji.
Sarthak Mittal

8

Aby określić aplikację pierwszoplanową, której możesz użyć do wykrywania aplikacji pierwszego planu, możesz użyć https://github.com/ricvalerio/foregroundappchecker . Używa różnych metod w zależności od wersji Androida urządzenia.

Jeśli chodzi o usługę, repozytorium zapewnia również potrzebny do niej kod. Zasadniczo pozwól Android Studio stworzyć usługę za Ciebie, a następnie onCreate dodaj fragment kodu korzystający z appChecker. Będziesz jednak musiał poprosić o pozwolenie.


idealny!! testowano na
Androidzie

Dzięki wielkie. To jedyna odpowiedź, która rozwiązała problem, z którym mieliśmy do czynienia w przypadku powiadomień o aplikacjach. Gdy pojawi się powiadomienie, wszystkie inne odpowiedzi podadzą nazwę pakietu aplikacji, która wysłała powiadomienie. Twój LollipopDetector rozwiązał ten problem. Jedna wskazówka: od API 29 MOVE_TO_FOREGROUND jest przestarzałe w UsageEvents.Event, więc od Androida Q należy używać ACTIVITY_RESUMED. Wtedy będzie działać jak urok!
Jorn Rigter

8

Biorąc pod uwagę, że getRunningTasks()jest to przestarzałe i getRunningAppProcesses()nie jest wiarygodne, zdecydowałem się połączyć 2 podejścia wymienione w StackOverflow:

   private boolean isAppInForeground(Context context)
    {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
        {
            ActivityManager am = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
            ActivityManager.RunningTaskInfo foregroundTaskInfo = am.getRunningTasks(1).get(0);
            String foregroundTaskPackageName = foregroundTaskInfo.topActivity.getPackageName();

            return foregroundTaskPackageName.toLowerCase().equals(context.getPackageName().toLowerCase());
        }
        else
        {
            ActivityManager.RunningAppProcessInfo appProcessInfo = new ActivityManager.RunningAppProcessInfo();
            ActivityManager.getMyMemoryState(appProcessInfo);
            if (appProcessInfo.importance == IMPORTANCE_FOREGROUND || appProcessInfo.importance == IMPORTANCE_VISIBLE)
            {
                return true;
            }

            KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
            // App is foreground, but screen is locked, so show notification
            return km.inKeyguardRestrictedInputMode();
        }
    }

4
musisz wspomnieć, aby dodać do manifestu przestarzałe uprawnienie <używa-zezwolenia android: name = "android.permission.GET_TASKS" /> w przeciwnym razie aplikacja ulegnie awarii
RJFares

1
android.permission.GET_TASKS - jest obecnie zabronione w Sklepie Play
Duna


1

To zadziałało dla mnie. Ale podaje tylko nazwę menu głównego. Oznacza to, że jeśli użytkownik otworzył ekran Ustawienia -> Bluetooth -> Nazwa urządzenia, RunningAppProcessInfo nazywa to po prostu Ustawieniami. Nie mogę wydobyć furthura

ActivityManager activityManager = (ActivityManager) context.getSystemService( Context.ACTIVITY_SERVICE );
                PackageManager pm = context.getPackageManager();
                List<RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
                for(RunningAppProcessInfo appProcess : appProcesses) {              
                    if(appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                        CharSequence c = pm.getApplicationLabel(pm.getApplicationInfo(appProcess.processName, PackageManager.GET_META_DATA));
                        Log.i("Foreground App", "package: " + appProcess.processName + " App: " + c.toString());
                    }               
                }

4
Myślę, że działa to tylko wtedy, gdy Twoja własna aplikacja działa na pierwszym planie. Nie zgłasza niczego, gdy na pierwszym planie jest inna aplikacja.
Alice Van Der Land

0

Zrób coś takiego:

int showLimit = 20;

/* Get all Tasks available (with limit set). */
ActivityManager mgr = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> allTasks = mgr.getRunningTasks(showLimit);
/* Loop through all tasks returned. */
for (ActivityManager.RunningTaskInfo aTask : allTasks) 
{                  
    Log.i("MyApp", "Task: " + aTask.baseActivity.getClassName()); 
    if (aTask.baseActivity.getClassName().equals("com.android.email.activity.MessageList")) 
        running=true;
}

Google prawdopodobnie odrzuci aplikację, która używa ActivityManager.getRunningTasks (). Dokumentacja stwierdza, że ​​jest to tylko dla celów deweloperskich : developer.android.com/reference/android/app/ ... Zobacz wstępny komentarz: stackoverflow.com/questions/4414171/ ...
ForceMagic

3
@ForceMagic - Google nie odrzuca aplikacji tylko za używanie zawodnych interfejsów API; ponadto Google nie jest jedynym kanałem dystrybucji aplikacji na Androida. To powiedziawszy, warto pamiętać, że informacje z tego interfejsu API nie są wiarygodne.
Chris Stratton

@ChrisStratton Interesujące, ale prawdopodobnie masz rację, chociaż odradza się używanie go do podstawowej logiki, nie odrzucą aplikacji. Możesz ostrzec Sky Kelsey z linku komentarza.
ForceMagic

3
„getRunningTasks” działa w przypadku interfejsu API 21 i późniejszych, więc dobrym pomysłem może być wymyślenie innego sposobu, aby działał w LOLLIPOP (ja sam nie rozgryzłem tego sam :()
am IT

0

W Lollipop i nowszych:

Dodaj do mainfest:

<uses-permission android:name="android.permission.GET_TASKS" />

I zrób coś takiego:

if( mTaskId < 0 )
{
    List<AppTask> tasks = mActivityManager.getAppTasks(); 
    if( tasks.size() > 0 )
        mTaskId = tasks.get( 0 ).getTaskInfo().id;
}

4
To jest przestarzałe do marshmallow
jinkal

0

W ten sposób sprawdzam, czy moja aplikacja jest na pierwszym planie. Uwaga Używam AsyncTask zgodnie z sugestią oficjalnego Androida dokumentacją

`

    private class CheckIfForeground extends AsyncTask<Void, Void, Void> {
    @Override
    protected Void doInBackground(Void... voids) {

        ActivityManager activityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
        for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
            if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                Log.i("Foreground App", appProcess.processName);

                if (mContext.getPackageName().equalsIgnoreCase(appProcess.processName)) {
                    Log.i(Constants.TAG, "foreground true:" + appProcess.processName);
                    foreground = true;
                    // close_app();
                }
            }
        }
        Log.d(Constants.TAG, "foreground value:" + foreground);
        if (foreground) {
            foreground = false;
            close_app();
            Log.i(Constants.TAG, "Close App and start Activity:");

        } else {
            //if not foreground
            close_app();
            foreground = false;
            Log.i(Constants.TAG, "Close App");

        }

        return null;
    }
}

i wykonaj AsyncTask w ten sposób. new CheckIfForeground().execute();


czy masz do tego link?
user1743524

0

Połączyłem dwa rozwiązania w jedną metodę i działa u mnie na API 24 i na API 21. Inne nie testowałem.

Kod w Kotlinie:

private fun isAppInForeground(context: Context): Boolean {
    val appProcessInfo = ActivityManager.RunningAppProcessInfo()
    ActivityManager.getMyMemoryState(appProcessInfo)
    if (appProcessInfo.importance == IMPORTANCE_FOREGROUND ||
            appProcessInfo.importance == IMPORTANCE_VISIBLE) {
        return true
    } else if (appProcessInfo.importance == IMPORTANCE_TOP_SLEEPING ||
            appProcessInfo.importance == IMPORTANCE_BACKGROUND) {
        return false
    }

    val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    val foregroundTaskInfo = am.getRunningTasks(1)[0]
    val foregroundTaskPackageName = foregroundTaskInfo.topActivity.packageName
    return foregroundTaskPackageName.toLowerCase() == context.packageName.toLowerCase()
}

iw Manifest

<!-- Check whether app in background or foreground -->
<uses-permission android:name="android.permission.GET_TASKS" />
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.