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


180

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

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


也许这可以给您一个主意stackoverflow.com/a/28423385/185022
AZ_ 2015年

Answers:


87

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

您可能不拥有“当前正在运行的活动”。

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

  1. Intent向活动发送广播- 这是演示此模式的样本项目
  2. 让活动提供服务调用的PendingIntent(例如通过createPendingResult()
  3. 让活动通过向服务注册回调或侦听器对象bindService(),并让服务在该回调/监听器对象上调用事件方法
  4. Intent向活动发送有序广播,并以低优先级BroadcastReceiver作为备份(Notification如果活动不在屏幕上,则发出提示)- 这是一篇博客文章,其中包含有关此模式的更多信息

3
谢谢,但是由于我确实有很多活动,并且不想全部更新,所以我一直在寻找一种获取前景色活动的Android方法。如你所说,我不应该那样做。意味着我必须解决这个问题。
乔治,2010年

1
@乔治:无论如何,您想要的东西都帮不了您。正如您指出的那样,您有“一系列活动”。因此,这些都是单独的类。因此,如果没有大量的if块instanceof检查或重构活动以共享一个公共的超类或接口,那么您的服务将无法与它们做任何事情。而且,如果您要重构活动,则最好以更适合框架并涵盖更多方案的方式进行,例如没有活动处于活动状态。#4可能是最少的工作,也是最灵活的。
CommonsWare,2010年

2
谢谢,但是我有一个更好的解决方案。我的所有活动都扩展了自定义的BaseActivity类。我已经设置了一个ContextRegister,可以在活动处于前台时将其注册为当前活动,在BaseActivity类中有3行内容。不过,感谢您的支持。
乔治,2010年

3
@乔治:当心内存泄漏。在Java中尽可能避免使用可变的静态数据成员。
CommonsWare 2010年

尽管确实会牢记这一点,但我已将对象紧密封装了。谢谢。
乔治

139

更新:从Android 5.0开始,此功能不再其他应用程序的活动一起使用


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

这里的例子

有一种简单的方法可以从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"/>

3
您的答案是100%正确。感谢4。您最好在这里发布相同的答案
Shahzad Imam

1
@ArtOfWarfare尚未验证,但是是的,您拥有的片段数量无关紧要,因为它们始终由单个活动托管。
尼尔森·拉米雷斯

2
如果我需要更频繁地进行当前活动,则必须非常频繁地轮询,对吗?每当前台活动发生变化时,是否有办法获取回调事件?
nizam.sp

43
大家都知道,文档对此进行了说明,涉及到getRunningTasks()方法。--------注意:此方法仅用于调试和显示任务管理用户界面。永远不要将其用于应用程序的核心逻辑,例如,根据此处找到的信息确定不同的行为。不支持此类用途,将来可能会中断。例如,如果多个应用程序可以同时主动运行,则出于控制流程的目的,此处关于数据含义的假设将是错误的。------------
瑞安(Ryan)

30
在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.
半假

114

警告:违反Google Play

Google威胁说,如果将出于无障碍目的使用无障碍服务的应用从Play商店中删除。但是,据报道正在对此进行重新考虑


使用 AccessibilityService

好处

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

缺点

  • 每个用户都必须在Android的辅助功能设置中启用该服务。
  • 这不是100%可靠的。有时事件是乱序的。
  • 该服务始终在运行。
  • 当用户尝试启用时AccessibilityService,如果应用在屏幕上放置了叠加层,则他们无法按OK(确定)按钮。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才能使用它。请参阅此StackOverflow答案以了解如何执行此操作。

请注意,如果某个应用在屏幕上放置了叠加层(例如Velis Auto Brightness或Lux),则尝试启用辅助功能时,用户将无法按“确定”按钮。


1
@lovemint,我的意思是:我指定的一个例子settingsActivityyour.app.ServiceSettingsActivity,所以你应该改变,要你自己设置的活动为您的无障碍服务。我认为设置活动无论如何都是可选的,因此我从答案中删除了该部分以使其更简单。
2015年

1
非常感谢,只有最后一个问题。如果我需要从API 8过渡到当前版本,我应该在代码中完成这项工作,而不是使用XML?
paolo2988 2015年

1
@lovemint,是的。我刚刚更新了示例代码来执行此操作。
2015年

1
@Sam我可以确认此服务是否正常运行。一种奇怪的行为,我在主要活动中使用了一种意图来启用辅助功能。首次激活该服务后,它已激活onAccessibilityEvent,但未收到任何事件,但是如果我禁用了此功能,然后再次启用了无障碍服务,则该服务将重新激活并 onAccessibilityEvent开始工作。
paolo2988 2015年

2
谢谢您的验证码。我为您创建了这个应用程序:)
Vault

20

可以通过以下方式完成:

  1. 实现您自己的应用程序类,注册ActivityLifecycleCallbacks-通过这种方式,您可以查看我们的应用程序的运行情况。在每一次继续执行时,回调都会在屏幕上分配当前可见的活动,并在暂停时将其删除。它使用registerActivityLifecycleCallbacks()API 14中添加的方法。

    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()-会给您当前可见的活动(如果没有活动可见,则为null)。您可以通过致电获取活动名称activeActivity.getClass().getSimpleName()


1
我在activeActivity.getClass()。getSimpleName()处收到Nullpointer误解。您能帮忙吗?
初学者

从ActivityLifecycleCallbacks中可以看到-如果活动不可见,则application.getActiveActivity()返回null。这意味着没有活动可见。您需要在服务中或其他任何地方检查此内容。
Vit Veres

好的..但如果活动恢复,则不应返回null?我的活动已经开始,在履历表中我也是如此
初学者

很难猜测,尝试在处设置断点onActivityResumed()onActivityPaused()getActiveActivity()查看其调用方式(如果有)。
Vit Veres

@beginner一个必须检查 activityactiveActivity是分配前一样activeActivitynull以便由于各种活动的生命周期方法调用混杂,以免差错。
拉胡尔·蒂瓦里

16

我找不到能使我们的团队满意的解决方案,所以我们自己动手了。我们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容器的回报情况MyApplicationContextProvider,如

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

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


4
一个必须检查 activitycurrentActivity是分配前一样currentActivitynull以便由于各种活动的生命周期方法调用混杂,以免差错。
拉胡尔·蒂瓦里

14

ActivityManager

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

好处

  • 应该可以在所有Android版本中使用。

缺点

  • 在Android 5.1以上版本中不起作用(仅返回您自己的应用)
  • 这些API的文档说,它们仅用于调试和管理用户界面。
  • 如果要实时更新,则需要使用轮询。
  • 依靠隐藏的API: ActivityManager.RunningAppProcessInfo.processState
  • 此实现不会处理应用程序切换器活动。

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

}

表现

GET_TASKS权限添加到AndroidManifest.xml

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

10
这在Android M上不再起作用。getRunningAppProcesses()现在仅返回您应用程序的包
Lior Iluz,2015年

1
同样不适用于API Level 22(Android 5.1)。在Build LPB23上测试
sumitb.mdi

9

我正在使用它进行测试。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版本。
奥利弗·乔纳斯

2

将此代码用于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

@kukroid,发布一个单独的问题,并包括您的源代码,设备信息以及您正在使用的消息和电话应用程序。
山姆

0

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

您应该能够以这种方式获取当前的活动...如果您使用一些带有很多片段的活动来构建应用程序,并且想要跟踪当前的活动是什么,那么它将需要大量的工作。我的观点是我确实有一个带有多个片段的活动。因此,我可以通过Application Object跟踪Current Activity,该对象可以存储Global变量的所有当前状态。

这是一种方法。启动活动时,可以通过Application.setCurrentActivity(getIntent());来存储该活动。该应用程序将存储它。在您的服务类上,您可以像Intent currentIntent = Application.getCurrentActivity();一样简单地进行操作。getApplication()。startActivity(currentIntent);


2
假设服务与活动在同一进程中运行,对吗?
helleye 2015年

0

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


1
我们正在寻找一种做到这一点的本地方法
IgniteCoders

-3

最近才发现这一点。使用api作为:

  • minSdkVersion 19
  • targetSdkVersion 26

    ActivityManager.getCurrentActivity(上下文)

希望这是有用的。


1
我有希望。也许当回答这个问题时它就存在了,但是到目前为止getCurrentActivity()ActivityManager该类中没有任何功能(developer.android.com/reference/android/app/ActivityManager)。有趣的是,该功能确实存在: ActivityManager.isUserAMonkey()
ByteSlinger

1
这可以检测您是否正在使用猴子测试代理来测试您的活动。虽然一个有趣的名字,它可能是用于测试非常有用(而使他人笑,obvs。) developer.android.com/studio/test/monkeyrunner
肯·科里
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.