Answers:
是否有原生的android方法从服务获取对当前正在运行的Activity的引用?
您可能不拥有“当前正在运行的活动”。
我有一个在后台运行的服务,当事件(在服务中)发生时,我想更新当前的活动。有没有简单的方法可以做到这一点(就像我上面建议的那样)?
instanceof
检查或重构活动以共享一个公共的超类或接口,那么您的服务将无法与它们做任何事情。而且,如果您要重构活动,则最好以更适合框架并涵盖更多方案的方式进行,例如没有活动处于活动状态。#4可能是最少的工作,也是最灵活的。
更新:从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"/>
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.
Google威胁说,如果将出于无障碍目的使用无障碍服务的应用从Play商店中删除。但是,据报道正在对此进行重新考虑。
AccessibilityService
AccessibilityService
。onAccessibilityEvent
回调中,检查事件类型以确定当前窗口何时更改。TYPE_WINDOW_STATE_CHANGED
PackageManager.getActivityInfo()
。GET_TASKS
权限。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() {}
}
合并到清单中:
<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),则尝试启用辅助功能时,用户将无法按“确定”按钮。
settingsActivity
的your.app.ServiceSettingsActivity
,所以你应该改变,要你自己设置的活动为您的无障碍服务。我认为设置活动无论如何都是可选的,因此我从答案中删除了该部分以使其更简单。
onAccessibilityEvent
,但未收到任何事件,但是如果我禁用了此功能,然后再次启用了无障碍服务,则该服务将重新激活并 onAccessibilityEvent
开始工作。
可以通过以下方式完成:
实现您自己的应用程序类,注册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;
}
}
在您的服务呼叫中getApplication()
,并将其强制转换为您的应用程序类名称(在这种情况下为App)。比您可以致电app.getActiveActivity()
-会给您当前可见的活动(如果没有活动可见,则为null)。您可以通过致电获取活动名称activeActivity.getClass().getSimpleName()
onActivityResumed()
,onActivityPaused()
并getActiveActivity()
查看其调用方式(如果有)。
activity
和activeActivity
是分配前一样activeActivity
与null
以便由于各种活动的生命周期方法调用混杂,以免差错。
我找不到能使我们的团队满意的解决方案,所以我们自己动手了。我们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()
上面的代码省略了的实现。这只是从应用程序构造函数设置的静态变量)
activity
和currentActivity
是分配前一样currentActivity
与null
以便由于各种活动的生命周期方法调用混杂,以免差错。
ActivityManager
如果您只想知道包含当前活动的应用程序,则可以使用ActivityManager
。您可以使用的技术取决于Android的版本:
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);
}
}
将GET_TASKS
权限添加到AndroidManifest.xml
:
<!--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");
}
将此代码用于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 Object跟踪Current Activity,该对象可以存储Global变量的所有当前状态。
这是一种方法。启动活动时,可以通过Application.setCurrentActivity(getIntent());来存储该活动。该应用程序将存储它。在您的服务类上,您可以像Intent currentIntent = Application.getCurrentActivity();一样简单地进行操作。getApplication()。startActivity(currentIntent);
我不知道这是否是一个愚蠢的答案,但是通过在每次输入任何活动的onCreate()时在共享的首选项中存储一个标志来解决此问题,然后我使用舍弃的首选项中的值来查找它是前台的活动。
最近才发现这一点。使用api作为:
targetSdkVersion 26
ActivityManager.getCurrentActivity(上下文)
希望这是有用的。
getCurrentActivity()
,ActivityManager
该类中没有任何功能(developer.android.com/reference/android/app/ActivityManager)。有趣的是,该功能确实存在: ActivityManager.isUserAMonkey()
。