Détermination de l'application de premier plan actuelle à partir d'une tâche ou d'un service d'arrière-plan


112

Je souhaite avoir une application qui s'exécute en arrière-plan, qui sait quand l'une des applications intégrées (messagerie, contacts, etc.) est en cours d'exécution.

Donc mes questions sont:

  1. Comment dois-je exécuter mon application en arrière-plan.

  2. Comment mon application d'arrière-plan peut savoir quelle est l'application en cours d'exécution au premier plan.

Les réponses de personnes expérimentées seraient grandement appréciées.


Je ne pense pas que vous ayez donné une explication adéquate de ce que vous essayez de faire. Qu'est - ce que votre application d'arrière-plan essaie de faire? De quelles manières devrait-il pouvoir interagir avec l'utilisateur? Pourquoi avez-vous besoin de savoir quelle est l'application de premier plan actuelle? Etc.
Charles Duffy


1
pour détecter l'application au premier plan, vous pouvez utiliser github.com/ricvalerio/foregroundappchecker
rvalerio

Réponses:


104

En ce qui concerne "2. Comment mon application d'arrière-plan peut savoir quelle est l'application actuellement en cours d'exécution au premier plan."

N'utilisez PAS la getRunningAppProcesses()méthode car cela renvoie toutes sortes de déchets système de mon expérience et vous obtiendrez plusieurs résultats qui ont RunningAppProcessInfo.IMPORTANCE_FOREGROUND. Utilisez getRunningTasks()plutôt

C'est le code que j'utilise dans mon service pour identifier l'application de premier plan actuelle, c'est vraiment simple:

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

C'est tout, alors vous pouvez facilement accéder aux détails de l'application / activité de premier plan:

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

Cela nécessite une autorisation supplémentaire dans l'activité menifest et fonctionne parfaitement.

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

1
Cela devrait être marqué comme la bonne réponse. Autorisation requise: android.permission.GET_TASKS est le seul inconvénient très mineur des autres méthodes qui peuvent l'éviter.
brandall

3
MODIFIER VERS CE QUI SUIT: Remarque: cette méthode est uniquement destinée au débogage et à la présentation des interfaces utilisateur de gestion des tâches. <- Je viens de le repérer dans le document Java. Cette méthode pourrait donc changer sans préavis à l'avenir.
brandall

47
Remarque: getRunningTasks()est obsolète dans l'API 21 (Lollipop) - developer.android.com/reference/android/app/…
dtyler

@dtyler, OK alors. Existe-t-il une autre méthode à la place? Bien que Google ne souhaite pas que les applications tierces obtiennent des informations sensibles, j'ai la clé de plate-forme de l'appareil. Existe-t-il une autre méthode pour faire la même chose si j'ai des privilèges système?
Yeung

Il existe un moyen d'utiliser "Usage statistics api" introduit dans 21
KunalK

38

J'ai dû trouver la bonne solution à la dure. le code ci-dessous fait partie de cyanogenmod7 (les modifications de la tablette) et est testé sur android 2.3.3 / gingerbread.

méthodes:

  • getForegroundApp - renvoie l'application au premier plan.
  • getActivityForApp - renvoie l'activité de l'application trouvée.
  • isStillActive - détermine si une application trouvée précédemment est toujours l'application active.
  • isRunningService - une fonction d'assistance pour getForegroundApp

nous espérons que cela répond à ce problème dans toute l'étendue (:

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
si cela ne vous dérange pas de me demander, pourquoi avez-vous besoin des méthodes pour voir s'il exécute un service, est toujours actif et pour obtenir une activité juste pour obtenir l'application au premier plan?

2
désolé, mais cela ne fonctionne pas. Certaines applications sont filtrées sans raison, je pense que c'est lorsqu'elles exécutent un service. Alors isRunningService les expulse.
seb

15
Malheureusement, getRunningTasks () est obsolète depuis Android L (API 20). À partir de L, cette méthode n'est plus disponible pour les applications tierces: l'introduction de recents centrés sur le document signifie qu'elle peut divulguer des informations sur la personne à l'appelant. Pour des raisons de compatibilité ascendante, il renverra toujours un petit sous-ensemble de ses données: au moins les propres tâches de l'appelant, et éventuellement certaines autres tâches telles que la maison qui sont connues pour ne pas être sensibles.
Sam Lu

Est-ce que quelqu'un sait comment cette approche est meilleure que de simplement faire mActivityManager.getRunningTasks(1).get(0).topActivity?
Sam

35

Essayez le code suivant:

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

Le nom du processus est le nom du package de l'application s'exécutant au premier plan. Comparez-le au nom du package de votre application. S'il en est de même, votre application s'exécute au premier plan.

J'espère que cela répond à votre question.


7
À partir de la version Android L, c'est la seule solution qui fonctionne car les méthodes utilisées dans les autres solutions sont devenues obsolètes.
androidGuy

4
cependant, cela ne fonctionne pas si l'application est au premier plan et que l'écran est verrouillé
Krit

La bonne réponse doit être cochée
A_rmas

1
A-t-il besoin d'une autorisation supplémentaire comme une réponse acceptée?
wonsuc

1
Ce n'est pas tout à fait correct. Le nom d'un processus n'est pas toujours le même que le nom du package d'application correspondant.
Sam

25

À partir de Lollipop, cela a changé. Veuillez trouver le code ci-dessous, avant que cet utilisateur n'ait à accéder aux paramètres -> Sécurité -> (Faites défiler vers le bas) Applications avec accès à l'utilisation -> Donnez les autorisations à notre application

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
ces statistiques d'utilisation ne peuvent-elles pas être définies par programme?
rubmz

@rubmz Avez-vous une solution à ce sujet?
VVB

1
dans mon doogee avec android 5.1, il n'y a pas d'option "Donner les autorisations à notre application"
djdance

Cela ne produit pas des résultats précis à 100% ... c'est juste la plupart du temps
CamHart

1
Cela n'affichera pas la dernière application au premier plan, mais peut-être la dernière "utilisée" d'une certaine manière - si vous quittez une activité et passez à une autre, tirez le menu du lanceur d'en haut et relâchez-la, vous serez dans le " mauvaise "activité jusqu'à ce que vous alliez à une autre. Peu importe si vous faites défiler l'écran ou non, cela signale la mauvaise activité jusqu'à ce que vous en lanciez une autre.
Azurlake

9

Pour les cas où nous devons vérifier à partir de notre propre service / thread d'arrière-plan si notre application est au premier plan ou non. Voici comment je l'ai implémenté, et cela fonctionne très bien pour moi:

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
}

Maintenant, pour vérifier à partir d'autres classes, si l'application est au premier plan ou non, appelez simplement:

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

Mise à jour (comme indiqué par SHS ):

N'oubliez pas de vous inscrire aux rappels dans la onCreateméthode de votre classe Application .

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

2
C'est ce que je cherchais depuis deux heures. Je vous remercie! :)
waseefakhtar

1
Nous devons appeler "registerActivityLifecycleCallbacks (this);" à l'intérieur de la méthode Override "onCreate ()" de la classe Application (TestApplication). Cela lancera les rappels dans notre classe d'application.
SHS

@SarthakMittal, Si un appareil passe en mode veille ou lorsque nous le verrouillons, onActivityStopped () sera appelé. En fonction de votre code, cela indiquera que l'application est en arrière-plan. Mais c'est en fait au premier plan.
Ayaz Alifov

@AyazAlifov Si le téléphone est en veille ou le téléphone est verrouillé, je ne le considérerai pas comme étant au premier plan, mais encore une fois, cela dépend de vos préférences.
Sarthak Mittal

8

Afin de déterminer l'application de premier plan, vous pouvez utiliser pour détecter l'application de premier plan, vous pouvez utiliser https://github.com/ricvalerio/foregroundappchecker . Il utilise différentes méthodes en fonction de la version Android de l'appareil.

En ce qui concerne le service, le repo fournit également le code dont vous avez besoin. Essentiellement, laissez android studio créer le service pour vous, puis onCreate ajoutez l'extrait de code qui utilise appChecker. Vous devrez cependant demander la permission.


parfait!! testé sur android 7
Pratik Tank

Merci beaucoup. C'est la seule réponse qui a résolu le problème auquel nous étions confrontés avec les notifications d'applications. Lorsqu'une notification apparaît, toutes les autres réponses renvoient le nom du package de l'application qui a envoyé la notification. Votre LollipopDetector a résolu ce problème. Un conseil: à partir de l'API 29, MOVE_TO_FOREGROUND est obsolète dans UsageEvents.Event, donc à partir d'Android Q, il faut utiliser ACTIVITY_RESUMED. Ensuite, cela fonctionnera comme un charme!
Jorn Rigter

8

Compte tenu de ce qui getRunningTasks()est obsolète et getRunningAppProcesses()n'est pas fiable, j'ai pris la décision de combiner 2 approches mentionnées dans 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
vous devez mentionner pour ajouter au manifeste l'autorisation obsolète <uses-permission android: name = "android.permission.GET_TASKS" /> sinon l'application plantera
RJFares

1
android.permission.GET_TASKS - est actuellement interdit dans le Play Store
Duna le

6

La classe ActivityManager est l'outil approprié pour voir quels processus sont en cours d'exécution.

Pour s'exécuter en arrière-plan, vous souhaitez généralement utiliser un service .


1

Cela a fonctionné pour moi. Mais il ne donne que le nom du menu principal. C'est-à-dire que si l'utilisateur a ouvert l'écran Paramètres -> Bluetooth -> Nom du périphérique, RunningAppProcessInfo l'appelle simplement Paramètres. Impossible de percer 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
Je pense que cela ne fonctionne que si votre propre application s'exécute au premier plan. Il ne signale rien lorsqu'une autre application est au premier plan.
Alice Van Der Land le

0

Faites quelque chose comme ceci:

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 rejettera probablement une application qui utilise ActivityManager.getRunningTasks (). La documentation indique que c'est uniquement à des fins de développement : developer.android.com/reference/android/app/... Voir le commentaire
initial

3
@ForceMagic - Google ne rejette pas les applications simplement pour avoir utilisé des API peu fiables ; de plus, Google n'est pas le seul canal de distribution des applications Android. Cela dit, il convient de garder à l'esprit que les informations de cette API ne sont pas fiables.
Chris Stratton

@ChrisStratton Intéressant, mais vous avez probablement raison, bien qu'il soit déconseillé de l'utiliser pour la logique de base, ils ne rejetteront pas l'application. Vous voudrez peut-être avertir Sky Kelsey à partir du lien de commentaire.
ForceMagic

3
"getRunningTasks" fonctionne pour l'api 21 et au-delà, il serait donc peut-être bon de trouver un autre moyen de le faire fonctionner pour LOLLIPOP (je n'ai moi-même pas compris cela moi-même :()
amIT

0

En sucette et plus:

Ajouter au mainfest:

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

Et faites quelque chose comme ça:

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

4
Il est obsolète en guimauve
jinkal

0

C'est ainsi que je vérifie si mon application est au premier plan. Remarque J'utilise AsyncTask comme suggéré par la documentation officielle d' 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;
    }
}

et exécutez AsyncTask comme ceci. new CheckIfForeground().execute();


as tu un lien pour ça?
user1743524

0

J'ai combiné deux solutions en une seule méthode et cela fonctionne pour moi pour l'API 24 et pour l'API 21. D'autres que je n'ai pas testées.

Le code dans 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()
}

et dans Manifest

<!-- Check whether app in background or foreground -->
<uses-permission android:name="android.permission.GET_TASKS" />
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.