Android: Як я можу отримати поточну активність переднього плану (від послуги)?


180

Чи існує власний андроїд спосіб отримати посилання на поточну діяльність, що виконується в сервісі?

У мене служба працює на задньому плані, і я хотів би оновити свою поточну активність, коли відбувається подія (у службі). Чи є простий спосіб зробити це (як той, який я запропонував вище)?


може бути , це може дати Вам ідею stackoverflow.com/a/28423385/185022
AZ_

Відповіді:


87

Чи існує власний андроїд спосіб отримати посилання на поточну діяльність, що виконується в сервісі?

Ви можете не володіти "Активністю, що виконується в даний момент".

У мене служба працює на задньому плані, і я хотів би оновити свою поточну активність, коли відбувається подія (у службі). Чи є простий спосіб зробити це (як той, який я запропонував вище)?

  1. Надішліть трансляцію Intentдіяльності - ось зразок проекту, що демонструє цю закономірність
  2. Попросіть активність PendingIntent(наприклад, через createPendingResult()), на яку посилається служба
  3. Запропонуйте сервісу зареєструвати зворотний дзвінок або об'єкт слухача за допомогою сервісу через bindService() , а службу викликати метод події для цього об'єкта зворотного виклику / слухача
  4. Надіслати впорядковану передачу Intentв діяльності, з низьким пріоритетом , BroadcastReceiverяк резервне копіювання (щоб підняти , Notificationякщо діяльність не на екрані) - ось блог з більше на цій моделі

3
Дякую, але оскільки у мене є цикл дій, і я не хочу все їх оновлювати, я шукав андроїд спосіб отримати передній план. Як ви кажете, я не міг би цього робити. Значить, я повинен обійти це.
Джордж

1
@George: Те, що ти хочеш, не допоможе тобі. Як ви зазначаєте, у вас є «цикл діяльності». Отже, це все окремі класи. Отже, ваша послуга не може зробити з ними без масивного if-блоку instanceofперевірок або рефакторингу дій для спільного використання загального суперкласу або інтерфейсу. І якщо ви збираєтеся переробляти заходи, ви можете також зробити це таким чином, що краще відповідає рамці та охоплює більше сценаріїв, наприклад, жодна з ваших заходів не є активною. №4 - це, мабуть, найменш робота та найбільш гнучка.
CommonsWare

2
Дякую, але у мене є краще рішення. Всі мої дії поширюють персоналізований клас BaseActivity. Я встановив ContextRegister, який реєструє активність як поточну щоразу, коли вона стоїть на передньому плані, з літерними трьома рядками в моєму класі BaseActivity. Все-таки дякую за підтримку.
Джордж

3
@George: стежте за витоками пам'яті. Там, де це можливо, слід уникати змінних статичних даних даних на 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
Ваша відповідь стовідсотково правильна. Спасибі 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.
шерпя

115

Попередження: порушення 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
@Sam Я можу підтвердити, що ця Служба працює чудово. Одне дивне поведінку, я використовував наміри в основній діяльності, щоб включити Службу доступності. Після цієї першої активації послуга активується, але onAccessibilityEventне отримує жодної події, але якщо я відключу і знову включаю Службу доступності, послуга знову активується і 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.getActiveActivity()- це дасть вам поточну видиму активність (або нульову, коли жодна активність не видно). Ви можете отримати ім'я Діяльності, зателефонувавшиactiveActivity.getClass().getSimpleName()


1
Я отримую Nullpointer exeception на 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 для повернення екземпляра MyApplication для ContextProvider, наприклад ,

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

(Зауважте, що впровадження getCurrent()пропущено з коду вище. Це просто статична змінна, встановлена ​​з конструктора додатків)


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. getRunningAppPropts () тепер повертає лише пакет вашої програми
Lior Iluz

1
Також не працює для API рівня 22 (Android 5.1). Тестований на Build 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");
}

Замініть 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);


2
Це передбачає, що служба працює в тому ж процесі, що і Активність, правда?
helleye

0

Я не знаю, чи це дурна відповідь, але я вирішив цю проблему, зберігаючи прапор у спільних налаштуваннях кожного разу, коли я входив уCreate () будь-якої активності, тоді я використовував значення з накреслених уподобань, щоб з’ясувати, що це за передній план діяльності.


1
Ми шукаємо рідний спосіб зробити це
IgniteCoders

-3

Нещодавно про це дізнався. З apis як:

  • minSdkVersion 19
  • targetSdkVersion 26

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

Сподіваюся, це корисно.


1
Були мої сподівання. Можливо, коли на це відповіли, воно існувало, але поки що getCurrentActivity()функція в ActivityManagerкласі не існує ( developer.android.com/reference/android/app/ActivityManager ). Забавно , що ця функція не існує: ActivityManager.isUserAMonkey().
ByteSlinger

1
Це визначає, якщо ви використовуєте агент для тестування мавп для перевірки своєї активності. Хоча смішне ім’я, воно може бути дуже корисним для тестування (і змусити інших людей сміятися, замовник) developer.android.com/studio/test/monkeyrunner
Кен Корі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.