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

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

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

13 ответов

Решение

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

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

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

  1. Отправить трансляцию Intent к действию - вот пример проекта, демонстрирующего эту модель
  2. Иметь деятельность поставлять PendingIntent (например, через createPendingResult()) что служба вызывает
  3. Пусть действие зарегистрирует объект обратного вызова или прослушивателя в сервисе через bindService()и чтобы служба вызывала метод события для этого объекта обратного вызова / прослушивателя
  4. Отправить заказанный эфир 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.

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

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

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

Другие вопросы по тегам