Android: come posso ottenere l'attività di primo piano corrente (da un servizio)?


180

Esiste un modo Android nativo per ottenere un riferimento all'attività attualmente in esecuzione da un servizio?

Ho un servizio in esecuzione in background e vorrei aggiornare la mia attività corrente quando si verifica un evento (nel servizio). C'è un modo semplice per farlo (come quello che ho suggerito sopra)?


forse questo può dare un'idea stackoverflow.com/a/28423385/185022
AZ_

Risposte:


87

Esiste un modo Android nativo per ottenere un riferimento all'attività attualmente in esecuzione da un servizio?

Potresti non essere proprietario dell '"Attività attualmente in esecuzione".

Ho un servizio in esecuzione in background e vorrei aggiornare la mia attività corrente quando si verifica un evento (nel servizio). C'è un modo semplice per farlo (come quello che ho suggerito sopra)?

  1. Invia una trasmissione Intentall'attività: ecco un esempio di progetto che dimostra questo schema
  2. Chiedi all'attività di fornire un PendingIntent(es. Via createPendingResult()) che il servizio richiama
  3. Chiedere all'attività di registrare un oggetto callback o listener con il servizio tramite bindService()e fare in modo che il servizio chiami un metodo evento su quell'oggetto callback / listener
  4. Invia una trasmissione ordinata Intentall'attività, con una priorità bassa BroadcastReceivercome backup (per aumentare un Notificationse l'attività non è sullo schermo) - ecco un post sul blog con più su questo schema

3
Grazie, ma dato che ho un sacco di attività e non voglio aggiornarle tutte, stavo cercando un modo Android per ottenere l'attivazione in primo piano. Come dici tu, non dovrei essere in grado di farlo. Significa che devo aggirare questo.
George,

1
@Gorge: Quello che vuoi non ti aiuterebbe, comunque. Come fai notare, hai "un bottino di attività". Quindi, quelle sono tutte classi separate. Quindi, non c'è nulla che il tuo servizio possa fare con loro senza un enorme blocco di instanceofcontrolli if o il refactoring delle attività per condividere una superclasse o un'interfaccia comune. E se hai intenzione di riformattare le attività, puoi anche farlo in un modo che si adatta meglio al framework e copre più scenari, come nessuna delle tue attività attive. # 4 è probabilmente il meno lavoro e il più flessibile.
CommonsWare

2
Grazie, ma ho una soluzione migliore. Tutte le mie attività estendono una classe BaseActivity personalizzata. Ho impostato un ContextRegister che registra l'attività come corrente ogni volta che è in primo piano, con literraly 3 righe nella mia classe BaseActivity. Tuttavia, grazie per il supporto.
George,

3
@George: attenzione alle perdite di memoria. I membri di dati statici mutabili devono essere evitati in Java ove possibile.
CommonsWare

Ho incapsulato strettamente l'oggetto, sebbene lo terrò davvero a mente. Grazie.
George,

139

Aggiornamento : non funziona più con le attività di altre app a partire da Android 5.0


Ecco un buon modo per farlo utilizzando il manager delle attività. Fondamentalmente ottieni le attività in esecuzione dal manager delle attività. Restituirà sempre prima l'attività attualmente attiva. Da lì puoi ottenere topActivity.

Esempio qui

Esiste un modo semplice per ottenere un elenco di attività in esecuzione dal servizio ActivityManager. È possibile richiedere un numero massimo di attività in esecuzione sul telefono e, per impostazione predefinita, l'attività attualmente attiva viene restituita per prima.

Una volta ottenuto, puoi ottenere un oggetto ComponentName richiedendo topActivity dal tuo elenco.

Ecco un esempio

    ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
    List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(1);
    Log.d("topActivity", "CURRENT Activity ::" + taskInfo.get(0).topActivity.getClassName());
    ComponentName componentInfo = taskInfo.get(0).topActivity;
    componentInfo.getPackageName();

Avrai bisogno della seguente autorizzazione sul tuo manifest:

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

3
La tua risposta è corretta al 100 %.Grazie a 4. È meglio che pubblichi la stessa risposta qui
Shahzad Imam,

1
@ArtOfWarfare non ha verificato ma sì, il numero di frammenti che hai è irrilevante in quanto sono sempre ospitati da una singola attività.
Nelson Ramirez,

2
Se ho bisogno dell'attività corrente più frequentemente, devo effettuare il polling molto frequentemente, giusto? C'è un modo per ottenere un evento di callback ogni volta che l'attività in primo piano cambia?
nizam.sp,

43
Solo così tutti lo sanno, i documenti lo affermano sul metodo getRunningTasks (). -------- Nota: questo metodo è destinato esclusivamente al debug e alla presentazione di interfacce utente per la gestione delle attività. Questo non dovrebbe mai essere usato per la logica di base in un'applicazione, come decidere tra comportamenti diversi in base alle informazioni trovate qui. Tali usi non sono supportati e probabilmente si romperanno in futuro. Ad esempio, se più applicazioni possono essere attivamente in esecuzione contemporaneamente, le ipotesi fatte sul significato dei dati qui ai fini del flusso di controllo saranno errate. ------------
Ryan,

30
semifake il 21 As of LOLLIPOP, this method is no longer available to third party applications: the introduction of document-centric recents means it can leak person information to the caller. For backwards compatibility, it will still return a small subset of its data: at least the caller's own tasks, and possibly some other tasks such as home that are known to not be sensitive.
aprile

116

Avviso: violazione di Google Play

Google ha minacciato di rimuovere app dal Play Store se utilizzano servizi di accessibilità per scopi di non accessibilità. Tuttavia, si dice che questo venga riconsiderato .


Usa un AccessibilityService

Benefici

  • Testato e funzionante in Android 2.2 (API 8) tramite Android 7.1 (API 25).
  • Non richiede il polling.
  • Non richiede l' GET_TASKSautorizzazione.

svantaggi

  • Ogni utente deve abilitare il servizio nelle impostazioni di accessibilità di Android.
  • Questo non è affidabile al 100%. Occasionalmente gli eventi vengono fuori servizio.
  • Il servizio è sempre in esecuzione.
  • Quando un utente tenta di abilitare l'opzione AccessibilityService, non può premere il pulsante OK se un'app ha posizionato un overlay sullo schermo. Alcune app che lo fanno sono Velis Auto Brightness e Lux. Questo può essere fonte di confusione perché l'utente potrebbe non sapere perché non è possibile premere il pulsante o come aggirare il problema.
  • Il AccessibilityServicenon conosceranno l'attività fino alla prima variazione di attività.

Esempio

Servizio

public class WindowChangeDetectingService extends AccessibilityService {

    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();

        //Configure these here for compatibility with API 13 and below.
        AccessibilityServiceInfo config = new AccessibilityServiceInfo();
        config.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
        config.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;

        if (Build.VERSION.SDK_INT >= 16)
            //Just in case this helps
            config.flags = AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;

        setServiceInfo(config);
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            if (event.getPackageName() != null && event.getClassName() != null) {
                ComponentName componentName = new ComponentName(
                    event.getPackageName().toString(),
                    event.getClassName().toString()
                );

                ActivityInfo activityInfo = tryGetActivity(componentName);
                boolean isActivity = activityInfo != null;
                if (isActivity)
                    Log.i("CurrentActivity", componentName.flattenToShortString());
            }
        }
    }

    private ActivityInfo tryGetActivity(ComponentName componentName) {
        try {
            return getPackageManager().getActivityInfo(componentName, 0);
        } catch (PackageManager.NameNotFoundException e) {
            return null;
        }
    }

    @Override
    public void onInterrupt() {}
}

AndroidManifest.xml

Unisci questo nel tuo manifest:

<application>
    <service
        android:label="@string/accessibility_service_name"
        android:name=".WindowChangeDetectingService"
        android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
        <intent-filter>
            <action android:name="android.accessibilityservice.AccessibilityService"/>
        </intent-filter>
        <meta-data
            android:name="android.accessibilityservice"
            android:resource="@xml/accessibilityservice"/>
    </service>
</application>

Informazioni sul servizio

Metti questo in res/xml/accessibilityservice.xml:

<?xml version="1.0" encoding="utf-8"?>
<!-- These options MUST be specified here in order for the events to be received on first
 start in Android 4.1.1 -->
<accessibility-service
    xmlns:tools="http://schemas.android.com/tools"
    android:accessibilityEventTypes="typeWindowStateChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagIncludeNotImportantViews"
    android:description="@string/accessibility_service_description"
    xmlns:android="http://schemas.android.com/apk/res/android"
    tools:ignore="UnusedAttribute"/>

Abilitazione del servizio

Ogni utente dell'app dovrà abilitare esplicitamente AccessibilityServiceper poter essere utilizzato. Vedi questa risposta StackOverflow per come fare.

Si noti che l'utente non sarà in grado di premere il pulsante OK quando si tenta di abilitare il servizio di accessibilità se un'app ha posizionato un overlay sullo schermo, come Velis Auto Brightness o Lux.


1
@lovemint, ciò che intendevo è: ho specificato un esempio settingsActivitydi your.app.ServiceSettingsActivity, quindi dovresti cambiarlo con le tue attività di impostazione per il tuo servizio di accessibilità. Penso che l'attività delle impostazioni sia facoltativa, quindi ho rimosso quella parte dalla mia risposta per renderla più semplice.
Sam,

1
Grazie mille, solo l'ultima domanda. Se devo trattare dall'API 8 all'attuale, dovrei farlo nel codice invece di utilizzare XML?
paolo2988,

1
@lovemint, è vero. Ho appena aggiornato il codice di esempio per farlo.
Sam,

1
@Sam Posso confermare che questo servizio funziona perfettamente. Un comportamento strano, ho usato un intento nell'attività principale per abilitare il servizio di accessibilità. Dopo questa prima attivazione il servizio viene attivato ma onAccessibilityEventnon riceve alcun evento, ma se disabilito e riattivo il Servizio di accessibilità il servizio viene riattivato e onAccessibilityEventinizia a funzionare.
paolo2988,

2
Grazie per il tuo codice Ho creato questa app per te :)
vault

20

Può essere fatto da:

  1. Implementa la tua classe di applicazione, registrati per ActivityLifecycleCallbacks: in questo modo puoi vedere cosa sta succedendo con la nostra app. Ad ogni ripresa, il callback assegna l'attività visibile corrente sullo schermo e in pausa rimuove l'assegnazione. Usa il metodo registerActivityLifecycleCallbacks()che è stato aggiunto in API 14.

    public class App extends Application {
    
    private Activity activeActivity;
    
    @Override
    public void onCreate() {
        super.onCreate();
        setupActivityListener();
    }
    
    private void setupActivityListener() {
    registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            }
            @Override
            public void onActivityStarted(Activity activity) {
            }
            @Override
            public void onActivityResumed(Activity activity) {
                activeActivity = activity;
            }
            @Override
            public void onActivityPaused(Activity activity) {
                activeActivity = null;
            }
            @Override
            public void onActivityStopped(Activity activity) {
            }
            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
            }
            @Override
            public void onActivityDestroyed(Activity activity) {
            }
        });
    }
    
    public Activity getActiveActivity(){
        return activeActivity;
    }
    
    }
  2. Nella tua chiamata di servizio getApplication()e esegui il cast sul nome della classe dell'app (in questo caso l'app). Di quanto tu possa chiamare app.getActiveActivity()- ciò ti darà un'attività visibile corrente (o nulla quando nessuna attività è visibile). Puoi ottenere il nome dell'attività chiamandoactiveActivity.getClass().getSimpleName()


1
Sto ottenendo l'esecuzione di Nullpointer su activeActivity.getClass (). GetSimpleName (). Per favore, puoi aiutarmi
principiante

Come puoi vedere da ActivityLifecycleCallbacks - nel caso in cui l'attività non sia visibile, application.getActiveActivity () restituisce null. Ciò significa che nessuna attività è visibile. Devi verificarlo nel tuo servizio o ovunque tu lo usi.
Vit Veres,

ok..ma nel caso in cui l'attività riprenda, questo non dovrebbe restituire null ?? La mia attività era stata avviata e nel suo curriculum l'ho ottenuta
principiante il

Difficile da indovinare, provare a mettere i punti di interruzione a onActivityResumed(), onActivityPaused()e getActiveActivity()per vedere come è Callet, se mai così.
Vit Veres,

@beginner uno deve verificare se activitye activeActivitysono gli stessi prima di assegnare activeActivitycon nullal fine di evitare errori dovuti all'ordine di chiamata mescolato dei metodi del ciclo di vita di varie attività.
Rahul Tiwari,

16

Non sono riuscito a trovare una soluzione di cui il nostro team sarebbe contento, quindi abbiamo creato la nostra. Usiamo ActivityLifecycleCallbacksper tenere traccia dell'attività corrente e quindi esporla attraverso un servizio:

public interface ContextProvider {
    Context getActivityContext();
}

public class MyApplication extends Application implements ContextProvider {
    private Activity currentActivity;

    @Override
    public Context getActivityContext() {
         return currentActivity;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                MyApplication.this.currentActivity = activity;
            }

            @Override
            public void onActivityStarted(Activity activity) {
                MyApplication.this.currentActivity = activity;
            }

            @Override
            public void onActivityResumed(Activity activity) {
                MyApplication.this.currentActivity = activity;
            }

            @Override
            public void onActivityPaused(Activity activity) {
                MyApplication.this.currentActivity = null;
            }

            @Override
            public void onActivityStopped(Activity activity) {
                // don't clear current activity because activity may get stopped after
                // the new activity is resumed
            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

            }

            @Override
            public void onActivityDestroyed(Activity activity) {
                // don't clear current activity because activity may get destroyed after
                // the new activity is resumed
            }
        });
    }
}

Quindi configurare il contenitore DI per restituire l'istanza di MyApplicationper ContextProvider, ad es

public class ApplicationModule extends AbstractModule {    
    @Provides
    ContextProvider provideMainActivity() {
        return MyApplication.getCurrent();
    }
}

(Si noti che l'implementazione di getCurrent()è omessa dal codice sopra. È solo una variabile statica impostata dal costruttore dell'applicazione)


4
si deve verificare se activitye currentActivitysono uguali prima di assegnare currentActivitycon nullper evitare errori dovuti all'ordine di chiamata mescolato dei metodi del ciclo di vita di varie attività.
Rahul Tiwari,

14

Uso ActivityManager

Se vuoi solo conoscere l' applicazione contenente l'attività corrente, puoi farlo usando ActivityManager. La tecnica che puoi usare dipende dalla versione di Android:

Benefici

  • Dovrebbe funzionare in tutte le versioni di Android ad oggi.

svantaggi

  • Non funziona in Android 5.1+ (restituisce solo la tua app)
  • La documentazione per queste API afferma che sono destinati esclusivamente al debug e alla gestione delle interfacce utente.
  • Se desideri aggiornamenti in tempo reale, devi utilizzare il polling.
  • Si basa su un'API nascosta: ActivityManager.RunningAppProcessInfo.processState
  • Questa implementazione non rileva l'attività del commutatore di app.

Esempio (basato sul codice KNaito )

public class CurrentApplicationPackageRetriever {

    private final Context context;

    public CurrentApplicationPackageRetriever(Context context) {
        this.context = context;
    }

    public String get() {
        if (Build.VERSION.SDK_INT < 21)
            return getPreLollipop();
        else
            return getLollipop();
    }

    private String getPreLollipop() {
        @SuppressWarnings("deprecation")
        List<ActivityManager.RunningTaskInfo> tasks =
            activityManager().getRunningTasks(1);
        ActivityManager.RunningTaskInfo currentTask = tasks.get(0);
        ComponentName currentActivity = currentTask.topActivity;
        return currentActivity.getPackageName();
    }

    private String getLollipop() {
        final int PROCESS_STATE_TOP = 2;

        try {
            Field processStateField = ActivityManager.RunningAppProcessInfo.class.getDeclaredField("processState");

            List<ActivityManager.RunningAppProcessInfo> processes =
                activityManager().getRunningAppProcesses();
            for (ActivityManager.RunningAppProcessInfo process : processes) {
                if (
                    // Filters out most non-activity processes
                    process.importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
                    &&
                    // Filters out processes that are just being
                    // _used_ by the process with the activity
                    process.importanceReasonCode == 0
                ) {
                    int state = processStateField.getInt(process);

                    if (state == PROCESS_STATE_TOP) {
                        String[] processNameParts = process.processName.split(":");
                        String packageName = processNameParts[0];

                        /*
                         If multiple candidate processes can get here,
                         it's most likely that apps are being switched.
                         The first one provided by the OS seems to be
                         the one being switched to, so we stop here.
                         */
                        return packageName;
                    }
                }
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }

        return null;
    }

    private ActivityManager activityManager() {
        return (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    }

}

Manifesto

Aggiungi l' GET_TASKSautorizzazione a AndroidManifest.xml:

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

10
questo non funziona più su Android M. getRunningAppProcesses () ora restituisce solo il pacchetto dell'applicazione
Lior Iluz,

1
Non funziona anche per API Level 22 (Android 5.1). Testato su Build LPB23
sumitb.mdi

9

Lo sto usando per i miei test. È API> 19 e solo per le attività della tua app.

@TargetApi(Build.VERSION_CODES.KITKAT)
public static Activity getRunningActivity() {
    try {
        Class activityThreadClass = Class.forName("android.app.ActivityThread");
        Object activityThread = activityThreadClass.getMethod("currentActivityThread")
                .invoke(null);
        Field activitiesField = activityThreadClass.getDeclaredField("mActivities");
        activitiesField.setAccessible(true);
        ArrayMap activities = (ArrayMap) activitiesField.get(activityThread);
        for (Object activityRecord : activities.values()) {
            Class activityRecordClass = activityRecord.getClass();
            Field pausedField = activityRecordClass.getDeclaredField("paused");
            pausedField.setAccessible(true);
            if (!pausedField.getBoolean(activityRecord)) {
                Field activityField = activityRecordClass.getDeclaredField("activity");
                activityField.setAccessible(true);
                return (Activity) activityField.get(activityRecord);
            }
        }
    } catch (Exception e) {
        throw new RuntimeException(e);
    }

    throw new RuntimeException("Didn't find the running activity");
}

Sostituisci ArrayMap con Map e funzionerà anche su 4.3. Non testate versioni precedenti di Android.
Oliver Jonas il

2

Utilizzare questo codice per API 21 o successive. Funziona e dà risultati migliori rispetto alle altre risposte, rileva perfettamente il processo in primo piano.

if (Build.VERSION.SDK_INT >= 21) {
    String currentApp = null;
    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();
        }
    }

Potete per favore approfondire come i risultati di questo metodo sono migliori delle altre risposte?
Sam,

Funziona anche con il menu di notifica. Sto affrontando un problema in cui se ricevo una chiamata o un messaggio. UsageStatsManager inizia a fornirmi il nome del pacchetto di systemUi invece della mia app.
kukroid,

@kukroid, pubblica una domanda separata e includi il codice sorgente, le informazioni sul dispositivo e quali app di messaggistica e telefono stai utilizzando.
Sam

0

Ecco la mia risposta che funziona bene ...

Dovresti essere in grado di ottenere l'attività corrente in questo modo ... Se strutturi la tua app con alcune attività con molti frammenti e vuoi tenere traccia di quale sia la tua attività corrente, ci vorrebbe molto lavoro. Il mio senario era che ho un'attività con più frammenti. Quindi posso tenere traccia dell'attività corrente tramite Application Object, che può memorizzare tutto lo stato corrente delle variabili globali.

Ecco un modo. Quando si avvia l'attività, si memorizza tale attività per Application.setCurrentActivity (getIntent ()); Questa applicazione lo memorizzerà. Sulla tua classe di servizio, puoi semplicemente fare come Intent currentIntent = Application.getCurrentActivity (); . GetApplication () startActivity (currentIntent);


2
Ciò presuppone che il servizio venga eseguito nello stesso processo dell'Attività, giusto?
Helleye,

0

Non so se è una risposta stupida, ma ho risolto questo problema memorizzando un flag nelle preferenze condivise ogni volta che ho inserito onCreate () di qualsiasi attività, quindi ho usato il valore delle preferenze coerenti per scoprire qual è l'attività in primo piano.


1
Stiamo cercando un modo nativo per farlo
IgniteCoders l'

-3

Di recente ho scoperto questo. Con le API come:

  • minSdkVersion 19
  • targetSdkVersion 26

    ActivityManager.getCurrentActivity (contesto)

Spero che sia di qualche utilità.


1
Ho avuto le mie speranze. Forse al momento della risposta esisteva, ma per ora non esiste alcuna getCurrentActivity()funzione nella ActivityManagerclasse ( developer.android.com/reference/android/app/ActivityManager ). Divertente che questa funzione non esiste: ActivityManager.isUserAMonkey().
ByteSlinger,

1
Questo rileva se stai usando un agente di test delle scimmie per testare la tua attività. Anche se un nome divertente, potrebbe essere molto utile per i test (e per far ridere, obvs.) Developer.android.com/studio/test/monkeyrunner
Ken Corey,
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.