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



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



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

1701   11  

11 ответов:

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

вы не можете владеть "в настоящее время работает деятельность".

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

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

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

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

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

недостатки

  • каждый пользователь должен включить службу в настройках специальных возможностей Android.
  • служба всегда работает.
  • когда пользователь пытается включить AccessibilityService, они не могут нажать кнопку OK, если приложение разместило наложение на экране. Некоторые приложения, которые делают это, - это Velis Auto Brightness и Lux. Это может привести к путанице, потому что пользователь может не знать, почему они не удается нажать кнопку или как ее обойти.
  • The 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 как это сделать.

обратите внимание, что пользователь не сможет нажать кнопку 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.getActiveActivity() - это даст вам текущую видимую активность (или null, когда никакая активность не видна). Вы можете получить имя вызов activeActivity.getClass().getSimpleName()

использовать ActivityManager

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

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

  • должен работать во всех версиях 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" />

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

Я использую это для моих тестов. Это 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");
}

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

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

вот способ. Когда вы начинаете свою деятельность, вы сохраняете эту деятельность Приложение.setCurrentActivity(getIntent()); Это приложение будет хранить его. На вашем классе обслуживания, вы можете просто сделать как Намерение currentIntent = применение.getCurrentActivity(); getApplication().startActivity (currentIntent);

используйте этот код для 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();
        }
    }

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

совсем недавно узнал об этом. С API как:

  • minSdkVersion 19
  • targetSdkVersion 26

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

надеюсь, что это имеет какую-либо пользу.

Comments

    Ничего не найдено.