Android:我如何获得当前的前台活动(从服务)?

有一个本地的Android的方式来获取从服务当前正在运行的活动的引用?

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

有一个本地的Android的方式来获取从服务当前正在运行的活动的引用?

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

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

  1. 发送一个广播Intent的活动 – 这是一个示范项目展示这种模式
  2. 让活动提供服务调用的PendingIntent (例如,通过createPendingResult()
  3. 让活动通过bindService()向服务注册回调或侦听器对象,并让服务在该回调/侦听器对象上调用一个事件方法
  4. 发送一个有序的广播Intent的活动,与一个低优先级的BroadcastReceiver作为备份(如果活动不是在屏幕上提出一个Notification ) – 这是一个博客文章更多关于这种模式

这是使用活动管理器完成的一个好方法。 你基本上从活动管理器获得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"/> 

警告:Google Play违规

使用此技术可能会将您的应用从Google Play中移除 。 谷歌最近告诉我更新我的应用程序,因为它滥用无障碍服务。 我查了一下文件 ,确认是这样的:

辅助功能服务只能用于帮助残障人士使用Android设备和应用程序。


使用AccessibilityService

  • 您可以使用AccessibilityService检测当前活动的窗口。
  • onAccessibilityEvent回调中,检查TYPE_WINDOW_STATE_CHANGED 事件类型以确定当前窗口何时更改。
  • 通过调用PackageManager.getActivityInfo()检查窗口是否是一个活动。

优点

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

缺点

  • 每个用户都必须在Android的辅助功能设置中启用该服务。
  • 该服务始终运行。
  • 当用户尝试启用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才能使用它。 有关如何执行此操作,请参阅此StackOverflow答案 。

请注意,如果应用程序在屏幕上放置了叠加层,例如Velis Auto Brightness或Lux,则在尝试启用辅助功能服务时,用户将无法按OK按钮。

使用ActivityManager

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

  • Pre-Lollipop: ActivityManager.getRunningTasks ( 示例 )
  • 棒棒糖: ActivityManager.getRunningAppProcesses ( 示例 )

优点

  • 应该在所有Android版本工作到目前为止。

缺点

  • 在Android M中不起作用 (基于使用预发布模拟器的测试)
  • 这些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 new String[] { 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) /* 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 process.pkgList; } } } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException(e); } return new String[] { }; } private ActivityManager activityManager() { return (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); } } 

表现

AndroidManifest.xml添加GET_TASKS权限:

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

我正在使用这个测试。 它的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"); } 

它可以通过以下方式完成:

  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.getActiveActivity() – 这将给你一个当前可见的活动(或null,当没有活动是可见的)。 你可以通过调用activeActivity.getClass().getSimpleName()来获得Activity的名字activeActivity.getClass().getSimpleName()

我找不到一个解决方案,我们的团队会很高兴,所以我们推出了自己的。 我们使用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()实现,它只是一个从应用程序构造函数中设置的静态变量)

这是我的答案,工作得很好…

您应该可以通过这种方式获得当前的活动…如果您使用多个片段来组织您的应用程序,并且想要跟踪当前的活动,那么需要花费大量的工作。 我的senario是我有一个活动与多个片段。 所以我可以通过应用程序对象跟踪当前活动,它可以存储全局变量的所有当前状态。

这是一个方法。 当你开始你的活动,你通过Application.setCurrentActivity(getIntent())存储该活动; 这个应用程序将存储它。 在你的服务类,你可以简单地做像Intent currentIntent = Application.getCurrentActivity(); 。getApplication()startActivity(currentIntent);

将此代码用于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(); } } 

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