【问题标题】:Android: How can I get the current foreground activity (from a service)?Android:如何获取当前的前台活动(来自服务)?
【发布时间】:2011-04-21 21:07:49
【问题描述】:

是否有一种原生的 android 方法可以从服务中获取对当前正在运行的 Activity 的引用?

我有一个在后台运行的服务,我想在事件发生时更新我当前的活动(在服务中)。有没有一种简单的方法可以做到这一点(就像我上面建议的那样)?

【问题讨论】:

标签: android service android-activity


【解决方案1】:

更新:此no longer works其他应用的活动(自 Android 5.0 起)


这是一个使用活动管理器的好方法。 您基本上可以从活动管理器中获取 runningTasks。它总是首先返回当前活动的任务。从那里你可以得到topActivity。

Example here

有一种从 ActivityManager 服务获取正在运行的任务列表的简单方法。 您可以请求手机上运行的任务的最大数量,默认情况下,首先返回当前活动的任务。

一旦你有了它,你就可以通过从你的列表中请求 topActivity 来获得一个 ComponentName 对象。

这是一个例子。

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

您的清单需要以下权限:

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

【讨论】:

  • 你的答案是 100% 正确的。Thanxx 4 它。你最好在这里发布相同的答案
  • 如果我更频繁地需要当前活动,我必须非常频繁地轮询,对吗?有没有办法在前台活动发生变化时获取回调事件?
  • 大家都知道,文档说明了有关 getRunningTasks() 方法的内容。 -------- 注意:此方法仅用于调试和呈现任务管理用户界面。这绝不应该用于应用程序中的核心逻辑,例如根据此处找到的信息来决定不同的行为。此类用途不受支持,将来可能会中断。例如,如果多个应用程序可以同时主动运行,则出于控制流的目的对此处数据的含义所做的假设将是不正确的。 ------------
  • 半假在 api 21 As of LOLLIPOP, this method is no longer available to third party applications: the introduction of document-centric recents means it can leak person information to the caller. For backwards compatibility, it will still return a small subset of its data: at least the caller's own tasks, and possibly some other tasks such as home that are known to not be sensitive.
  • 有什么方法可以让我们在 Lollipop 中获得最高活动?
【解决方案2】:

警告:Google Play 违规

Google has threatened 将应用程序从 Play 商店中删除,如果他们将无障碍服务用于非无障碍目的。但是,this is reportedly being reconsidered


使用AccessibilityService

好处

  • 在 Android 2.2 (API 8) 到 Android 7.1 (API 25) 中经过测试和工作。
  • 不需要轮询。
  • 不需要GET_TASKS 权限。

缺点

  • 每个用户都必须在 Android 的辅助功能设置中启用该服务。
  • 这不是 100% 可靠的。有时事件会出现乱序。
  • The service is always running.
  • 当用户尝试启用AccessibilityService 时,如果应用程序在屏幕上放置了覆盖,他们将无法按下确定按钮。一些执行此操作的应用程序是 Velis Auto Brightness 和 Lux。这可能会令人困惑,因为用户可能不知道他们为什么不能按下按钮或如何解决它。
  • AccessibilityService 在第一次更改活动之前不会知道当前活动。

示例

服务

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

将其合并到您的清单中:

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

服务信息

把这个放到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"/>

启用服务

应用程序的每个用户都需要明确启用AccessibilityService 才能使用它。请参阅this StackOverflow answer 了解如何执行此操作。

请注意,如果应用在屏幕上放置了叠加层(例如 Velis Auto Brightness 或 Lux),则用户在尝试启用无障碍服务时将无法按下 OK 按钮。

【讨论】:

  • @lovemint,我的意思是:我指定了your.app.ServiceSettingsActivity 的示例settingsActivity,因此您应该将其更改为您自己的无障碍服务设置活动。我认为设置活动无论如何都是可选的,因此我已从答案中删除了该部分以使其更简单。
  • 非常感谢,最后一个问题。如果我需要从 API 8 处理到当前,我应该在代码中而不是使用 XML 来完成这项工作?
  • @lovemint,没错。我刚刚更新了示例代码来执行此操作。
  • @Sam 我可以确认该服务运行良好。一个奇怪的行为,我在主要活动中使用了一个意图来启用辅助功能服务。在第一次激活后,服务被激活,但onAccessibilityEvent 没有收到任何事件,但如果我禁用并再次启用辅助功能服务,它会重新激活,onAccessibilityEvent 开始工作。
  • 感谢您的代码。我为你创建了this app :)
【解决方案3】:

是否有一种原生的 android 方法可以从服务中获取对当前正在运行的 Activity 的引用?

您可能不拥有“当前运行的 Activity”。

我有一个在后台运行的服务,我想在事件发生时更新我当前的活动(在服务中)。有没有一种简单的方法可以做到这一点(就像我上面建议的那样)?

  1. 向活动发送Intent 广播 -- here is a sample project 演示此模式
  2. 让活动提供服务调用的PendingIntent(例如,通过createPendingResult()
  3. 让活动通过bindService() 向服务注册回调或侦听器对象,并让服务在该回调/侦听器对象上调用事件方法
  4. 向活动发送一个有序的广播Intent,以低优先级BroadcastReceiver作为备份(如果活动不在屏幕上,则引发Notification)--here is a blog post,更多关于此模式

【讨论】:

  • 谢谢,但由于我确实有大量活动,并且不想全部更新,因此我正在寻找一种获取 foregroundActivity 的 android 方法。正如你所说,我不应该那样做。意味着我必须解决这个问题。
  • @George:无论如何,你想要的对你没有帮助。正如您所指出的,您有“大量活动”。因此,这些都是单独的类。因此,如果没有 instanceof 的大量 if 块检查或重构活动以共享公共超类或接口,您的服务将无法使用它们。如果你要重构活动,你不妨以更适合框架并涵盖更多场景的方式进行,例如你的活动都没有处于活动状态。 #4 可能是最少的工作和最灵活的。
  • 谢谢,但我有更好的解决方案。我所有的活动都扩展了一个定制的 BaseActivity 类。我已经设置了一个 ContextRegister,只要它在前台,它就会将活动注册为当前,在我的 BaseActivity 类中使​​用 literraly 3 行。不过,感谢您的支持。
  • @George:注意内存泄漏。在 Java 中尽可能避免使用可变的静态数据成员。
  • 我已经紧紧地封装了这个对象,尽管我确实会记住这一点。谢谢。
【解决方案4】:

可以通过以下方式完成:

  1. 实现您自己的应用程序类,注册 ActivityLifecycleCallbacks - 这样您就可以看到我们的应用程序发生了什么。在每次恢复时,回调都会分配屏幕上当前可见的活动,并在暂停时删除分配。它使用 API 14 中添加的方法 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. 在您的服务调用中 getApplication() 并将其转换为您的应用类名称(在本例中为 App)。你可以打电话给app.getActiveActivity() - 这会给你一个当前可见的活动(或者当没有活动可见时为空)。可以通过调用activeActivity.getClass().getSimpleName()

  3. 获取Activity的名称

【讨论】:

  • 我在 activeActivity.getClass().getSimpleName() 收到 Nullpointer 异常。请您帮忙
  • 正如您从 ActivityLifecycleCallbacks 中看到的那样 - 如果活动不可见,application.getActiveActivity() 将返回 null。这意味着没有可见的活动。您需要在您的服务或您使用它的任何其他地方检查此内容。
  • 好的..但是如果活动恢复,那么这不应该返回 null ??我的活动已经启动,并且在简历中我得到了这样的结果
  • 很难猜,尝试在onActivityResumed()onActivityPaused()getActiveActivity() 设置断点,看看它是如何调用callet(如果有的话)。
  • @beginner 在分配activeActivitynull 之前必须检查activityactiveActivity 是否相同,以避免由于各种活动的生命周期方法的混合调用顺序而导致错误。
【解决方案5】:

我找不到我们的团队会满意的解决方案,所以我们推出了自己的解决方案。我们使用ActivityLifecycleCallbacks 来跟踪当前活动,然后通过服务公开它:

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

然后将您的 DI 容器配置为为 ContextProvider 返回 MyApplication 的实例,例如

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

(请注意,上面的代码中省略了getCurrent()的实现。它只是一个从应用程序构造函数中设置的静态变量)

【讨论】:

  • 在分配currentActivitynull之前必须检查activitycurrentActivity是否相同,以避免由于各种活动的生命周期方法的混合调用顺序而导致错误。
【解决方案6】:

使用ActivityManager

如果您只想知道包含当前活动的应用程序,可以使用ActivityManager 进行。您可以使用的技术取决于 Android 的版本:

好处

  • 应该适用于迄今为止的所有 Android 版本。

缺点

  • 不适用于 Android 5.1+(它只会返回您自己的应用)
  • The documentation for these APIs 表示它们仅用于调试和管理用户界面。
  • 如果要实时更新,需要使用轮询。
  • 依赖于隐藏的 API:ActivityManager.RunningAppProcessInfo.processState
  • 此实现不会获取应用切换器活动。

示例(基于KNaito's 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);
    }

}

清单

GET_TASKS权限添加到AndroidManifest.xml

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

【讨论】:

  • 这不再适用于 Android M。 getRunningAppProcesses() 现在只返回你的应用程序包
  • 也不适用于 API 级别 22 (Android 5.1)。在构建 LPB23 上测试
【解决方案7】:

我正在使用它进行测试。不过,它的 API > 19,用于您应用的活动。

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

【讨论】:

  • 用 Map 替换 ArrayMap,它也适用于 4.3。未测试早期的 Android 版本。
【解决方案8】:

将此代码用于 API 21 或更高版本。与其他答案相比,这有效并提供了更好的结果,它可以完美地检测到前台进程。

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

【讨论】:

  • 您能否详细说明这种方法的结果如何优于其他答案?
  • 这是否也适用于通知菜单。我面临一个问题,如果我接到电话或消息。 UsageStatsManager 开始给我 systemUi 的包名而不是我的应用程序。
  • @kukroid,发布一个单独的问题,并附上您的源代码、设备信息以及您正在使用的消息传递和电话应用程序。
【解决方案9】:

这是我的建议以及对我有用的方法。在您的应用程序类中,实现一个Application.ActivityLifeCycleCallbacks 侦听器并在您的应用程序类中设置一个变量。然后根据需要查询变量。

class YourApplication: Application.ActivityLifeCycleCallbacks {
    
    var currentActivity: Activity? = null

    fun onCreate() {
        registerActivityLifecycleCallbacks(this)
    }

    ...
    
    override fun onActivityResumed(activity: Activity) {
        currentActivity = activity
    }
}

【讨论】:

  • 拯救了我的一天...
【解决方案10】:

我喜欢Application.ActivityLifecycleCallbacks 的想法。但获得热门活动可能有点棘手

  • 您的应用可能有多个活动
  • 某些活动可能会被破坏
  • 某些活动可能会进入后台

要处理所有这些情况,您需要跟踪每个活动生命周期。这正是我对以下解决方案所做的。

如果堆栈中没有活动或它们都不在前台,则所有这些都合并为一个 getTopForegroundActivity() 调用,该调用返回顶部前台活动或 null

用法

public class MyApp extends Application {

    private ActivityTracker activityTracker;

    @Override
    public void onCreate() {
        super.onCreate();

        activityTracker = new ActivityTracker();
        registerActivityLifecycleCallbacks(activityTracker);
        
        ...

        Activity activity = activityTracker.getTopForegroundActivity();
        if(activity != null) {
            // Do something
        }
    }
}

来源

public class ActivityTracker implements Application.ActivityLifecycleCallbacks {
    private final Map<Activity, ActivityData> activities = new HashMap<>();


    public Activity getTopForegroundActivity() {
        if (activities.isEmpty()) {
            return null;
        }
        ArrayList<ActivityData> list = new ArrayList<>(activities.values());
        Collections.sort(list, (o1, o2) -> {
            int compare = Long.compare(o2.started, o1.started);
            return compare != 0 ? compare : Long.compare(o2.resumed, o1.resumed);
        });

        ActivityData topActivity = list.get(0);
        return topActivity.started != -1 ? topActivity.activity : null;
    }


    @Override
    public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
        activities.put(activity, new ActivityData(activity));
    }

    @Override
    public void onActivityStarted(@NonNull Activity activity) {
        ActivityData activityData = activities.get(activity);
        if (activityData != null) {
            activityData.started = System.currentTimeMillis();
        }
    }

    @Override
    public void onActivityResumed(@NonNull Activity activity) {
        ActivityData activityData = activities.get(activity);
        if (activityData != null) {
            activityData.resumed = System.currentTimeMillis();
        }
    }

    @Override
    public void onActivityPaused(@NonNull Activity activity) {
        ActivityData activityData = activities.get(activity);
        if (activityData != null) {
            activityData.resumed = -1;
        }
    }

    @Override
    public void onActivityStopped(@NonNull Activity activity) {
        ActivityData activityData = activities.get(activity);
        if (activityData != null) {
            activityData.started = -1;
        }
    }

    @Override
    public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {

    }

    @Override
    public void onActivityDestroyed(@NonNull Activity activity) {
        activities.remove(activity);
    }

    private static class ActivityData {

        public final Activity activity;
        public long started;
        public long resumed;

        private ActivityData(Activity activity) {
            this.activity = activity;
        }
    }
}

【讨论】:

    【解决方案11】:

    我不知道这是否是一个愚蠢的答案,但是通过在每次我输入任何活动的 onCreate() 时在共享首选项中存储一个标志来解决这个问题,然后我使用来自 shered 首选项的值来找出它是什么前台活动。

    【讨论】:

    • 我们正在寻找一种本地方式来做到这一点
    【解决方案12】:

    这是我的答案,效果很好......

    您应该可以通过这种方式获取当前的 Activity... 如果你用一些带有许多片段的活动来构建你的应用程序,并且你想跟踪你当前的活动是什么,那么这将需要大量的工作。我的情景是我确实有一个包含多个片段的活动。所以我可以通过Application Object来跟踪Current Activity,它可以存储全局变量的所有当前状态。

    这是一种方法。当您启动 Activity 时,您将通过以下方式存储该 Activity Application.setCurrentActivity(getIntent()); 此应用程序将存储它。 在你的服务类上,你可以简单地做 意图 currentIntent = Application.getCurrentActivity(); getApplication().startActivity(currentIntent);

    【讨论】:

    • 这里假设服务和Activity在同一个进程中运行,对吧?
    【解决方案13】:

    最近才知道这件事。用api为:

    • minSdkVersion 19
    • targetSdkVersion 26

      ActivityManager.getCurrentActivity(context)

    希望对你有用。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-07-30
    • 2012-07-09
    • 1970-01-01
    • 1970-01-01
    • 2018-03-03
    • 2017-09-02
    • 1970-01-01
    相关资源
    最近更新 更多