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

180

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

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

Джордж
источник
может быть, это может дать вам идею stackoverflow.com/a/28423385/185022
AZ_

Ответы:

87

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

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

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

  1. Отправить трансляцию Intentна мероприятие - вот пример проекта, демонстрирующего эту схему
  2. Сделайте, чтобы действие поставило PendingIntent(например, через createPendingResult()), что служба вызывает
  3. Пусть действие зарегистрирует объект обратного вызова или прослушивателя с помощью службы через bindService(), а служба вызовет метод события для этого объекта обратного вызова / прослушивателя.
  4. Отправьте упорядоченную трансляцию Intentв мероприятие с BroadcastReceiverрезервным копированием с низким приоритетом (для повышения, Notificationесли действие не отображается на экране) - вот сообщение в блоге, в котором больше об этом шаблоне
CommonsWare
источник
3
Спасибо, но поскольку у меня есть множество действий, и я не хочу обновлять их все, я искал способ для Android получить приоритетную активность. Как вы говорите, я не должен быть в состоянии сделать это. Значит, я должен обойти это.
Джордж
1
@ Джордж: То, что ты хочешь, не помогло бы тебе, так или иначе. Как вы указываете, у вас есть «добыча деятельности». Следовательно, это все отдельные классы. Следовательно, ваш сервис ничего не сможет с ними сделать без массивного блока instanceofпроверок if или рефакторинга действий для совместного использования общего суперкласса или интерфейса. И если вы собираетесь провести рефакторинг действий, вы можете также сделать это таким образом, чтобы лучше соответствовать структуре и охватывать больше сценариев, например, ни одно из ваших действий не было активным. # 4, вероятно, наименее трудоемкий и самый гибкий.
CommonsWare
2
Спасибо, но у меня есть лучшее решение. Все мои действия расширяют настроенный класс BaseActivity. Я установил ContextRegister, который регистрирует активность как текущую, когда она находится на переднем плане, с 3 литральными линиями в моем классе BaseActivity. Тем не менее, спасибо за поддержку.
Джордж
3
@ Джордж: Остерегайтесь утечек памяти. Изменяемые члены-статические данные следует избегать в Java везде, где это возможно.
CommonsWare
Я плотно инкапсулировал объект, хотя я действительно буду помнить об этом. Спасибо.
Джордж
139

Обновление : это больше не работает с действиями других приложений с Android 5.0


Вот хороший способ сделать это с помощью менеджера активности. Вы в основном получаете рабочие задачи от менеджера активности. Сначала он всегда вернет текущую активную задачу. Оттуда вы можете получить 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"/>
Нельсон Рамирес
источник
3
Ваш ответ на 100% правильный. Спасибо 4. Лучше, если вы оставите здесь тот же ответ
Шахзад Имам
1
@ArtOfWarfare не проверено, но да, количество имеющихся у вас фрагментов не имеет значения, поскольку они всегда размещаются в одном действии.
Нельсон Рамирес
2
Если мне нужна текущая активность чаще, я должен опрашивать очень часто, верно? Есть ли способ получить событие обратного вызова при изменении активности на переднем плане?
nizam.sp
43
Точно так же, как все знают, в документации говорится о методе getRunningTasks (). -------- Примечание: этот метод предназначен только для отладки и представления пользовательских интерфейсов управления задачами. Это никогда не должно использоваться для базовой логики в приложении, например, для выбора между различными поведениями на основе информации, найденной здесь. Такое использование не поддерживается и, скорее всего, в будущем прекратится. Например, если несколько приложений могут быть запущены одновременно, предположения о значении приведенных здесь данных для целей управления будут неверными. ------------
Райан
30
Полуфиг на api 21As 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.
шерпя
116

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

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


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

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

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

Недостатки

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

Обратите внимание, что пользователь не сможет нажать кнопку ОК при попытке включить службу специальных возможностей, если приложение поместило на экран наложение, например Velis Auto Brightness или Lux.

Сэм
источник
1
@lovemint, что я имел в виду: Я указан пример settingsActivityиз your.app.ServiceSettingsActivity, так что вы должны изменить это на вашу собственную деятельность настройки для службы доступности. Я думаю, что настройка параметров в любом случае необязательна, поэтому я удалил эту часть из своего ответа, чтобы упростить ее.
Сэм
1
Большое спасибо, только последний вопрос. Если мне нужно разобраться с API 8 до текущего, я должен сделать эту работу в коде вместо использования XML?
paolo2988
1
@lovemint, это верно. Я просто обновил пример кода, чтобы сделать это.
Сэм
1
@ Сам я могу подтвердить, что этот Сервис работает отлично. Одно странное поведение, я использовал намерение в основной деятельности, чтобы включить Accessibility Service. После этой первой активации служба активируется, но onAccessibilityEventне получает никаких событий, но если я отключаю и снова включаю Accessibility Service, то служба повторно активируется и onAccessibilityEventначинает работать.
paolo2988
2
Спасибо за ваш код. Я создал это приложение для вас :)
хранилище
20

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

  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()

Вит Верес
источник
1
Я получаю исключение Nullpointer по адресу activeActivity.getClass (). GetSimpleName (). Не могли бы вы помочь
новичок
Как вы можете видеть из ActivityLifecycleCallbacks - если активность не видна, application.getActiveActivity () возвращает ноль. Это означает, что никакой активности не видно. Вы должны проверить это в вашем сервисе или везде, где вы используете это.
Вит Верес
хорошо .. но в случае возобновления активности, это не должно возвращать ноль ?? Моя деятельность началась, и в ее возобновлении я получил так
новичок
Трудно догадаться, попробуйте поставить точки останова onActivityResumed(), onActivityPaused()и getActiveActivity()посмотреть , как это Callet, если когда - нибудь так.
Вит Верес
@beginner нужно проверить, activityи activeActivityтакие же , перед назначением activeActivityс nullтем чтобы ошибки во избежание из - за приобщены порядок вызова методов жизненного цикла различных видов деятельности.
Рахул Тивари
16

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

public class ApplicationModule extends AbstractModule {    
    @Provides
    ContextProvider provideMainActivity() {
        return MyApplication.getCurrent();
    }
}

(Обратите внимание, что реализация getCurrent()кода в приведенном выше коде опущена. Это просто статическая переменная, установленная в конструкторе приложения)

Muxa
источник
4
необходимо проверить, activityи currentActivityтакие же , перед назначением currentActivityс nullтем чтобы ошибки во избежание из - за приобщены порядок вызова методов жизненного цикла различных видов деятельности.
Рахул Тивари
14

использование 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);
    }

}

манифест

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

<!--suppress DeprecatedClassUsageInspection -->
<uses-permission android:name="android.permission.GET_TASKS" />
Сэм
источник
10
это не работает на Android M больше. getRunningAppProcesses () теперь возвращает только пакет вашего приложения
Lior Iluz
1
Также не работает для API уровня 22 (Android 5.1). Проверено на сборке LPB23
sumitb.mdi
9

Я использую это для моих тестов. Это 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");
}
espinchi
источник
Замените ArrayMap на Map, и он будет работать и на 4.3. Не проверял более ранние версии Android.
Оливер Джонас
2

Используйте этот код для 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();
        }
    }
Маниш Годхани
источник
Не могли бы вы уточнить, насколько результаты этого метода лучше, чем другие ответы?
Сэм
Работает ли это также с меню уведомлений. Я сталкиваюсь с проблемой, когда я получаю звонок или сообщение. UsageStatsManager начинает давать мне имя пакета systemUi вместо моего приложения.
kukroid
@kukroid, опубликуйте отдельный вопрос и укажите свой исходный код, информацию об устройстве и используемые приложения для обмена сообщениями и телефона.
Сэм
0

Вот мой ответ, который работает просто отлично ...

Вы должны быть в состоянии получить текущую активность таким образом ... Если вы структурируете свое приложение с несколькими действиями с большим количеством фрагментов, и вы хотите отслеживать текущую активность, это займет много работы. Моим сенарио было то, что у меня есть одно задание с несколькими фрагментами. Поэтому я могу отслеживать текущую активность через объект приложения, который может хранить все текущее состояние глобальных переменных.

Вот способ. Когда вы запускаете свою активность, вы сохраняете эту активность с помощью Application.setCurrentActivity (getIntent ()); Это приложение будет хранить его. В своем классе обслуживания вы можете просто сделать так: Intent currentIntent = Application.getCurrentActivity (); . GetApplication () startActivity (currentIntent);

John3328
источник
2
Это предполагает, что служба работает в том же процессе, что и Activity, верно?
helleye
0

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

Влад Владеску
источник
1
Мы ищем
оригинальный
-3

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

  • minSdkVersion 19
  • targetSdkVersion 26

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

Надеюсь, что это полезно.

user3709410
источник
1
Если бы мои надежды оправдались. Возможно, когда на него ответили, он существовал, но на данный момент getCurrentActivity()в ActivityManagerклассе нет функции ( developer.android.com/reference/android/app/ActivityManager ). Забавно , что эта функция не существует: ActivityManager.isUserAMonkey().
ByteSlinger
1
Он обнаруживает, используете ли вы агент тестирования обезьян для проверки вашей активности. Хотя смешное имя, это может быть весьма полезным для тестирования (и делать другие люди смеются, obvs.) Developer.android.com/studio/test/monkeyrunner
Кен Кори