Определение текущего приложения переднего плана из фоновой задачи или службы

112

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

Итак, мои вопросы:

  1. Как мне запускать приложение в фоновом режиме.

  2. Как мое фоновое приложение может узнать, какое приложение в данный момент запущено на переднем плане.

Будем очень благодарны за отзывы людей с опытом.

Дхайват
источник
Не думаю, что вы дали адекватное объяснение того, что пытаетесь сделать. Что пытается сделать ваше фоновое приложение? Каким образом он должен иметь возможность взаимодействовать с пользователем? Зачем вам нужно знать текущее приложение переднего плана? И т.д.
Чарльз Даффи
1
для обнаружения приложения переднего плана вы можете использовать github.com/ricvalerio/foregroundappchecker
rvalerio

Ответы:

104

Что касается «2. Как мое фоновое приложение может узнать, какое приложение в данный момент запущено на переднем плане».

НЕ используйте getRunningAppProcesses() метод, так как из моего опыта он возвращает всевозможный системный мусор, и вы получите несколько результатов, которые имели RunningAppProcessInfo.IMPORTANCE_FOREGROUND. Используйте getRunningTasks()вместо

Это код, который я использую в своем сервисе для идентификации текущего приложения переднего плана, это очень просто:

ActivityManager am = (ActivityManager) AppService.this.getSystemService(ACTIVITY_SERVICE);
// The first in the list of RunningTasks is always the foreground task.
RunningTaskInfo foregroundTaskInfo = am.getRunningTasks(1).get(0);

Вот и все, тогда вы можете легко получить доступ к деталям приложения / активности переднего плана:

String foregroundTaskPackageName = foregroundTaskInfo .topActivity.getPackageName();
PackageManager pm = AppService.this.getPackageManager();
PackageInfo foregroundAppPackageInfo = pm.getPackageInfo(foregroundTaskPackageName, 0);
String foregroundTaskAppName = foregroundAppPackageInfo.applicationInfo.loadLabel(pm).toString();

Это требует дополнительного разрешения в манифесте активности и отлично работает.

<uses-permission android:name="android.permission.GET_TASKS" />
Оливер Пирмэйн
источник
1
Это должно быть отмечено как правильный ответ. Требуемое разрешение: android.permission.GET_TASKS - единственный очень незначительный недостаток других методов, которые могут его избежать.
brandall 04
3
ИЗМЕНИТЬ ВЫШЕ: Примечание: этот метод предназначен только для отладки и представления пользовательских интерфейсов управления задачами. <- Только что заметил это в документе Java. Таким образом, этот метод может быть изменен без предварительного уведомления в будущем.
brandall 04
47
Примечание: getRunningTasks()устарело в API 21 (Lollipop) - developer.android.com/reference/android/app/…
dtyler
@dtyler, тогда ладно. Есть ли другой способ взамен? Хотя Google не хочет, чтобы сторонние приложения получали конфиденциальную информацию, у меня есть ключ платформы устройства. Есть ли другой способ сделать то же самое, если у меня есть системные привилегии?
Yeung
Есть способ использовать «API статистики использования», представленный в 21
KunalK
38

Мне пришлось нелегко найти правильное решение. приведенный ниже код является частью cyanogenmod7 (настройки планшета) и протестирован на android 2.3.3 / gingerbread.

методы:

  • getForegroundApp - возвращает приложение переднего плана.
  • getActivityForApp - возвращает активность найденного приложения.
  • isStillActive - определяет, является ли ранее найденное приложение активным.
  • isRunningService - вспомогательная функция для getForegroundApp

это, надеюсь, ответит на этот вопрос во всех отношениях (:

private RunningAppProcessInfo getForegroundApp() {
    RunningAppProcessInfo result=null, info=null;

    if(mActivityManager==null)
        mActivityManager = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
    List <RunningAppProcessInfo> l = mActivityManager.getRunningAppProcesses();
    Iterator <RunningAppProcessInfo> i = l.iterator();
    while(i.hasNext()){
        info = i.next();
        if(info.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND
                && !isRunningService(info.processName)){
            result=info;
            break;
        }
    }
    return result;
}

private ComponentName getActivityForApp(RunningAppProcessInfo target){
    ComponentName result=null;
    ActivityManager.RunningTaskInfo info;

    if(target==null)
        return null;

    if(mActivityManager==null)
        mActivityManager = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
    List <ActivityManager.RunningTaskInfo> l = mActivityManager.getRunningTasks(9999);
    Iterator <ActivityManager.RunningTaskInfo> i = l.iterator();

    while(i.hasNext()){
        info=i.next();
        if(info.baseActivity.getPackageName().equals(target.processName)){
            result=info.topActivity;
            break;
        }
    }

    return result;
}

private boolean isStillActive(RunningAppProcessInfo process, ComponentName activity)
{
    // activity can be null in cases, where one app starts another. for example, astro
    // starting rock player when a move file was clicked. we dont have an activity then,
    // but the package exits as soon as back is hit. so we can ignore the activity
    // in this case
    if(process==null)
        return false;

    RunningAppProcessInfo currentFg=getForegroundApp();
    ComponentName currentActivity=getActivityForApp(currentFg);

    if(currentFg!=null && currentFg.processName.equals(process.processName) &&
            (activity==null || currentActivity.compareTo(activity)==0))
        return true;

    Slog.i(TAG, "isStillActive returns false - CallerProcess: " + process.processName + " CurrentProcess: "
            + (currentFg==null ? "null" : currentFg.processName) + " CallerActivity:" + (activity==null ? "null" : activity.toString())
            + " CurrentActivity: " + (currentActivity==null ? "null" : currentActivity.toString()));
    return false;
}

private boolean isRunningService(String processname){
    if(processname==null || processname.isEmpty())
        return false;

    RunningServiceInfo service;

    if(mActivityManager==null)
        mActivityManager = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
    List <RunningServiceInfo> l = mActivityManager.getRunningServices(9999);
    Iterator <RunningServiceInfo> i = l.iterator();
    while(i.hasNext()){
        service = i.next();
        if(service.process.equals(processname))
            return true;
    }

    return false;
}
Свен Давиц
источник
1
Если вы не возражаете, я спрашиваю, зачем вам нужны методы, чтобы видеть, работает ли служба, активна ли она, и получать активность только для того, чтобы получить приложение переднего плана?
2
извините, но это не работает. Некоторые приложения фильтруются без причины, я думаю, это когда они запускают какую-то службу. Так что isRunningService их выгоняет.
сен
15
К сожалению, функция getRunningTasks () устарела с Android L (API 20). Что касается L, этот метод больше не доступен для сторонних приложений: введение ориентированных на документы последних означает, что он может передавать информацию о личности вызывающему. Для обратной совместимости он по-прежнему будет возвращать небольшое подмножество своих данных: по крайней мере, собственные задачи вызывающего абонента и, возможно, некоторые другие задачи, такие как home, которые, как известно, не являются конфиденциальными.
Сэм Лу
Кто-нибудь знает, чем этот подход лучше, чем просто делать mActivityManager.getRunningTasks(1).get(0).topActivity?
Сэм
35

Попробуйте следующий код:

ActivityManager activityManager = (ActivityManager) newContext.getSystemService( Context.ACTIVITY_SERVICE );
List<RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
for(RunningAppProcessInfo appProcess : appProcesses){
    if(appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND){
        Log.i("Foreground App", appProcess.processName);
    }
}

Имя процесса - это имя пакета приложения, работающего на переднем плане. Сравните его с названием пакета вашего приложения. Если это то же самое, то ваше приложение работает на переднем плане.

Надеюсь, это ответит на ваш вопрос.

Фридрих Баэр
источник
7
Начиная с версии Android L и выше, это единственное решение, которое работает, поскольку методы, используемые в других решениях, устарели.
androidGuy
4
однако это не работает, если приложение находится на переднем плане, а экран заблокирован
Krit
Правильный ответ должен быть
отмечен
1
Требуется ли дополнительное разрешение, например принятый ответ?
wonsuc
1
Это не совсем правильно. Имя процесса не всегда совпадает с именем соответствующего пакета приложения.
Сэм
25

Начиная с леденца на палочке, это изменилось. Пожалуйста, найдите ниже код, прежде чем этот пользователь должен перейти в Настройки -> Безопасность -> (Прокрутите вниз до последнего) Приложения с доступом к использованию -> Предоставьте разрешения нашему приложению

private void printForegroundTask() {
    String currentApp = "NULL";
    if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
        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();
            }
        }
    } else {
        ActivityManager am = (ActivityManager)this.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> tasks = am.getRunningAppProcesses();
        currentApp = tasks.get(0).processName;
    }

    Log.e(TAG, "Current App in foreground is: " + currentApp);
}
Суман
источник
1
нельзя установить эту статистику использования программно?
rubmz
@rubmz У вас есть решение по этому поводу?
VVB
1
в моем doogee с android 5.1 нет опции «Дать разрешения нашему приложению»
djdance
Это не дает 100% точных результатов ... в большинстве случаев это
верно
1
Это не покажет последнее приложение на переднем плане, но, возможно, последнее "использованное" каким-то образом - если вы оставите действие и перейдете к другому, потяните меню запуска сверху и отпустите его, вы окажетесь в " неправильное "занятие", пока вы не перейдете к другому. Неважно, прокручиваете вы экран или нет, он сообщает о неправильной активности, пока вы не запустите другой.
Azurlake
9

Для случаев, когда нам нужно проверить из нашего собственного сервиса / фонового потока, находится ли наше приложение на переднем плане или нет. Вот как я это реализовал, и у меня он отлично работает:

public class TestApplication extends Application implements Application.ActivityLifecycleCallbacks {

    public static WeakReference<Activity> foregroundActivityRef = null;

    @Override
    public void onActivityStarted(Activity activity) {
        foregroundActivityRef = new WeakReference<>(activity);
    }

    @Override
    public void onActivityStopped(Activity activity) {
        if (foregroundActivityRef != null && foregroundActivityRef.get() == activity) {
            foregroundActivityRef = null;
        }
    }

    // IMPLEMENT OTHER CALLBACK METHODS
}

Теперь, чтобы проверить из других классов, находится ли приложение на переднем плане, просто вызовите:

if(TestApplication.foregroundActivityRef!=null){
    // APP IS IN FOREGROUND!
    // We can also get the activity that is currently visible!
}

Обновление (как указано в SHS ):

Не забудьте зарегистрироваться для обратных вызовов в onCreateметоде вашего класса Application .

@Override
public void onCreate() {
    ...
    registerActivityLifecycleCallbacks(this);
}
Сартак Миттал
источник
2
Это то, что я искал последние два часа. Спасибо! :)
waseefakhtar
1
Нам нужно вызвать registerActivityLifecycleCallbacks (this); внутри метода переопределения "onCreate ()" класса Application (TestApplication). Это запустит обратные вызовы в нашем классе приложения.
SHS
@SarthakMittal, Если устройство переходит в режим ожидания или когда мы его блокируем, то будет вызвана onActivityStopped (). В зависимости от вашего кода это будет означать, что приложение работает в фоновом режиме. Но на самом деле это на переднем плане.
Аяз
@AyazAlifov Если телефон находится в режиме ожидания или телефон заблокирован, я не буду считать, что он находится на переднем плане, но опять же, это зависит от предпочтений.
Сартак Миттал,
8

Чтобы определить приложение переднего плана, которое вы можете использовать для обнаружения приложения переднего плана, вы можете использовать https://github.com/ricvalerio/foregroundappchecker . Он использует разные методы в зависимости от версии Android устройства.

Что касается сервиса, репо также предоставляет необходимый вам код. По сути, позвольте студии Android создать службу для вас, а затем onCreate добавить фрагмент, который использует appChecker. Однако вам нужно будет запросить разрешение.

Rvalerio
источник
идеальный!! протестировано на android 7
Pratik Tank
Спасибо. Это единственный ответ, который решил проблему, с которой мы столкнулись с уведомлениями приложений. Когда появляется уведомление, все остальные ответы возвращают имя пакета приложения, отправившего уведомление. Ваш LollipopDetector решил эту проблему. Один совет: из API 29 MOVE_TO_FOREGROUND устарел в UsageEvents.Event, поэтому начиная с Android Q следует использовать ACTIVITY_RESUMED. Тогда это будет работать как шарм!
Йорн Ригтер,
8

Принимая во внимание, что getRunningTasks()это устарело и getRunningAppProcesses()ненадежно, я решил объединить 2 подхода, упомянутых в StackOverflow:

   private boolean isAppInForeground(Context context)
    {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
        {
            ActivityManager am = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
            ActivityManager.RunningTaskInfo foregroundTaskInfo = am.getRunningTasks(1).get(0);
            String foregroundTaskPackageName = foregroundTaskInfo.topActivity.getPackageName();

            return foregroundTaskPackageName.toLowerCase().equals(context.getPackageName().toLowerCase());
        }
        else
        {
            ActivityManager.RunningAppProcessInfo appProcessInfo = new ActivityManager.RunningAppProcessInfo();
            ActivityManager.getMyMemoryState(appProcessInfo);
            if (appProcessInfo.importance == IMPORTANCE_FOREGROUND || appProcessInfo.importance == IMPORTANCE_VISIBLE)
            {
                return true;
            }

            KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
            // App is foreground, but screen is locked, so show notification
            return km.inKeyguardRestrictedInputMode();
        }
    }
Аяз Алифов
источник
4
вам нужно упомянуть, чтобы добавить в манифест устаревшее разрешение <uses-permission android: name = "android.permission.GET_TASKS" />, иначе приложение выйдет из
строя
1
android.permission.GET_TASKS - в настоящее время запрещено в Play Store
Duna
6

Класс ActivityManager - подходящий инструмент для просмотра запущенных процессов.

Для работы в фоновом режиме вы обычно хотите использовать службу .

Чарльз Даффи
источник
1

Это сработало для меня. Но дает только название главного меню. То есть, если пользователь открыл экран «Настройки» -> «Bluetooth» -> «Имя устройства», RunningAppProcessInfo называет его просто «Настройки». Не удалось развернуть Furthur

ActivityManager activityManager = (ActivityManager) context.getSystemService( Context.ACTIVITY_SERVICE );
                PackageManager pm = context.getPackageManager();
                List<RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
                for(RunningAppProcessInfo appProcess : appProcesses) {              
                    if(appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                        CharSequence c = pm.getApplicationLabel(pm.getApplicationInfo(appProcess.processName, PackageManager.GET_META_DATA));
                        Log.i("Foreground App", "package: " + appProcess.processName + " App: " + c.toString());
                    }               
                }
НакулСаргур
источник
4
Я думаю, это работает, только если ваше собственное приложение работает на переднем плане. Он ни о чем не сообщает, когда на переднем плане находится другое приложение.
Алиса Ван Дер Лэнд,
0

Сделайте что-нибудь вроде этого:

int showLimit = 20;

/* Get all Tasks available (with limit set). */
ActivityManager mgr = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> allTasks = mgr.getRunningTasks(showLimit);
/* Loop through all tasks returned. */
for (ActivityManager.RunningTaskInfo aTask : allTasks) 
{                  
    Log.i("MyApp", "Task: " + aTask.baseActivity.getClassName()); 
    if (aTask.baseActivity.getClassName().equals("com.android.email.activity.MessageList")) 
        running=true;
}
ChristianS
источник
Google, вероятно, отклонит приложение, которое использует ActivityManager.getRunningTasks (). В документации указано, что это только для целей разработчиков : developer.android.com/reference/android/app/ ... См. Исходный комментарий: stackoverflow.com/questions/4414171/…
ForceMagic
3
@ForceMagic - Google не отклоняет приложения просто за использование ненадежных API; более того, Google - не единственный канал распространения приложений Android. Тем не менее, следует иметь в виду, что информация из этого API не является надежной.
Крис Стрэттон
@ChrisStratton Интересно, но вы, вероятно, правы, хотя не рекомендуется использовать его для основной логики, они не отклонят приложение. Вы можете предупредить Скай Келси по ссылке для комментария.
ForceMagic
3
"getRunningTasks" действительно работает для api 21 и более поздних версий, поэтому было бы неплохо придумать другой способ заставить его работать для LOLLIPOP (хотя я сам не понял этого :()
amIT
0

В леденцах и выше:

Добавить в mainfest:

<uses-permission android:name="android.permission.GET_TASKS" />

И сделайте примерно так:

if( mTaskId < 0 )
{
    List<AppTask> tasks = mActivityManager.getAppTasks(); 
    if( tasks.size() > 0 )
        mTaskId = tasks.get( 0 ).getTaskInfo().id;
}
рубмз
источник
4
Он устарел на
маршмеллоу
0

Вот как я проверяю, находится ли мое приложение на переднем плане. Обратите внимание: я использую AsyncTask, как указано в официальной документации Android.

`

    private class CheckIfForeground extends AsyncTask<Void, Void, Void> {
    @Override
    protected Void doInBackground(Void... voids) {

        ActivityManager activityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
        for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
            if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                Log.i("Foreground App", appProcess.processName);

                if (mContext.getPackageName().equalsIgnoreCase(appProcess.processName)) {
                    Log.i(Constants.TAG, "foreground true:" + appProcess.processName);
                    foreground = true;
                    // close_app();
                }
            }
        }
        Log.d(Constants.TAG, "foreground value:" + foreground);
        if (foreground) {
            foreground = false;
            close_app();
            Log.i(Constants.TAG, "Close App and start Activity:");

        } else {
            //if not foreground
            close_app();
            foreground = false;
            Log.i(Constants.TAG, "Close App");

        }

        return null;
    }
}

и выполните AsyncTask следующим образом. new CheckIfForeground().execute();

Developine
источник
у вас есть ссылка на это?
user1743524
0

Я объединил два решения в одном методе, и он работает у меня для API 24 и для API 21. Остальные я не тестировал.

Код на Котлине:

private fun isAppInForeground(context: Context): Boolean {
    val appProcessInfo = ActivityManager.RunningAppProcessInfo()
    ActivityManager.getMyMemoryState(appProcessInfo)
    if (appProcessInfo.importance == IMPORTANCE_FOREGROUND ||
            appProcessInfo.importance == IMPORTANCE_VISIBLE) {
        return true
    } else if (appProcessInfo.importance == IMPORTANCE_TOP_SLEEPING ||
            appProcessInfo.importance == IMPORTANCE_BACKGROUND) {
        return false
    }

    val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    val foregroundTaskInfo = am.getRunningTasks(1)[0]
    val foregroundTaskPackageName = foregroundTaskInfo.topActivity.packageName
    return foregroundTaskPackageName.toLowerCase() == context.packageName.toLowerCase()
}

и в манифесте

<!-- Check whether app in background or foreground -->
<uses-permission android:name="android.permission.GET_TASKS" />
Деншов
источник