Визначення поточної програми переднього плану з фонового завдання чи послуги


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

3
ВИДАЛЕННЯ ВІДОМОГО: Примітка: цей метод призначений лише для налагодження та представлення користувальницьких інтерфейсів управління завданнями. <- Щойно помітили, що в документі Java. Тож цей спосіб у майбутньому може змінитися без попередження.
brandall

47
Примітка: getRunningTasks()застаріло в API 21 (Lollipop) - developer.android.com/reference/android/app/…
dtyler

@dtyler, гаразд тоді. Чи є натомість якийсь інший метод? Хоча Google не хоче, щоб сторонні програми отримували конфіденційну інформацію, у мене є ключ платформи пристрою. Чи є якийсь інший метод, щоб зробити те саме, якщо у мене є системні привілеї?
Yeung

Існує спосіб використання "Статистика використання api", запроваджена 21
KunalK

38

Мені довелося з’ясувати правильне рішення важким шляхом. наведений нижче код є частиною cyanogenmod7 (виправлення планшета) і тестується на Android 2.3.3 / пряники.

методи:

  • 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 виганяє їх.
seb

15
На жаль, функція getRunningTasks () застаріла з Android L (API 20). За станом на L, цей спосіб більше не доступний для сторонніх додатків: введення документів, орієнтованих на документ, означає, що він може просочувати інформацію про особу, що телефонує. Для зворотної сумісності він все одно поверне невелику підмножину своїх даних: принаймні власні завдання абонента та, можливо, деякі інші завдання, наприклад, домашні, які, як відомо, не чутливі.
Сем Лу

Хтось знає, наскільки цей підхід кращий, ніж просто робити 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
однак це не працює, якщо додаток на передньому плані, а екран заблокований
Крит

Правильну відповідь слід поставити галочкою
A_rmas

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 У вас є рішення щодо цього?
ВВБ

1
у мого doogee з android 5.1 немає опції "Надати дозволи нашому додатку"
djdance

Це не дає 100% точних результатів ... це найбільше значення
CamHart

1
Це не відображатиме останній додаток на передньому плані, але, можливо, останній "використаний" способом - якщо ви залишите дію та перейдете до іншого, витягніть меню запуску зверху та відпустіть його, ви опинитесь у " неправильна "діяльність", поки ви не перейдете до іншої. Неважливо, прокручуєте ви екран чи ні, він повідомляє про неправильну активність, поки не запустите інший.
Azurlake

9

У випадках, коли нам потрібно перевірити у власній службі / background-thread, чи наша програма стоїть на передньому плані чи ні. Ось як я його реалізував, і він прекрасно працює для мене:

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 (це);" всередині методу Override "onCreate ()" класу Application (TestApplication). Це запустить зворотні дзвінки в нашому класі додатків.
SHS

@SarthakMittal, Якщо пристрій переходить у режим очікування або коли ми його заблокуємо, тоді буде викликано onActivityStopped (). На основі вашого коду це вказуватиме на те, що програма знаходиться у фоновому режимі. Але насправді це на передньому плані.
Аяз Аліфов

@AyazAlifov Якщо телефон у режимі очікування або телефон заблокований, я не вважатиму його на передньому плані, але знову це залежить від уподобань.
Сартак Міттал

8

Щоб визначити додаток переднього плану, ви можете використовувати для виявлення програми переднього плану, ви можете використовувати https://github.com/ricvalerio/foregroundappchecker . Він використовує різні методи залежно від версії пристрою для Android.

Що стосується послуги, то РЕПО також надає необхідний для вас код. По суті, дозвольте андроїд-студії створити сервіс для вас, а потім onCreate додати фрагмент, який використовує appChecker. Однак вам потрібно буде просити дозволу.


ідеально !! перевірено на android 7
Pratik Tank

Дуже дякую. Це єдина відповідь, яка вирішила проблему, з якою ми стикалися з повідомленнями програм. Коли спливе повідомлення, усі інші відповіді дадуть назву пакета програми, яка надіслала сповіщення. Ваш LollipopDetector вирішив цю проблему. Один натяк: від API 29 у UsageEvents.Event застарілий MOVE_TO_FOREGROUND застарілий, тому від Android Q далі слід використовувати ACTIVITY_RESUMED. Тоді це спрацює як шарм!
Jorn Rigter

8

Враховуючи, що getRunningTasks()застаріле і getRunningAppProcesses()не є надійним, я прийняв рішення поєднати два підходи, згадані в 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
вам потрібно згадати, щоб додати до маніфесту застарілий дозвіл <використання-дозволу android: name = "android.permission.GET_TASKS" /> в іншому випадку додаток вийде з
ладу

1
android.permission.GET_TASKS - наразі заборонено в Play Store
Дуная


1

Це працювало для мене. Але він дає лише назву головного меню. Тобто, якщо користувач відкрив екран Налаштування -> Bluetooth -> Ім'я пристрою, RunningAppProcessInfo називає це лише Параметри. Не в змозі пробурити фуртур

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;
}

Google, ймовірно, відхилить додаток, який використовує ActivityManager.getRunningTasks (). У документації йдеться , що тільки цілі недоброзичливець Dev: developer.android.com/reference/android/app / ... Дивіться intial коментар stackoverflow.com/questions/4414171 / ...
ForceMagic

3
@ForceMagic - Google не відхиляє програми просто для використання ненадійних API; крім того, Google - не єдиний канал розповсюдження програм для Android. З огляду на це, варто пам’ятати, що інформація цього API не є надійною.
Кріс Страттон

@ChrisStratton Цікаво, але ви, мабуть, праві, хоча використовувати його для основної логіки не рекомендується, вони не відхилять додаток. Ви можете попередити Скай Келсі за посиланням для коментарів.
ForceMagic

3
"getRunningTasks" працює для api 21 і за її межами, тому, можливо, буде гарна ідея розглянути інший спосіб змусити його працювати для LOLLIPOP (я сам сам цього не зрозумів :()
amIT

0

У льодяник і вгору:

Додати в основний фест:

<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
Він застарілий на зефір
jinkal

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


у вас є посилання на це?
користувач1743524

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" />
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.