简体   繁体   English

Android:如何获取当前的前台活动(来自服务)?

[英]Android: How can I get the current foreground activity (from a service)?

Is there a native android way to get a reference to the currently running Activity from a service?是否有一种原生的 android 方法可以从服务中获取对当前正在运行的 Activity 的引用?

I have a service running on the background, and I would like to update my current Activity when an event occurs (in the service).我有一个在后台运行的服务,我想在事件发生时更新我当前的活动(在服务中)。 Is there a easy way to do that (like the one I suggested above)?有没有一种简单的方法可以做到这一点(就像我上面建议的那样)?

Update : this no longer works with other apps' activities as of Android 5.0更新:从 Android 5.0 开始,这不再适用其他应用的活动


Here's a good way to do it using the activity manager.这是使用活动管理器执行此操作的好方法。 You basically get the runningTasks from the activity manager.您基本上可以从活动管理器中获取 runningTasks。 It will always return the currently active task first.它总是首先返回当前活动的任务。 From there you can get the topActivity.从那里你可以得到topActivity。

Example here 这里的例子

There's an easy way of getting a list of running tasks from the ActivityManager service.有一种从 ActivityManager 服务获取正在运行的任务列表的简单方法。 You can request a maximum number of tasks running on the phone, and by default, the currently active task is returned first.您可以请求在手机上运行的最大任务数,默认情况下,首先返回当前活动的任务。

Once you have that you can get a ComponentName object by requesting the topActivity from your list.一旦你有了它,你可以通过从你的列表中请求 topActivity 来获得一个 ComponentName 对象。

Here's an example.这是一个例子。

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

You will need the following permission on your manifest:您的清单需要以下权限:

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

Warning: Google Play violation警告:Google Play 违规

Google has threatened to remove apps from the Play Store if they use accessibility services for non-accessibility purposes. 谷歌威胁说,如果应用程序将无障碍服务用于非无障碍目的,他们将从 Play 商店中删除。 However, this is reportedly being reconsidered .然而, 据报道,这正在重新考虑


Use an AccessibilityService使用AccessibilityService

Benefits好处

  • Tested and working in Android 2.2 (API 8) through Android 7.1 (API 25).在 Android 2.2 (API 8) 到 Android 7.1 (API 25) 中测试和工作。
  • Doesn't require polling.不需要轮询。
  • Doesn't require the GET_TASKS permission.不需要GET_TASKS权限。

Disadvantages缺点

  • Each user must enable the service in Android's accessibility settings.每个用户都必须在 Android 的辅助功能设置中启用该服务。
  • This isn't 100% reliable.这不是 100% 可靠的。 Occasionally the events come in out-of-order.有时事件会出现乱序。
  • The service is always running. 该服务始终在运行。
  • When a user tries to enable the AccessibilityService , they can't press the OK button if an app has placed an overlay on the screen.当用户尝试启用AccessibilityService时,如果应用程序在屏幕上放置了覆盖,则他们无法按下 OK 按钮。 Some apps that do this are Velis Auto Brightness and Lux.一些执行此操作的应用程序是 Velis Auto Brightness 和 Lux。 This can be confusing because the user might not know why they can't press the button or how to work around it.这可能会令人困惑,因为用户可能不知道他们为什么不能按下按钮或如何解决它。
  • The AccessibilityService won't know the current activity until the first change of activity. AccessibilityService在第一次更改活动之前不会知道当前活动。

Example例子

Service服务

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 AndroidManifest.xml

Merge this into your manifest:将其合并到您的清单中:

<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服务信息

Put this in res/xml/accessibilityservice.xml :把它放在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"/>

Enabling the Service启用服务

Each user of the app will need to explicitly enable the AccessibilityService in order for it to be used.应用程序的每个用户都需要显式启用AccessibilityService才能使用它。 See this StackOverflow answer for how to do this.有关如何执行此操作的信息,请参阅此 StackOverflow 答案

Note that the user won't be able to press the OK button when trying to enable the accessibility service if an app has placed an overlay on the screen, such as Velis Auto Brightness or Lux.请注意,如果应用程序在屏幕上放置了叠加层,例如 Velis Auto Brightness 或 Lux,则用户在尝试启用无障碍服务时将无法按下 OK 按钮。

Is there a native android way to get a reference to the currently running Activity from a service?是否有一种原生的 android 方法可以从服务中获取对当前正在运行的 Activity 的引用?

You may not own the "currently running Activity".您可能不拥有“当前运行的活动”。

I have a service running on the background, and I would like to update my current Activity when an event occurs (in the service).我有一个在后台运行的服务,我想在事件发生时更新我当前的活动(在服务中)。 Is there a easy way to do that (like the one I suggested above)?有没有一种简单的方法可以做到这一点(就像我上面建议的那样)?

  1. Send a broadcast Intent to the activity -- here is a sample project demonstrating this pattern向活动发送广播Intent - 这是一个演示此模式的示例项目
  2. Have the activity supply a PendingIntent (eg, via createPendingResult() ) that the service invokes让活动提供服务调用的PendingIntent (例如,通过createPendingResult()
  3. Have the activity register a callback or listener object with the service via bindService() , and have the service call an event method on that callback/listener object让活动通过bindService()向服务注册回调或侦听器对象,并让服务调用该回调/侦听器对象上的事件方法
  4. Send an ordered broadcast Intent to the activity, with a low-priority BroadcastReceiver as backup (to raise a Notification if the activity is not on-screen) -- here is a blog post with more on this pattern向 Activity 发送有序的广播Intent ,使用低优先级的BroadcastReceiver作为备份(如果 Activity 不在屏幕上,则发出Notification )—— 这是一篇关于此模式的博客文章

It can be done by:可以通过以下方式完成:

  1. Implement your own application class, register for ActivityLifecycleCallbacks - this way you can see what is going on with our app.实现您自己的应用程序类,注册 ActivityLifecycleCallbacks - 这样您就可以看到我们的应用程序发生了什么。 On every on resume the callback assigns the current visible activity on the screen and on pause it removes the assignment.在每次恢复时,回调都会分配屏幕上当前可见的活动,并在暂停时删除分配。 It uses method registerActivityLifecycleCallbacks() which was added in API 14.它使用 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. In your service call getApplication() and cast it to your app class name (App in this case).在您的服务调用getApplication()并将其转换为您的应用程序类名称(在本例中为 App)。 Than you can call app.getActiveActivity() - that will give you a current visible Activity (or null when no activity is visible).比你可以调用app.getActiveActivity() - 这会给你一个当前可见的活动(或 null 当没有活动是可见的)。 You can get the name of the Activity by calling activeActivity.getClass().getSimpleName()您可以通过调用activeActivity.getClass().getSimpleName()来获取 Activity 的名称

I could not find a solution that our team would be happy with so we rolled our own.我找不到我们的团队会满意的解决方案,所以我们推出了自己的解决方案。 We use ActivityLifecycleCallbacks to keep track of current activity and then expose it through a service:我们使用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
            }
        });
    }
}

Then configure your DI container to return instance of MyApplication for ContextProvider , eg然后配置您的 DI 容器以返回MyApplicationContextProvider实例,例如

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

(Note that implementation of getCurrent() is omitted from the code above. It's just a static variable that's set from the application constructor) (请注意,上面的代码中省略了getCurrent()的实现。它只是从应用程序构造函数中设置的静态变量)

Use ActivityManager使用ActivityManager

If you only want to know the application containing the current activity, you can do so using ActivityManager .如果您只想知道包含当前活动的应用程序,您可以使用ActivityManager来实现。 The technique you can use depends on the version of Android:您可以使用的技术取决于 Android 的版本:

Benefits好处

  • Should work in all Android versions to-date.应该适用于迄今为止的所有 Android 版本。

Disadvantages缺点

  • Doesn't work in Android 5.1+ (it only returns your own app)在 Android 5.1+ 中不起作用(它只返回您自己的应用程序)
  • The documentation for these APIs says they're only intended for debugging and management user interfaces. 这些 API 的文档说它们仅用于调试和管理用户界面。
  • If you want real-time updates, you need to use polling.如果要实时更新,则需要使用轮询。
  • Relies on a hidden API: ActivityManager.RunningAppProcessInfo.processState依赖于一个隐藏的 API: ActivityManager.RunningAppProcessInfo.processState
  • This implementation doesn't pick up the app switcher activity.此实现不会获取应用程序切换器活动。

Example (based on KNaito's code )示例(基于KNaito 的代码

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

Add the GET_TASKS permission to AndroidManifest.xml :GET_TASKS权限添加到AndroidManifest.xml

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

I'm using this for my tests.我正在使用它进行测试。 It's API > 19, and only for activities of your app, though.它的 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");
}

Here's what I suggest and what has worked for me.这是我的建议以及对我有用的方法。 In your application class, implement an Application.ActivityLifeCycleCallbacks listener and set a variable in your application class.在您的应用程序类中,实现一个Application.ActivityLifeCycleCallbacks侦听器并在您的应用程序类中设置一个变量。 Then query the variable as needed.然后根据需要查询变量。

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

    fun onCreate() {
        registerActivityLifecycleCallbacks(this)
    }

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

Use this code for API 21 or above.将此代码用于 API 21 或更高版本。 This works and gives better result compared to the other answers, it detects perfectly the foreground process.与其他答案相比,这有效并提供了更好的结果,它完美地检测了前台进程。

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

I like the idea of the Application.ActivityLifecycleCallbacks .我喜欢Application.ActivityLifecycleCallbacks的想法。 But it can be a bit tricky getting the top activity但获得顶级活动可能有点棘手

  • Your app might have multiple activities您的应用可能有多个活动
  • Some activities might get destroyed一些活动可能会被破坏
  • Some activities might go to background一些活动可能会进入后台

To handle all those cases, you need to track each activity life cycle.要处理所有这些情况,您需要跟踪每个活动生命周期。 This is exactly what I did with my below solution.这正是我对以下解决方案所做的。

It all consolidates into a single call of getTopForegroundActivity() that returns the top foreground activity or null if no activities in the stack or non of them are in the foreground.所有这些都合并为一个getTopForegroundActivity()调用,该调用返回顶部前台活动,如果堆栈中没有活动或它们都不在前台,则返回null

Usage用法

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

Source资源

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

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

Here is my answer that works just fine...这是我的答案,效果很好......

You should be able to get current Activity in this way... If you structure your app with a few Activities with many fragments and you want to keep track of what is your current Activity, it would take a lot of work though.您应该能够以这种方式获取当前的 Activity... 如果您使用一些带有许多片段的 Activity 来构建您的应用程序,并且您想跟踪您当前的 Activity 是什么,那么这将需要大量的工作。 My senario was I do have one Activity with multiple Fragments.我的情景是我确实有一个包含多个片段的活动。 So I can keep track of Current Activity through Application Object, which can store all of the current state of Global variables.所以我可以通过Application Object来跟踪Current Activity,它可以存储全局变量的所有当前状态。

Here is a way.这是一种方法。 When you start your Activity, you store that Activity by Application.setCurrentActivity(getIntent());当您启动 Activity 时,您通过 Application.setCurrentActivity(getIntent()); 存储该 Activity; This Application will store it.此应用程序将存储它。 On your service class, you can simply do like Intent currentIntent = Application.getCurrentActivity();在您的服务类上,您可以简单地执行 Intent currentIntent = Application.getCurrentActivity(); getApplication().startActivity(currentIntent); getApplication().startActivity(currentIntent);

Just recently found out about this.最近才知道这件事。 With apis as:使用 api 为:

  • minSdkVersion 19 minSdkVersion 19
  • targetSdkVersion 26 targetSdkVersion 26

    ActivityManager.getCurrentActivity(context) ActivityManager.getCurrentActivity(上下文)

Hope this is of any use.希望这有任何用处。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM