Determinazione dell'applicazione in primo piano corrente da un'attività o un servizio in background


112

Vorrei avere un'applicazione che venga eseguita in background, che sappia quando è in esecuzione una delle applicazioni integrate (messaggistica, contatti, ecc.).

Quindi le mie domande sono:

  1. Come dovrei eseguire la mia applicazione in background.

  2. Come la mia applicazione in background può sapere qual è l'applicazione attualmente in esecuzione in primo piano.

Le risposte di persone con esperienza sarebbero molto apprezzate.


Non credo che tu abbia dato una spiegazione adeguata di ciò che stai cercando di fare. Cosa sta cercando di fare la tua applicazione in background? In che modo dovrebbe essere in grado di interagire con l'utente? Perché hai bisogno di sapere qual è l'attuale app in primo piano? Ecc.
Charles Duffy il


1
per rilevare l'app in primo piano, puoi utilizzare github.com/ricvalerio/foregroundappchecker
rvalerio

Risposte:


104

Riguardo a "2. Come la mia applicazione in background può sapere qual è l'applicazione attualmente in esecuzione in primo piano".

NON utilizzare il getRunningAppProcesses()metodo poiché restituisce tutti i tipi di rifiuti di sistema dalla mia esperienza e otterrai più risultati che hanno RunningAppProcessInfo.IMPORTANCE_FOREGROUND. Usa getRunningTasks()invece

Questo è il codice che utilizzo nel mio servizio per identificare l'applicazione in primo piano corrente, è davvero facile:

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);

Questo è tutto, quindi puoi accedere facilmente ai dettagli dell'app / attività in primo piano:

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

Ciò richiede un permesso aggiuntivo per l'attività manifest e funziona perfettamente.

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

1
Questo dovrebbe essere contrassegnato come la risposta corretta. Autorizzazione richiesta: android.permission.GET_TASKS è l'unico piccolo svantaggio di altri metodi che possono evitarlo.
brandall

3
MODIFICARE QUANTO SOPRA: Nota: questo metodo è inteso solo per il debug e la presentazione delle interfacce utente di gestione delle attività. <- L'ho appena notato nel documento Java. Quindi questo metodo potrebbe cambiare senza preavviso in futuro.
brandall

47
Nota: getRunningTasks()è deprecato nell'API 21 (Lollipop) - developer.android.com/reference/android/app/…
dtyler

@ dtyler, ok allora. C'è un altro metodo invece? Sebbene Google non desideri che le app di terze parti ottengano informazioni sensibili, ho la chiave della piattaforma del dispositivo. C'è qualche altro metodo per fare lo stesso se ho i privilegi di sistema?
Yeung

C'è un modo per utilizzare "Usage statistics api" introdotto in 21
KunalK

38

ho dovuto trovare la soluzione giusta nel modo più duro. il codice seguente fa parte di cyanogenmod7 (il tablet tweaks) ed è testato su Android 2.3.3 / gingerbread.

metodi:

  • getForegroundApp: restituisce l'applicazione in primo piano.
  • getActivityForApp: restituisce l'attività dell'app trovata.
  • isStillActive: determina se un'app trovata in precedenza è ancora l'app attiva.
  • isRunningService: una funzione di supporto per getForegroundApp

si spera che questo risponda a questo problema in tutta l'estensione (:

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
se non ti dispiace che ti chieda, perché hai bisogno dei metodi per vedere se è in esecuzione un servizio, è ancora attivo e per ottenere attività solo per ottenere l'applicazione in primo piano?

2
scusa, ma non funziona. Alcune app vengono filtrate senza motivo, penso che sia quando eseguono un servizio. Quindi isRunningService li sta buttando fuori.
seb

15
Sfortunatamente, getRunningTasks () è stato deprecato da Android L (API 20). A partire da L, questo metodo non è più disponibile per le applicazioni di terze parti: l'introduzione di documenti recenti significa che possono far trapelare informazioni sulla persona al chiamante. Per compatibilità con le versioni precedenti, restituirà comunque un piccolo sottoinsieme dei suoi dati: almeno le attività del chiamante e possibilmente alcune altre attività come home note per non essere sensibili.
Sam Lu

Qualcuno sa come questo approccio sia migliore del semplice fare mActivityManager.getRunningTasks(1).get(0).topActivity?
Sam

35

Prova il codice seguente:

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);
    }
}

Il nome del processo è il nome del pacchetto dell'app in esecuzione in primo piano. Confrontalo con il nome del pacchetto della tua applicazione. Se è lo stesso, la tua applicazione è in esecuzione in primo piano.

Spero che questo risponda alla tua domanda.


7
Dalla versione L di Android in poi, questa è l'unica soluzione che funziona in quanto i metodi utilizzati nelle altre soluzioni sono diventati obsoleti.
androidGuy

4
tuttavia, questo non funziona nel caso in cui l'app sia in primo piano e lo schermo è bloccato
Krit

La risposta corretta deve essere contrassegnata
A_rmas

1
Ha bisogno di un'autorizzazione aggiuntiva come la risposta accettata?
wonsuc

1
Questo non è del tutto corretto. Il nome di un processo non è sempre uguale al nome del pacchetto dell'app corrispondente.
Sam

25

Da lecca-lecca in poi questo è stato cambiato. Si prega di trovare il codice di seguito, prima che l'utente debba andare su Impostazioni -> Sicurezza -> (Scorri verso il basso fino all'ultimo) App con accesso di utilizzo -> Dai le autorizzazioni alla nostra app

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
non è possibile impostare queste statistiche sull'utilizzo a livello di programmazione?
rubmz

@ rubmz Hai qualche soluzione su questo?
VVB

1
nel mio doogee con Android 5.1 non c'è l'opzione "Dai i permessi alla nostra app"
djdance

Questo non produce risultati accurati al 100% ... è giusto il più delle volte
CamHart

1
Questa non mostrerà l'ultima App in primo piano, ma forse l'ultima "usata" in un certo senso: se lasci un'attività e vai a un'altra, apri il menu di avvio dall'alto e rilascialo, sarai nella " attività sbagliata "fino a quando non si passa a un'altra. Non importa se scorri o meno lo schermo, segnala l'attività sbagliata fino a quando non ne avvii un'altra.
Azurlake

9

Per i casi in cui dobbiamo controllare dal nostro servizio / thread in background se la nostra app è in primo piano o meno. Ecco come l'ho implementato e funziona bene per me:

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
}

Ora per controllare da altre classi, se l'app è in primo piano o meno, chiama semplicemente:

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

Aggiornamento (come sottolineato da SHS ):

Non dimenticare di registrarti per i callback nel onCreatemetodo della tua classe Application .

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

2
È quello che stavo cercando dalle ultime due ore. Grazie! :)
waseefakhtar

1
Dobbiamo chiamare "registerActivityLifecycleCallbacks (this);" inside Sostituisci il metodo "onCreate ()" della classe Application (TestApplication). Questo avvierà i callback nella nostra classe di applicazione.
SHS

@SarthakMittal, Se un dispositivo entra in modalità standby o quando lo blocchiamo, verrà chiamato onActivityStopped (). In base al codice, ciò indicherà che l'applicazione è in background. Ma in realtà è in primo piano.
Ayaz Alifov

@AyazAlifov Se il telefono è in standby o è bloccato, non lo considero in primo piano, ma ancora una volta dipende dalle preferenze.
Sarthak Mittal

8

Per determinare l'applicazione in primo piano, è possibile utilizzare per rilevare l'app in primo piano, è possibile utilizzare https://github.com/ricvalerio/foregroundappchecker . Utilizza metodi diversi a seconda della versione Android del dispositivo.

Per quanto riguarda il servizio, il repository fornisce anche il codice necessario. In sostanza, lascia che Android Studio crei il servizio per te, quindi onCreate aggiungi lo snippet che usa appChecker. Tuttavia, dovrai richiedere l'autorizzazione.


Perfetto!! testato su Android 7
Pratik Tank

Grazie mille. Questa è l'unica risposta che ha risolto il problema che stavamo affrontando con le notifiche delle app. Quando viene visualizzata una notifica, tutte le altre risposte restituiranno il nome del pacchetto dell'app che ha inviato la notifica. Il tuo LollipopDetector ha risolto questo problema. Un suggerimento: dall'API 29, MOVE_TO_FOREGROUND è deprecato in UsageEvents.Event, quindi da Android Q in poi si dovrebbe usare ACTIVITY_RESUMED. Allora funzionerà come un incantesimo!
Jorn Rigter

8

Tenendo conto che getRunningTasks()è deprecato e getRunningAppProcesses()non è affidabile, ho deciso di combinare 2 approcci menzionati in 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
devi menzionare per aggiungere al manifest l'autorizzazione deprecata <uses-permission android: name = "android.permission.GET_TASKS" /> altrimenti l'app andrà in crash
RJFares

1
android.permission.GET_TASKS - attualmente vietato nel Play Store
Duna il


1

Questo ha funzionato per me. Ma fornisce solo il nome del menu principale. Cioè se l'utente ha aperto la schermata Impostazioni -> Bluetooth -> Nome dispositivo, RunningAppProcessInfo lo chiama semplicemente Impostazioni. Non sono in grado di approfondire Furthur

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
Penso che funzioni solo se la tua app è in esecuzione in primo piano. Non segnala nulla quando qualche altra app è in primo piano.
Alice Van Der Land

0

Fai qualcosa del genere:

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 probabilmente rifiuterà un'app che utilizza ActivityManager.getRunningTasks (). Gli stati di documentazione che è scopi nemico dev solo: developer.android.com/reference/android/app/...~~MD~~aux~~plural~~3rd Vedere il commento intial: stackoverflow.com/questions/4414171/...
ForceMagic

3
@ForceMagic - Google non rifiuta le app semplicemente perché utilizzano API inaffidabili ; inoltre, Google non è l'unico canale di distribuzione per le applicazioni Android. Detto questo, vale la pena tenere presente che le informazioni di questa API non sono affidabili.
Chris Stratton

@ChrisStratton Interessante, ma probabilmente hai ragione, anche se è sconsigliato usarlo per la logica di base, non rifiuteranno l'app. Potresti voler avvisare Sky Kelsey dal link del commento.
ForceMagic

3
"getRunningTasks" funziona per api 21 e oltre, quindi potrebbe essere una buona idea trovare un altro modo per farlo funzionare per LOLLIPOP (io stesso non l'ho capito da solo però :()
amIT

0

In lecca-lecca e oltre:

Aggiungi a mainfest:

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

E fai qualcosa di simile:

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

4
È deprecato in marshmallow
jinkal

0

Ecco come controllo se la mia app è in primo piano. Nota che sto usando AsyncTask come suggerito dalla documentazione ufficiale di Android

`

    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;
    }
}

ed esegui AsyncTask in questo modo. new CheckIfForeground().execute();


hai un link per questo?
user1743524

0

Ho combinato due soluzioni in un metodo e funziona per me per API 24 e per API 21. Altri non ho testato.

Il codice in Kotlin:

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()
}

e in Manifest

<!-- Check whether app in background or foreground -->
<uses-permission android:name="android.permission.GET_TASKS" />
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.