Ermitteln der aktuellen Vordergrundanwendung anhand einer Hintergrundaufgabe oder eines Hintergrunddienstes

112

Ich möchte eine Anwendung, die im Hintergrund ausgeführt wird und weiß, wann eine der integrierten Anwendungen (Messaging, Kontakte usw.) ausgeführt wird.

Meine Fragen sind also:

  1. Wie soll ich meine Anwendung im Hintergrund ausführen?

  2. Wie meine Hintergrundanwendung wissen kann, welche Anwendung derzeit im Vordergrund ausgeführt wird.

Antworten von Leuten mit Erfahrung wären sehr dankbar.

Dhaiwat
quelle
Ich glaube nicht, dass Sie eine angemessene Erklärung dafür gegeben haben, was Sie versuchen zu tun. Was versucht Ihre Hintergrundanwendung zu tun? Auf welche Weise sollte es in der Lage sein, mit dem Benutzer zu interagieren? Warum müssen Sie wissen, was die aktuelle Vordergrund-App ist? Usw.
Charles Duffy
1
Zum Erkennen der Vordergrund-App können Sie github.com/ricvalerio/foregroundappchecker
rvalerio

Antworten:

104

In Bezug auf "2. Wie meine Hintergrundanwendung wissen kann, welche Anwendung derzeit im Vordergrund ausgeführt wird."

Verwenden Sie die getRunningAppProcesses()Methode NICHT , da dies meiner Erfahrung nach alle Arten von Systemabfällen zurückgibt und Sie mehrere Ergebnisse erhalten, die haben RunningAppProcessInfo.IMPORTANCE_FOREGROUND. Verwenden Sie getRunningTasks()stattdessen

Dies ist der Code, den ich in meinem Dienst verwende, um die aktuelle Vordergrundanwendung zu identifizieren. Es ist wirklich einfach:

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

Das ist es, dann können Sie einfach auf Details der Vordergrund-App / Aktivität zugreifen:

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

Dies erfordert eine zusätzliche Erlaubnis im Aktivitätsmenifest und funktioniert einwandfrei.

<uses-permission android:name="android.permission.GET_TASKS" />
Oliver Pearmain
quelle
1
Dies sollte als die richtige Antwort markiert werden. Erforderliche Berechtigung: android.permission.GET_TASKS ist der einzige sehr kleine Nachteil anderer Methoden, die dies vermeiden können.
Brandall
3
BEARBEITEN OBEN: Hinweis: Diese Methode dient nur zum Debuggen und Präsentieren von Benutzeroberflächen für die Aufgabenverwaltung. <- Hab das gerade im Java-Dokument entdeckt. Daher kann sich diese Methode in Zukunft ohne vorherige Ankündigung ändern.
Brandall
47
Hinweis: getRunningTasks()ist in API 21 (Lollipop) - developer.android.com/reference/android/app/…
dtyler
@dtyler, OK dann. Gibt es stattdessen eine andere Methode? Obwohl Google nicht möchte, dass Apps von Drittanbietern vertrauliche Informationen erhalten, habe ich den Plattformschlüssel des Geräts. Gibt es eine andere Methode, die dasselbe tut, wenn ich über Systemberechtigungen verfüge?
Yeung
Es gibt eine Möglichkeit, "Usage Statistics API" zu verwenden, die in 21
KunalK
38

Ich musste auf die harte Tour die richtige Lösung finden. Der folgende Code ist Teil von Cyanogenmod7 (die Tablet-Optimierungen) und wird auf Android 2.3.3 / Lebkuchen getestet.

Methoden:

  • getForegroundApp - gibt die Vordergrundanwendung zurück.
  • getActivityForApp - gibt die Aktivität der gefundenen App zurück.
  • isStillActive - Bestimmt, ob eine zuvor gefundene App noch die aktive App ist.
  • isRunningService - eine Hilfsfunktion für getForegroundApp

Dies beantwortet dieses Problem hoffentlich in vollem Umfang (:

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;
}
Sven Dawitz
quelle
1
Wenn es Ihnen nichts ausmacht, wenn ich Sie frage, warum benötigen Sie die Methoden, um festzustellen, ob ein Dienst ausgeführt wird, noch aktiv ist und um Aktivitäten zu erhalten, nur um die Vordergrundanwendung abzurufen?
2
Entschuldigung, aber es funktioniert nicht. Einige Apps werden ohne Grund gefiltert. Ich denke, es ist, wenn sie einen Dienst ausführen. IsRunningService wirft sie also raus.
seb
15
Leider ist getRunningTasks () seit Android L (API 20) veraltet. Ab L steht diese Methode nicht mehr für Anwendungen von Drittanbietern zur Verfügung: Durch die Einführung dokumentenzentrierter Neuerungen können Personeninformationen an den Anrufer weitergegeben werden. Aus Gründen der Abwärtskompatibilität wird immer noch eine kleine Teilmenge seiner Daten zurückgegeben: zumindest die eigenen Aufgaben des Anrufers und möglicherweise einige andere Aufgaben wie "Home", von denen bekannt ist, dass sie nicht vertraulich sind.
Sam Lu
Weiß jemand, wie dieser Ansatz besser ist als nur mActivityManager.getRunningTasks(1).get(0).topActivity?
Sam
35

Versuchen Sie den folgenden Code:

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

Prozessname ist der Paketname der App, die im Vordergrund ausgeführt wird. Vergleichen Sie es mit dem Paketnamen Ihrer Anwendung. Wenn es dasselbe ist, wird Ihre Anwendung im Vordergrund ausgeführt.

Ich hoffe das beantwortet deine Frage.

Friedrich Bhaer
quelle
7
Ab Android Version L ist dies die einzige Lösung, die funktioniert, da die in den anderen Lösungen verwendeten Methoden veraltet sind.
AndroidGuy
4
Dies funktioniert jedoch nicht, wenn die App im Vordergrund steht und der Bildschirm
gesperrt
Die richtige Antwort sollte angekreuzt werden
A_rmas
1
Benötigt es zusätzliche Erlaubnis wie akzeptierte Antwort?
Wonsuc
1
Das ist nicht ganz richtig. Der Name eines Prozesses stimmt nicht immer mit dem Namen des entsprechenden App-Pakets überein.
Sam
25

Ab Lutscher wurde dies geändert. Den folgenden Code finden Sie, bevor dieser Benutzer zu Einstellungen -> Sicherheit -> (Zum letzten Bildlauf scrollen) gehen muss. Apps mit Nutzungszugriff -> Geben Sie die Berechtigungen für unsere 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);
}
Suman
quelle
1
Können diese Nutzungsstatistiken nicht programmgesteuert festgelegt werden?
Rubmz
@rubmz Hast du eine Lösung dafür?
VVB
1
In meinem Doogee mit Android 5.1 gibt es keine Option "Gib die Berechtigungen für unsere App"
DJDance
Dies führt nicht zu 100% genauen Ergebnissen ... es ist die meiste Zeit
richtig
1
Dadurch wird nicht die letzte App im Vordergrund angezeigt, sondern möglicherweise die zuletzt "verwendete". Wenn Sie eine Aktivität verlassen und zu einer anderen wechseln, das Launcher-Menü von oben ziehen und loslassen, befinden Sie sich im ". falsche "Aktivität, bis Sie zu einer anderen gehen. Es spielt keine Rolle, ob Sie auf dem Bildschirm scrollen oder nicht, es wird die falsche Aktivität gemeldet, bis Sie eine andere starten.
Azurlake
9

Für Fälle, in denen wir von unserem eigenen Service / Hintergrund-Thread aus prüfen müssen, ob unsere App im Vordergrund steht oder nicht. So habe ich es implementiert und es funktioniert gut für mich:

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
}

Um nun anhand anderer Klassen zu überprüfen, ob die App im Vordergrund steht oder nicht, rufen Sie einfach Folgendes auf:

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

Update (wie von SHS hervorgehoben ):

Vergessen Sie nicht, sich für die Rückrufe in der onCreateMethode Ihrer Anwendungsklasse zu registrieren .

@Override
public void onCreate() {
    ...
    registerActivityLifecycleCallbacks(this);
}
Sarthak Mittal
quelle
2
Das habe ich seit zwei Stunden gesucht. Danke dir! :)
waseefakhtar
1
Wir müssen "registerActivityLifecycleCallbacks (this)" aufrufen. innerhalb der Override-Methode "onCreate ()" der Anwendungsklasse (TestApplication). Dadurch werden die Rückrufe in unserer Anwendungsklasse gestartet.
SHS
@SarthakMittal, Wenn ein Gerät in den Standby-Modus wechselt oder wenn wir es sperren, wird onActivityStopped () aufgerufen. Basierend auf Ihrem Code zeigt dies an, dass sich die Anwendung im Hintergrund befindet. Aber es steht tatsächlich im Vordergrund.
Ayaz Alifov
@AyazAlifov Wenn sich das Telefon im Standby-Modus befindet oder das Telefon gesperrt ist, wird es nicht als Vordergrund betrachtet, aber es hängt wiederum von den Vorlieben ab.
Sarthak Mittal
8

Um die Vordergrundanwendung zu bestimmen, können Sie zur Erkennung der Vordergrundanwendung https://github.com/ricvalerio/foregroundappchecker verwenden . Abhängig von der Android-Version des Geräts werden unterschiedliche Methoden verwendet.

Für den Service stellt das Repo auch den Code bereit, den Sie dafür benötigen. Lassen Sie Android Studio im Wesentlichen den Dienst für Sie erstellen und fügen Sie dann onCreate das Snippet hinzu, das den AppChecker verwendet. Sie müssen jedoch eine Erlaubnis anfordern.

rvalerio
quelle
perfekt!! getestet auf Android 7
Pratik Tank
Vielen Dank. Dies ist die einzige Antwort, die das Problem mit Benachrichtigungen über Apps gelöst hat. Wenn eine Benachrichtigung angezeigt wird, geben alle anderen Antworten den Paketnamen der App zurück, die die Benachrichtigung gesendet hat. Ihr LollipopDetector hat dieses Problem behoben. Ein Hinweis: Ab API 29 ist MOVE_TO_FOREGROUND in UsageEvents.Event veraltet. Ab Android Q sollte daher ACTIVITY_RESUMED verwendet werden. Dann wird es wie ein Zauber wirken!
Jorn Rigter
8

Angesichts der Tatsache, dass dies getRunningTasks()veraltet und getRunningAppProcesses()nicht zuverlässig ist, habe ich beschlossen, zwei in StackOverflow erwähnte Ansätze zu kombinieren:

   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();
        }
    }
Ayaz Alifov
quelle
4
Sie müssen erwähnen, dass Sie dem Manifest die veraltete Berechtigung hinzufügen <verwendungsberechtigung android: name = "android.permission.GET_TASKS" />, sonst stürzt die App ab
RJFares
1
android.permission.GET_TASKS - ist derzeit im Play Store verboten
Duna
6

Der ActivityManager Klasse ist das geeignete Tool, um festzustellen, welche Prozesse ausgeführt werden.

Um im Hintergrund ausgeführt zu werden, möchten Sie normalerweise einen Dienst verwenden .

Charles Duffy
quelle
1

Das hat bei mir funktioniert. Es wird jedoch nur der Name des Hauptmenüs angegeben. Wenn der Benutzer den Bildschirm Einstellungen -> Bluetooth -> Gerätename geöffnet hat, nennt RunningAppProcessInfo ihn nur als Einstellungen. Nicht in der Lage, weiter zu bohren

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());
                    }               
                }
NakulSargur
quelle
4
Ich denke, das funktioniert nur, wenn Ihre eigene App im Vordergrund läuft. Es wird nichts gemeldet, wenn eine andere App im Vordergrund steht.
Alice Van Der Land
0

Mach so etwas:

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;
}
ChristianS
quelle
Google wird wahrscheinlich eine App ablehnen, die ActivityManager.getRunningTasks () verwendet. Die Dokumentation besagt, dass es sich nur um Feindentwicklungszwecke handelt : developer.android.com/reference/android/app/… Siehe den ersten Kommentar: stackoverflow.com/questions/4414171/…
ForceMagic
3
@ForceMagic - Google lehnt Apps nicht einfach wegen der Verwendung unzuverlässiger APIs ab. Darüber hinaus ist Google nicht der einzige Vertriebskanal für Android-Anwendungen. Beachten Sie jedoch, dass die Informationen aus dieser API nicht zuverlässig sind.
Chris Stratton
@ChrisStratton Interessant, aber Sie haben wahrscheinlich Recht, obwohl davon abgeraten wird, es für die Kernlogik zu verwenden, werden sie die App nicht ablehnen. Vielleicht möchten Sie Sky Kelsey über den Kommentar-Link warnen.
ForceMagic
3
"getRunningTasks" funktioniert für API 21 und darüber hinaus, daher ist es möglicherweise eine gute Idee, einen anderen Weg zu finden, damit es für LOLLIPOP funktioniert (ich selbst habe dies jedoch nicht selbst herausgefunden :()
amIT
0

In Lutscher und höher:

Zum Hauptfest hinzufügen:

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

Und mach so etwas:

if( mTaskId < 0 )
{
    List<AppTask> tasks = mActivityManager.getAppTasks(); 
    if( tasks.size() > 0 )
        mTaskId = tasks.get( 0 ).getTaskInfo().id;
}
rubmz
quelle
4
Es ist in Marshmallow
Jinkal
0

So überprüfe ich, ob meine App im Vordergrund steht. Hinweis Ich verwende AsyncTask, wie in der offiziellen Android- Dokumentation vorgeschlagen. "

`

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

und führen Sie AsyncTask wie folgt aus. new CheckIfForeground().execute();

Entwickeln
quelle
Hast du einen Link dafür?
user1743524
0

Ich habe zwei Lösungen in einer Methode kombiniert und es funktioniert für mich für API 24 und für API 21. Andere habe ich nicht getestet.

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

und im Manifest

<!-- Check whether app in background or foreground -->
<uses-permission android:name="android.permission.GET_TASKS" />
Denshov
quelle