wake-up-neo.com

Android: Wie kann ich die aktuelle Vordergrundaktivität (von einem Dienst) abrufen?

Gibt es eine native Android-Methode, um von einem Dienst einen Hinweis auf die aktuell ausgeführte Aktivität zu erhalten?

Ich habe einen Dienst, der im Hintergrund ausgeführt wird, und ich möchte meine aktuelle Aktivität aktualisieren, wenn ein Ereignis (im Dienst) auftritt. Gibt es eine einfache Möglichkeit, dies zu tun (wie die, die ich oben vorgeschlagen habe)?

162
George

Gibt es eine native Android-Methode, um von einem Dienst einen Hinweis auf die aktuell ausgeführte Aktivität zu erhalten?

Möglicherweise besitzen Sie nicht die "aktuell ausgeführte Aktivität".

Ich habe einen Dienst, der im Hintergrund ausgeführt wird, und ich möchte meine aktuelle Aktivität aktualisieren, wenn ein Ereignis (im Dienst) auftritt. Gibt es eine einfache Möglichkeit, dies zu tun (wie die, die ich oben vorgeschlagen habe)?

  1. Senden Sie eine Sendung Intent an die Aktivität - hier ist ein Beispielprojekt , das dieses Muster demonstriert
  2. Lassen Sie die Aktivität eine PendingIntent bereitstellen (z. B. über createPendingResult()), die der Service aufruft
  3. Lassen Sie die Aktivität über bindService() ein Callback- oder Listener-Objekt mit dem Service registrieren, und rufen Sie mit dem Service eine Ereignismethode für dieses Callback/Listener-Objekt auf
  4. Senden Sie eine geordnete Sendung Intent an die Aktivität, mit einer niedrigen Priorität BroadcastReceiver als Backup (um eine Notification zu erzeugen, wenn die Aktivität nicht auf dem Bildschirm angezeigt wird) - hier ist ein Blogpost mit mehr Informationen zu diesem Muster
81
CommonsWare

Hier ist eine gute Möglichkeit, dies mit dem Aktivitätsmanager zu tun .. Sie erhalten im Prinzip die laufenden Aufgaben vom Aktivitätsmanager. Die aktuell aktive Aufgabe wird immer zuerst zurückgegeben. Von dort können Sie die topActivity erhalten.

Beispiel hier

Es gibt eine einfache Möglichkeit, eine Liste der ausgeführten Aufgaben vom ActivityManager-Dienst abzurufen. Sie können eine maximale Anzahl von Aufgaben anfordern, die auf dem Telefon ausgeführt werden. Standardmäßig wird die derzeit aktive Aufgabe zuerst zurückgegeben.

Sobald Sie das haben, können Sie ein ComponentName-Objekt abrufen, indem Sie die topActivity von Ihrer Liste anfordern.

Hier ist ein Beispiel.

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

Sie benötigen die folgende Berechtigung für Ihr Manifest:

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

Warnung: Google Play-Verletzung

Google hat gedroht , Apps aus dem Play Store zu entfernen, wenn sie Accessibility-Services zu Zwecken der Barrierefreiheit verwenden. dies wird jedoch Berichten zufolge überdacht .


Verwenden Sie eine AccessibilityService

Leistungen

  • Getestet und funktioniert in Android 2.2 (API 8) bis Android 7.1 (API 25).
  • Benötigt keine Abfrage.
  • Erfordert nicht die Berechtigung GET_TASKS .

Nachteile

  • Jeder Benutzer muss den Dienst in den Zugriffseinstellungen von Android aktivieren.
  • Das ist nicht 100% zuverlässig. Gelegentlich werden die Ereignisse nicht in der richtigen Reihenfolge angezeigt.
  • Der Dienst läuft immer.
  • Wenn ein Benutzer versucht, die AccessibilityService zu aktivieren, kann er die Schaltfläche OK nicht drücken, wenn eine App eine Überlagerung auf dem Bildschirm platziert hat. Einige Apps, die dies tun, sind Velis Auto Brightness und Lux. Dies kann verwirrend sein, da der Benutzer möglicherweise nicht weiß, warum er die Taste nicht drücken kann oder wie er umgangen werden kann. 
  • Die AccessibilityService kennt die aktuelle Aktivität erst beim ersten change der Aktivität.

Beispiel

Bedienung

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

Füge dies in dein Manifest ein:

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

Service Info

Schreibe dies 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"/>

Aktivieren des Dienstes

Jeder Benutzer der App muss die Option AccessibilityService explizit aktivieren, damit er verwendet werden kann. Siehe diese StackOverflow-Antwort , um zu erfahren, wie Sie dies tun können.

Beachten Sie, dass der Benutzer nicht in der Lage ist, die OK-Taste zu drücken, wenn Sie versuchen, den Accessibility-Dienst zu aktivieren, wenn eine Anwendung eine Überlagerung auf dem Bildschirm platziert hat, z. B. Velis Auto Brightness oder Lux. 

97
Sam

Es kann gemacht werden durch:

  1. Implementieren Sie Ihre eigene Anwendungsklasse, registrieren Sie sich für ActivityLifecycleCallbacks - auf diese Weise können Sie sehen, was mit unserer App passiert. Bei jedem Neustart weist der Rückruf die aktuell sichtbare Aktivität auf dem Bildschirm zu, und bei Pause entfernt er die Zuweisung. Es verwendet die in API 14 hinzugefügte Methode registerActivityLifecycleCallbacks().

    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. Rufen Sie in Ihrem Service getApplication() auf und wandeln Sie es in Ihren App-Klassennamen um (App in diesem Fall). Dann können Sie app.getActiveActivity() aufrufen - dies gibt Ihnen eine aktuell sichtbare Aktivität (oder null, wenn keine Aktivität sichtbar ist). Sie können den Namen der Aktivität abrufen, indem Sie activeActivity.getClass().getSimpleName() aufrufen.

18
Vit Veres

Ich konnte keine Lösung finden, mit der unser Team zufrieden wäre, also haben wir unsere eigene gewürfelt. Wir verwenden ActivityLifecycleCallbacks, um die aktuellen Aktivitäten zu verfolgen und sie dann über einen Dienst verfügbar zu machen:

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

Konfigurieren Sie dann Ihren DI-Container so, dass die Instanz von MyApplication für ContextProvider zurückgegeben wird, z.

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

(Beachten Sie, dass die Implementierung von getCurrent() im obigen Code weggelassen wird. Es ist nur eine statische Variable, die vom Anwendungskonstruktor festgelegt wird.)

12
Muxa

Verwenden Sie ActivityManager

Wenn Sie nur die Anwendung mit der aktuellen Aktivität erfahren möchten, können Sie dies mit ActivityManager tun. Welche Technik Sie verwenden können, hängt von der Android-Version ab:

Leistungen

  • Sollte in allen Android-Versionen bis heute funktionieren.

Nachteile

  • Funktioniert nicht in Android 5.1+ (es wird nur Ihre eigene App zurückgegeben)
  • Die Dokumentation für diese APIs besagt, dass sie nur zum Debuggen und Verwalten von Benutzeroberflächen gedacht sind.
  • Wenn Sie Aktualisierungen in Echtzeit wünschen, müssen Sie die Abfrage verwenden.
  • Stützt sich auf eine versteckte API: ActivityManager.RunningAppProcessInfo.processState
  • Bei dieser Implementierung wird die Aktivität der App-Umschaltung nicht berücksichtigt.

Beispiel (basierend auf KNaitos Code )

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

}

Manifest

Fügen Sie die Berechtigung GET_TASKS zu AndroidManifest.xml hinzu:

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

Ich benutze das für meine Tests. Es ist jedoch API> 19 und nur für Aktivitäten Ihrer 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");
}
9
espinchi

Verwenden Sie diesen Code für API 21 oder höher. Dies funktioniert und liefert bessere Ergebnisse im Vergleich zu den anderen Antworten. Es erkennt den Vordergrundprozess perfekt. 

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

Hier ist meine Antwort, die gut funktioniert ...

Sie sollten in der Lage sein, die aktuelle Aktivität auf diese Weise abzurufen. Wenn Sie Ihre App mit einigen Aktivitäten mit vielen Fragmenten strukturieren und den Überblick über Ihre aktuelle Aktivität behalten möchten, ist dies jedoch sehr zeitaufwändig . Mein Senario bestand aus einer Aktivität mit mehreren Fragmenten. So kann ich die aktuelle Aktivität über das Anwendungsobjekt verfolgen, in dem der aktuelle Status globaler Variablen gespeichert werden kann.

Hier ist ein Weg. Wenn Sie Ihre Aktivität starten, speichern Sie diese Aktivität mit Application.setCurrentActivity (getIntent ()); Diese Anwendung speichert es . In Ihrer Serviceklasse können Sie einfach wie Intent currentIntent = Application.getCurrentActivity (); getApplication (). startActivity (currentIntent);

0
John3328

Ich weiß nicht, ob es eine dumme Antwort ist, aber dieses Problem wurde behoben, indem bei jeder Eingabe von onCreate () einer Aktivität ein Flag in den gemeinsamen Einstellungen gespeichert wurde. Dann habe ich den Wert aus den abgespeckten Einstellungen verwendet, um herauszufinden, was die Vordergrundaktivität ist.

0
Vlad Vladescu