Android: Как я могу получить текущую активность переднего плана (из службы)?
Есть ли собственный способ Android получить ссылку на текущее действие в службе?
У меня есть служба, работающая в фоновом режиме, и я хотел бы обновить свою текущую активность, когда происходит событие (в службе). Есть ли простой способ сделать это (как тот, который я предложил выше)?
13 ответов
Есть ли собственный способ Android получить ссылку на текущее действие в службе?
Вы не можете владеть "действующим в данный момент действием".
У меня есть служба, работающая в фоновом режиме, и я хотел бы обновить свою текущую активность, когда происходит событие (в службе). Есть ли простой способ сделать это (как тот, который я предложил выше)?
- Отправить трансляцию
Intent
к действию - вот пример проекта, демонстрирующего эту модель - Иметь деятельность поставлять
PendingIntent
(например, черезcreatePendingResult()
) что служба вызывает - Пусть действие зарегистрирует объект обратного вызова или прослушивателя в сервисе через
bindService()
и чтобы служба вызывала метод события для этого объекта обратного вызова / прослушивателя - Отправить заказанный эфир
Intent
к деятельности, с низким приоритетомBroadcastReceiver
в качестве резервной копии (чтобы поднятьNotification
если действие не на экране) - вот сообщение в блоге с больше об этом образце
Вот хороший способ сделать это с помощью менеджера активности. Вы в основном получаете рабочие задачи от менеджера активности. Сначала он всегда вернет текущую активную задачу. Оттуда вы можете получить topActivity.
Есть простой способ получить список запущенных задач из сервиса ActivityManager. Вы можете запросить максимальное количество задач, выполняемых на телефоне, и по умолчанию текущая активная задача возвращается первой.
Как только вы это сделаете, вы можете получить объект ComponentName, запросив topActivity из вашего списка.
Вот пример.
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 Store, если они используют службы специальных возможностей в целях недоступности. Однако, по сообщениям, это пересматривается.
Используйте AccessibilityService
- Вы можете определить текущее активное окно, используя
AccessibilityService
, - в
onAccessibilityEvent
обратный звонок, проверьте наличиеTYPE_WINDOW_STATE_CHANGED
тип события, чтобы определить, когда текущее окно изменяется. - Проверьте, является ли окно активностью, вызвав
PackageManager.getActivityInfo()
,
Выгоды
- Протестировано и работает в Android 2.2 (API 8) через Android 7.1 (API 25).
- Не требует опроса.
- Не требует
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
для того, чтобы его можно было использовать. Посмотрите этот ответ Stackru о том, как это сделать.
Обратите внимание, что пользователь не сможет нажать кнопку OK при попытке включить службу специальных возможностей, если приложение поместило на экран наложение, например Velis Auto Brightness или Lux.
Это может быть сделано:
Реализуйте свой собственный класс приложения, зарегистрируйтесь для 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()
- это даст вам текущую видимую активность (или ноль, когда никакая активность не видна). Вы можете получить название деятельности, позвонив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-контейнер для возврата экземпляра MyApplication
за ContextProvider
например,
public class ApplicationModule extends AbstractModule {
@Provides
ContextProvider provideMainActivity() {
return MyApplication.getCurrent();
}
}
(Обратите внимание, что реализация getCurrent()
опущен в коде выше. Это просто статическая переменная, которая устанавливается из конструктора приложения)
использование ActivityManager
Если вы хотите узнать только приложение, содержащее текущую активность, вы можете сделать это, используя ActivityManager
, Техника, которую вы можете использовать, зависит от версии Android:
- Pre-леденец:
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);
}
}
манифест
Добавить 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");
}
Вот что я предлагаю и что сработало для меня. В своем классе приложения реализуйтеApplication.ActivityLifeCycleCallbacks
слушатель и установите переменную в своем классе приложения. Затем запросите переменную по мере необходимости.
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;
}
}
}
Я не знаю, является ли это глупым ответом, но решил эту проблему, сохраняя флаг в общих настройках каждый раз, когда я вводил onCreate() любого действия, затем я использовал значение из общих предпочтений, чтобы выяснить, что это за приоритетное действие.
Только недавно узнал об этом. С apis как:
- minSdkVersion 19
targetSdkVersion 26
ActivityManager.getCurrentActivity (контекст)
Надеюсь, что это полезно.
Вот мой ответ, который работает просто отлично...
Вы должны быть в состоянии получить текущее действие таким образом... Если вы структурируете свое приложение с несколькими действиями с большим количеством фрагментов, и вы хотите отслеживать то, что является вашим текущим действием, то потребуется много работы. Моим сенарио было то, что у меня есть одно задание с несколькими фрагментами. Поэтому я могу отслеживать текущую активность через объект приложения, который может хранить все текущее состояние глобальных переменных.
Вот способ. Когда вы запускаете свою активность, вы сохраняете эту активность с помощью Application.setCurrentActivity(getIntent()); Это приложение будет хранить его. В своем классе обслуживания вы можете просто сделать так: Intent currentIntent = Application.getCurrentActivity();. GetApplication() startActivity(currentIntent);