有没有一种原生的Android方法可以从服务中获取对当前正在运行的Activity的引用?
我有一个在后台运行的服务,当事件发生(在服务中)时,我想更新我的当前活动。有没有一种简单的方法可以做到这一点(就像我上面提出的那样)?
有没有一种原生的Android方法可以从服务中获取对当前正在运行的Activity的引用?
我有一个在后台运行的服务,当事件发生(在服务中)时,我想更新我的当前活动。有没有一种简单的方法可以做到这一点(就像我上面提出的那样)?
更新:从Android 5.0开始,这个不再适用于其他应用程序的活动。
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商店中删除那些将辅助功能服务用于非辅助目的的应用程序。但是,据报道,这一政策正在重新考虑。
AccessibilityService
AccessibilityService
检测当前活动窗口。onAccessibilityEvent
回调中,通过检查事件类型为 TYPE_WINDOW_STATE_CHANGED
来确定当前窗口何时发生变化。PackageManager.getActivityInfo()
检查窗口是否为活动窗口。GET_TASKS
权限。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() {}
}
将此合并到您的清单中:
<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"/>
每个应用的用户都需要明确启用辅助功能服务
,以便使用它。有关如何执行此操作,请参见此 StackOverflow 答案。
请注意,如果应用程序在屏幕上放置了叠加层(例如 Velis Auto Brightness 或 Lux),则用户在尝试启用辅助功能服务时将无法按下“确定”按钮。
settingsActivity
,它是your.app.ServiceSettingsActivity
,所以你应该将其更改为你自己的辅助功能服务设置活动。我认为设置活动是可选的,所以我从我的答案中删除了这部分,使它更简单。 - SamonAccessibilityEvent
没有接收到任何事件,但是如果我禁用并再次启用辅助功能服务,则服务会重新激活,并且 onAccessibilityEvent
开始工作。 - paolo2988有没有一种原生的Android方法可以从服务中获取对当前正在运行的Activity的引用?
您可能不拥有“当前正在运行的Activity”。
我有一个在后台运行的服务,并且当发生事件(在服务中)时,我想更新我的当前Activity。有没有简单的方法可以做到这一点(就像我上面提到的那样)?
instanceof
检查或重构活动以共享公共超类或接口,您的服务将无法处理它们。如果您要重构这些活动,最好以更适合框架并涵盖更多场景的方式进行,例如没有任何活动处于活动状态。第4种方法可能是最少工作和最灵活的。 - CommonsWare可以通过以下方式完成:
实现自己的应用程序类,注册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;
}
}
在你的服务中调用getApplication()
并将其转换为你的应用程序类名称(在这种情况下为App)。然后,您可以调用app.getActiveActivity()
- 这将为您提供当前可见的活动(或者当没有活动可见时返回null)。您可以通过调用activeActivity.getClass().getSimpleName()
来获取活动的名称。
onActivityResumed()
、onActivityPaused()
和getActiveActivity()
处设置断点,以查看如何调用它们(如果有的话)。 - Vit VeresactiveActivity
赋值为null
之前,必须检查activity
和activeActivity
是否相同,以避免由于各种活动的生命周期方法调用顺序混乱而导致错误。 - Rahul Tiwari我们团队找不到满意的解决方案,所以我们自己开发了一个。我们使用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 容器,使其返回 MyApplication
的实例,以供 ContextProvider
使用,例如:
public class ApplicationModule extends AbstractModule {
@Provides
ContextProvider provideMainActivity() {
return MyApplication.getCurrent();
}
}
getCurrent()
的实现。这只是一个从应用程序构造函数设置的静态变量。)currentActivity
赋值为null
之前,必须检查activity
和currentActivity
是否相同,以避免由于各种活动的生命周期方法调用顺序混杂而导致的错误。 - Rahul TiwariActivityManager
如果您只想知道包含当前活动的应用程序,可以使用ActivityManager
实现。可使用的技术取决于Android版本:
ActivityManager.getRunningTasks
(示例)ActivityManager.getRunningAppProcesses
(示例)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);
}
}
在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");
}
以下是我建议的做法,也是我验证有效的方式。在你的Application类中,实现一个Application.ActivityLifeCycleCallbacks
监听器,并在你的Application类中设置一个变量。然后根据需要查询该变量。
class YourApplication: Application.ActivityLifeCycleCallbacks {
var currentActivity: Activity? = null
fun onCreate() {
registerActivityLifecycleCallbacks(this)
}
...
override fun onActivityResumed(activity: Activity) {
currentActivity = activity
}
}
在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();
}
}
我喜欢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;
}
}
}