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)?
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)?
Intent
an die Aktivität - hier ist ein Beispielprojekt , das dieses Muster demonstriertPendingIntent
bereitstellen (z. B. über createPendingResult()
), die der Service aufruftbindService()
ein Callback- oder Listener-Objekt mit dem Service registrieren, und rufen Sie mit dem Service eine Ereignismethode für dieses Callback/Listener-Objekt aufIntent
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 MusterHier 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.
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"/>
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 .
AccessibilityService
AccessibilityService
ermitteln.onAccessibilityEvent
callback nach dem TYPE_WINDOW_STATE_CHANGED
- Ereignistyp , um festzustellen, wann sich das aktuelle Fenster ändert.PackageManager.getActivityInfo()
aufrufen.GET_TASKS
.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. AccessibilityService
kennt die aktuelle Aktivität erst beim ersten change der Aktivität.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() {}
}
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>
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"/>
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.
Es kann gemacht werden durch:
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;
}
}
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.
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.)
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:
ActivityManager.getRunningTasks
( Beispiel )ActivityManager.getRunningAppProcesses
( Beispiel )ActivityManager.RunningAppProcessInfo.processState
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);
}
}
Fügen Sie die Berechtigung GET_TASKS
zu AndroidManifest.xml
hinzu:
<!--suppress DeprecatedClassUsageInspection -->
<uses-permission Android:name="Android.permission.GET_TASKS" />
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");
}
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();
}
}
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);
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.