[英]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。
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"/>
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 .然而, 据报道,这正在重新考虑。
AccessibilityService
使用AccessibilityService
AccessibilityService
.您可以使用AccessibilityService
检测当前活动的窗口。onAccessibilityEvent
callback, check for the TYPE_WINDOW_STATE_CHANGED
event type to determine when the current window changes.在onAccessibilityEvent
回调中,检查TYPE_WINDOW_STATE_CHANGED
事件类型以确定当前窗口何时更改。PackageManager.getActivityInfo()
.通过调用PackageManager.getActivityInfo()
检查窗口是否为活动。GET_TASKS
permission.不需要GET_TASKS
权限。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.这可能会令人困惑,因为用户可能不知道他们为什么不能按下按钮或如何解决它。AccessibilityService
won't know the current activity until the first change of activity. 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() {}
}
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>
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"/>
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)?有没有一种简单的方法可以做到这一点(就像我上面建议的那样)?
Intent
to the activity -- here is a sample project demonstrating this pattern向活动发送广播Intent
- 这是一个演示此模式的示例项目PendingIntent
(eg, via createPendingResult()
) that the service invokes让活动提供服务调用的PendingIntent
(例如,通过createPendingResult()
)bindService()
, and have the service call an event method on that callback/listener object让活动通过bindService()
向服务注册回调或侦听器对象,并让服务调用该回调/侦听器对象上的事件方法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:可以通过以下方式完成:
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; } }
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 容器以返回MyApplication
的ContextProvider
实例,例如
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()
的实现。它只是从应用程序构造函数中设置的静态变量)
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 的版本:
ActivityManager.getRunningTasks
( example )棒棒糖前: ActivityManager.getRunningTasks
(示例)ActivityManager.getRunningAppProcesses
( example )棒棒糖: ActivityManager.getRunningAppProcesses
(示例)ActivityManager.RunningAppProcessInfo.processState
依赖于一个隐藏的 API: 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);
}
}
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但获得顶级活动可能有点棘手
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
。
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;
}
}
}
我不知道这是否是一个愚蠢的答案,但是通过在每次我输入任何活动的 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 为:
targetSdkVersion 26 targetSdkVersion 26
ActivityManager.getCurrentActivity(context) ActivityManager.getCurrentActivity(上下文)
Hope this is of any use.希望这有任何用处。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.