Android: Как я могу получить текущую активность переднего плана (из службы)? - PullRequest
168 голосов
/ 06 октября 2010

Есть ли собственный способ Android получить ссылку на текущую активность из службы?

У меня есть служба, работающая в фоновом режиме, и я хочу обновить текущую активность, когда происходит событие(в сервисе).Есть ли простой способ сделать это (как тот, который я предложил выше)?

Ответы [ 11 ]

137 голосов
/ 21 января 2011

Вот хороший способ сделать это с помощью диспетчера активности.Вы в основном получаете рабочие задачи от менеджера активности.Сначала он всегда вернет текущую активную задачу.Оттуда вы можете получить 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"/>
102 голосов
/ 25 декабря 2014

Предупреждение: нарушение Google Play

Google пригрозил удалить приложения из Play Store, если они используют службы специальных возможностей в целях недоступности. Однако это, как сообщается, пересматривается .


Используйте AccessibilityService

Преимущества

  • Протестировано и работает в Android 2.2 (API 8) через Android 7.1 (API 25).
  • Не требует опроса.
  • Не требует разрешения GET_TASKS.

Недостатки

  • Каждый пользователь должен включить службу в настройках доступности Android.
  • Это не на 100% надежно. Иногда события происходят не по порядку.
  • Служба всегда работает.
  • Когда пользователь пытается включить 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() {} } 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.

82 голосов
/ 06 октября 2010

Есть ли собственный способ для Android получить ссылку на текущее действие в службе?

Вы не можете владеть "текущим действием".

У меня есть служба, работающая в фоновом режиме, и я хотел бы обновить свою текущую активность, когда происходит событие (в службе). Есть ли простой способ сделать это (как тот, который я предложил выше)?

  1. Отправить трансляцию Intent на мероприятие - вот пример проекта , демонстрирующий этот паттерн
  2. Имейте в распоряжении операции PendingIntent (например, через createPendingResult()), которую служба вызывает
  3. Пусть действие зарегистрирует объект обратного вызова или прослушивателя в службе через bindService(), а служба вызовет метод события для этого объекта обратного вызова / прослушивателя
  4. Отправить заказанную трансляцию Intent в мероприятие с низким приоритетом BroadcastReceiver в качестве резервной копии (чтобы поднять Notification, если действие не отображается на экране) - здесь - запись в блоге больше с этим шаблоном
18 голосов
/ 27 ноября 2016

Это может быть сделано:

  1. Реализуйте свой собственный класс приложения, зарегистрируйтесь для 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;
    }
    
    }
    
  2. В вашем сервисе позвоните getApplication() и приведите его к имени класса вашего приложения (в данном случае App) Чем вы можете позвонить app.getActiveActivity() - это даст вам текущую видимую активность (или ноль, когда никакая активность не видна). Вы можете получить название занятия, позвонив по номеру activeActivity.getClass().getSimpleName()

13 голосов
/ 29 июля 2016

Я не мог найти решение, которым наша команда была бы довольна, поэтому мы катались самостоятельно.Мы используем 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() не включена в приведенный выше код. Это просто статическая переменная, заданная в конструкторе приложения)

12 голосов
/ 20 апреля 2015

Использование ActivityManager

Если вы хотите знать только приложение , содержащее текущую активность, вы можете сделать это, используя ActivityManager. Техника, которую вы можете использовать, зависит от версии Android:

Преимущества

  • Должно работать во всех версиях Android на сегодняшний день.

Недостатки

  • Не работает в Android 5.1 + (возвращает только ваше собственное приложение)
  • В документации по этим 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 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);
    }

}

Manifest

Добавьте разрешение GET_TASKS к AndroidManifest.xml:

<!--suppress DeprecatedClassUsageInspection -->
<uses-permission android:name="android.permission.GET_TASKS" />
9 голосов
/ 04 октября 2015

Я использую это для своих тестов. Это 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 голос
/ 30 июля 2016

Используйте этот код для 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();
        }
    }
0 голосов
/ 14 марта 2018

Только недавно узнал об этом.С apis как:

  • minSdkVersion 19
  • targetSdkVersion 26

    ActivityManager.getCurrentActivity (контекст)

Надеюсь, это пригодится.

0 голосов
/ 08 марта 2017

Я не знаю, является ли это глупым ответом, но решил эту проблему, сохраняя флаг в общих настройках каждый раз, когда я вводил onCreate () любого действия, затем я использовал значение из общих предпочтений, чтобы выяснить, что это такое. передний план деятельности.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...