web-dev-qa-db-fra.com

Android: Comment puis-je obtenir l'activité de premier plan actuelle (à partir d'un service)?

Existe-t-il un moyen Android natif d'obtenir une référence à l'activité en cours d'exécution à partir d'un service?

J'ai un service en tâche de fond et je souhaite mettre à jour mon activité actuelle lorsqu'un événement se produit (dans le service). Y a-t-il un moyen facile de faire cela (comme celui que j'ai suggéré ci-dessus)?

162
George

Existe-t-il un moyen Android natif d'obtenir une référence à l'activité en cours d'exécution à partir d'un service?

Vous ne pouvez pas posséder "l'activité en cours".

J'ai un service en tâche de fond et je souhaite mettre à jour mon activité actuelle lorsqu'un événement se produit (dans le service). Y a-t-il un moyen facile de faire cela (comme celui que j'ai suggéré ci-dessus)?

  1. Envoyez une émission Intent à l'activité - voici un exemple de projet démontrant ce modèle
  2. Demandez à l'activité de fournir une PendingIntent (par exemple, via createPendingResult()) que le service appelle
  3. Demandez à l'activité d'enregistrer un objet callback ou listener auprès du service via bindService() et demandez au service d'appeler une méthode d'événement sur cet objet callback/listener.
  4. Envoyez une diffusion commandée Intent à l'activité, avec une BroadcastReceiver de priorité basse en tant que sauvegarde (pour générer une Notification si l'activité n'est pas à l'écran) - voici un article de blog avec plus d'informations sur ce modèle
81
CommonsWare

Voici un bon moyen de le faire en utilisant le gestionnaire d'activités . Vous obtenez essentiellement les tâches en cours d'exécution du gestionnaire d'activités. Il retournera toujours d'abord la tâche actuellement active. De là, vous pouvez obtenir le topActivity.

Exemple ici

Il existe un moyen simple d'obtenir une liste des tâches en cours d'exécution à partir du service ActivityManager . Vous pouvez demander un nombre maximal de tâches exécutées sur le téléphone. Par défaut, la tâche actuellement active est renvoyée en premier.

Une fois que vous avez cela, vous pouvez obtenir un objet ComponentName en demandant topActivity à votre liste.

Voici un exemple.

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

Vous aurez besoin de l'autorisation suivante sur votre manifeste:

<uses-permission Android:name="Android.permission.GET_TASKS"/>
135
Nelson Ramirez

Avertissement: violation de Google Play

Google a menacé de supprimer des applications du Play Store s’ils utilisent des services d’accessibilité à des fins autres que l’accessibilité. Cependant, ceci serait en train d'être reconsidéré .


Utilisez une AccessibilityService

Avantages

  • Testé et fonctionnant sous Android 2.2 (API 8) à travers Android 7.1 (API 25).
  • Ne nécessite pas de vote.
  • Ne nécessite pas l'autorisation GET_TASKS .

Désavantages

  • Chaque utilisateur doit activer le service dans les paramètres d'accessibilité d'Android.
  • Ce n'est pas fiable à 100%. De temps en temps, les événements sont en panne.
  • Le service est toujours en cours d'exécution.
  • Lorsqu'un utilisateur tente d'activer la variable AccessibilityService , il ne peut pas appuyer sur le bouton OK si une application a placé une superposition à l'écran. Velis Auto Brightness et Lux sont quelques-unes de ces applications. Cela peut être déroutant, car l'utilisateur peut ne pas savoir pourquoi il ne peut pas appuyer sur le bouton ou comment le contourner. 
  • Le AccessibilityService ne saura pas l'activité en cours avant le premier changement d'activité.

Exemple

Un service

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

Fusionnez ceci dans votre manifeste:

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

Informations de service

Mettez ceci dans 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"/>

Activer le service

Chaque utilisateur de l'application devra activer explicitement le AccessibilityService pour pouvoir l'utiliser. Voir cette réponse StackOverflow pour savoir comment faire cela.

Notez que l'utilisateur ne pourra pas appuyer sur le bouton OK lorsqu'il tentera d'activer le service d'accessibilité si une application a placé une superposition à l'écran, telle que Velis Auto Brightness ou Lux. 

97
Sam

Cela peut être fait par:

  1. Implémentez votre propre classe d'application, inscrivez-vous à ActivityLifecycleCallbacks - de cette façon, vous pourrez voir ce qui se passe avec notre application. À chaque reprise, le rappel affecte l'activité visible en cours à l'écran et, en pause, supprime l'affectation. Il utilise la méthode registerActivityLifecycleCallbacks() qui a été ajoutée à l'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. Dans votre service, appelez getApplication() et transmettez-le au nom de la classe de votre application (App dans ce cas). Ensuite, vous pouvez appeler app.getActiveActivity() - cela vous donnera une activité visible en cours (ou nulle si aucune activité n’est visible). Vous pouvez obtenir le nom de l'activité en appelant activeActivity.getClass().getSimpleName()

18
Vit Veres

Je ne pouvais pas trouver une solution qui plairait à notre équipe et nous avons donc lancé la nôtre. Nous utilisons ActivityLifecycleCallbacks pour suivre l’activité en cours et l’exposer via un service:

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

Configurez ensuite votre conteneur DI pour renvoyer l’instance de MyApplication pour ContextProvider, par exemple.

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

(Notez que l'implémentation de getCurrent() est omise du code ci-dessus. Il s'agit simplement d'une variable statique définie à partir du constructeur de l'application.)

12
Muxa

Utilisez ActivityManager

Si vous voulez seulement connaître le application contenant l'activité en cours, vous pouvez le faire avec ActivityManager . La technique que vous pouvez utiliser dépend de la version d'Android:

Avantages

  • Devrait fonctionner dans toutes les versions Android à ce jour.

Désavantages

  • Ne fonctionne pas sous Android 5.1+ (il ne renvoie que votre propre application)
  • La documentation de ces API indique qu'elles ne sont destinées qu'aux interfaces utilisateur de débogage et de gestion.
  • Si vous souhaitez des mises à jour en temps réel, vous devez utiliser la scrutation.
  • S'appuie sur une API cachée: ActivityManager.RunningAppProcessInfo.processState
  • Cette implémentation ne détecte pas l'activité du commutateur d'applications.

Exemple (basé sur le code de 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);
    }

}

Manifeste

Ajoutez le GET_TASKS permission à AndroidManifest.xml:

<!--suppress DeprecatedClassUsageInspection -->
<uses-permission Android:name="Android.permission.GET_TASKS" />
10
Sam

J'utilise ceci pour mes tests. C'est API> 19, et seulement pour les activités de votre application, cependant.

@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");
}
9
espinchi

Utilisez ce code pour l'API 21 ou supérieure. Cela fonctionne et donne un meilleur résultat par rapport aux autres réponses, il détecte parfaitement le processus de premier plan. 

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();
        }
    }
1
Manish Godhani

Voici ma réponse qui fonctionne très bien ...

Vous devriez pouvoir obtenir l'activité actuelle de cette manière .......... Si vous structurez votre application avec quelques activités comportant de nombreux fragments et que vous souhaitez garder une trace de votre activité actuelle, il vous faudra beaucoup de travail. . Mon sénario était j'ai une activité avec plusieurs fragments. Je peux donc garder une trace de l'activité en cours via l'objet Application, qui peut stocker tout l'état actuel des variables globales.

Voici un moyen. Lorsque vous démarrez votre activité, vous enregistrez celle-ci par Application.setCurrentActivity (getIntent ()); Cette application va le stocker . Sur votre classe de service, vous pouvez simplement faire comme Intention currentIntent = Application.getCurrentActivity (); getApplication (). startActivity (currentIntent);

0
John3328

Je ne sais pas si c'est une réponse stupide, mais j'ai résolu ce problème en stockant un indicateur dans les préférences partagées chaque fois que je suis entré dans onCreate () de n'importe quelle activité. J'ai ensuite utilisé la valeur des préférences shered pour déterminer en quoi consiste l'activité de premier plan.

0
Vlad Vladescu