Android 5.1.1 и выше - getRunningAppProcesses () возвращает только мой пакет приложения

100

Похоже, Google наконец закрыл все двери для получения текущего пакета приложений переднего плана.

После обновления Lollipop, которое убило, getRunningTasks(int maxNum)и благодаря этому ответу я использовал этот код для получения пакета приложения переднего плана, начиная с Lollipop:

final int PROCESS_STATE_TOP = 2;
RunningAppProcessInfo currentInfo = null;
Field field = null;
try {
    field = RunningAppProcessInfo.class.getDeclaredField("processState");
} catch (Exception ignored) { 
}
ActivityManager am = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> appList = am.getRunningAppProcesses();
for (RunningAppProcessInfo app : appList) {
    if (app.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
        app.importanceReasonCode == 0 ) {
        Integer state = null;
        try {
            state = field.getInt( app );
        } catch (Exception ignored) {
        }
        if (state != null && state == PROCESS_STATE_TOP) {
            currentInfo = app;
            break;
        }
    }
}
return currentInfo;

Android 5.1.1 и выше (6.0 Marshmallow) вроде getRunningAppProcesses()тоже убили . Теперь он возвращает список вашего собственного пакета приложений.


UsageStatsManager

Мы можем использовать новый UsageStatsManagerAPI, как описано здесь, но он работает не для всех приложений. Некоторые системные приложения возвращают один и тот же пакет

com.google.android.googlequicksearchbox

AccessibilityService (декабрь 2017 г .: будет запрещен для использования Google)

Некоторые приложения используют AccessibilityService(как показано здесь ), но у него есть некоторые недостатки.


Есть ли другой способ получить пакет текущего работающего приложения?

Лиор Илуз
источник
1
Du Speed ​​Booster вроде работает. Я не уверен, использует ли он UsageStatsManager.
thecr0w
3
Обратите внимание, что несколько основных поставщиков удалили системные действия, которые предоставляют доступ к UsageStats API со своих устройств. Это означает, что приложения на этих устройствах никогда не смогут получить необходимое разрешение.
Кевин Крумвиде,
3
Samsung является одним из них. Это более 60% рынка.
Кевин Крамвиде,
3
Вот билет из системы отслеживания ошибок Android по этой проблеме: code.google.com/p/android-developer-preview/issues/…
Флориан Барт,
2
Если я анализирую вывод выполнения psв оболочке и политика равна, "fg"а содержимое /proc/[pid]/oom_adj_scoreравно, 0тогда приложение является приложением переднего плана. К сожалению, он /proc/[pid]/oom_adj_scoreбольше не читается на Android 6.0. gist.github.com/jaredrummler/7d1498485e584c8a120e
Джаред Раммлер,

Ответы:

93

Чтобы получить список запущенных процессов на Android 1.6 - Android 6.0, вы можете использовать эту библиотеку, которую я написал: https://github.com/jaredrummler/AndroidProcesses . Библиотека читает / proc для получения информации о процессе.

Google значительно ограничил доступ к / proc в Android Nougat. Чтобы получить список запущенных процессов на Android Nougat, вам необходимо использовать UsageStatsManager или иметь root-доступ.

Щелкните историю редактирования, чтобы просмотреть предыдущие альтернативные решения.

Джаред Раммлер
источник
4
@androiddeveloper нет, я использовал только libsuperuser, потому что он предоставляет простой способ запустить команду оболочки (не root или root) и получить результат. Я могу переписать его без libsuperuser, если будет достаточно спроса.
Джаред Раммлер,
1
Простите за это. Было просто любопытно. :(
разработчик Android
1
@JaredRummler Я сейчас внедряю ваше решение в свое приложение. Кажется, работает нормально, насколько я могу судить
guy.gc
1
@androiddeveloper, если вы хотите отсортировать его на основе другого атрибута, сделайте Processреализацию Comparableи переопределите compareToметод. Затем, когда вы используете Collections.sort(processesList), он будет использовать указанный вами порядок.
Srini
2
@BruceWayne Я думаю, что Джаред ссылается на эту ссылку: https://source.android.com/devices/tech/security/selinux/index.html
Эдуардо Херцер
25
 private String printForegroundTask() {
    String currentApp = "NULL";
    if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
        UsageStatsManager usm = (UsageStatsManager)this.getSystemService("usagestats");
        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("adapter", "Current App in foreground is: " + currentApp);
    return currentApp;
}

Используйте этот метод для получения задачи переднего плана. U потребуется системное разрешение "android: get_usage_stats"

public static boolean needPermissionForBlocking(Context context){
    try {
        PackageManager packageManager = context.getPackageManager();
        ApplicationInfo applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), 0);
        AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
        return  (mode != AppOpsManager.MODE_ALLOWED);
    } catch (PackageManager.NameNotFoundException e) {
        return true;
    }
}

ЕСЛИ пользователь включит это в настройках -> Безопасность-> приложение с доступом к использованию. После этого вы получите задачу переднего плана. Аналогичный процесс Очистить матсер от Cheetahamobile ссылка на игру в Google

Тарун Шарма
источник
Как указано в OP и комментариях, есть серьезные недостатки использования UsageStatsManager. Компания Samsung удалила запросы, и пользователю очень неприятно предоставлять системное разрешение.
Джаред Раммлер
Тестировал в мото е2 девайс. Он работает нормально, и да, нам нужно реализовать это разрешение. мы все сталкиваемся с одинаковыми проблемами. Нам всем действительно нужен лучший подход. Удачи, чувак
Тарун Шарма
2
Этот подход не работает, по крайней мере, на LG G3, потому что в меню нет пункта «Настройки -> Безопасность -> Приложение с доступом к использованию»
Дмитрий
2
Это не ответ на мой вопрос. Более того, в этом ответе нет новой информации.
Лиор Илуз 05
1
Работает нормально, но не в зефире. если пришло уведомление, то текущим запущенным процессом будет полученное уведомление о пакете. на самом деле приложение не работает на переднем плане или в фоновом режиме :(. Требуется решение.
WonderSoftwares 01
6

Взгляните на https://github.com/ricvalerio/foregroundappchecker , возможно, это то, что вам нужно. Предоставляет образец кода и избавляет от необходимости реализовывать перекрестный детектор переднего плана.

Вот два образца:

AppChecker appChecker = new AppChecker();
String packageName = appChecker.getForegroundApp();

Или регулярно проверяйте:

AppChecker appChecker = new AppChecker();
appChecker
    .when("com.other.app", new AppChecker.Listener() {
        @Override
        public void onForeground(String packageName) {
            // do something
        }
    )
    .when("com.my.app", new AppChecker.Listener() {
        @Override
        public void onForeground(String packageName) {
            // do something
        }
    )
    .other(new AppChecker.Listener() {
        @Override
        public void onForeground(String packageName) {
            // do something
        }
    )
    .timeout(1000)
    .start(this);
Rvalerio
источник
2
Спасибо, но здесь ничего нового. Он просто обернул UsageStatsManager API, упомянутый в OP. Пользователь должен будет включить доступ, как и другие методы, начиная с Android 5.1.1
Лиор Илуз,
@ Джереми, вы запрашивали необходимые разрешения?
rvalerio
4

Google ограничил эту функцию только для системных приложений. Как сообщалось в сообщении об ошибке , для доступа к нему вам потребуется разрешение REAL_GET_TASKS .

Теперь приложения должны иметь ... разрешение.REAL_GET_TASKS, чтобы иметь возможность получать информацию о процессе для всех приложений. Будет возвращена только информация о процессе для вызывающего приложения, если у приложения нет разрешения. Приложения с привилегиями временно смогут получать информацию о процессе для всех приложений, если у них нет нового разрешения, но они устарели ... разрешение. GET_TASKS Кроме того, только системные приложения могут получить разрешение REAL_GET_TASKS.

Илья Газман
источник
Я видел, что блокировка приложений все еще работает над 5.1.1, как только приложение получит разрешение в безопасности -> «приложения с доступом к использованию» ... Это несколько удивительно
Махеш Рен
«Разрешения с уровнем защиты signature, privileged или signatureOrSystem предоставляются только системным приложениям. Если приложение является обычным несистемным приложением, оно никогда не сможет использовать эти разрешения», - говорит студия Android. Удачи в общении с Google будет признан "системным приложением".
Джереми
2

Просто добавление потенциальной оптимизации к тому, что я представляю, - это сильно скопированный фрагмент кода для обнаружения самого верхнего приложения на Android M.

это

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
    UsageStatsManager usm = (UsageStatsManager)this.getSystemService("usagestats");
    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();
        }
    }
}

Можно упростить до этого

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
    UsageStatsManager usm = (UsageStatsManager) context.getSystemService(
        Context.USAGE_STATS_SERVICE);
    long time = System.currentTimeMillis();
    List<UsageStats> appStatsList = usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY,
            time - 1000 * 1000, time);
    if (appStatsList != null && !appStatsList.isEmpty()) {
        currentApp = Collections.max(appStatsList, (o1, o2) ->
            Long.compare(o1.getLastTimeUsed(), o2.getLastTimeUsed())).getPackageName();
    }
}

Я обнаружил, что использую этот код в двухсекундном цикле, и задался вопросом, почему я использую сложное решение, которое было O (n * log (n)), когда более простое решение было доступно в Collections.max (), которое равно O (n ).

bstar55
источник
0
public class AccessibilityDetectingService 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(final AccessibilityEvent event) {
        if (event == null ) {
            return;
        } else if(event.getPackageName() == null && event.getClassName() == null){
            return;
        }

            if (activityInfo != null){

                Log.d("CurrentActivity", componentName.flattenToShortString());
        }

}

private ActivityInfo tryGetActivity(ComponentName componentName) {
    try {
        return getPackageManager().getActivityInfo(componentName, 0);
    } catch (PackageManager.NameNotFoundException e) {
        return null;
    }
}
@Override
public void onInterrupt() {
}                
}
}//`enter code here`uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
<uses-permission android:name="android.permission.GET_TASKS" />

Затем запустите доступность службы и приложения в настройках вашего устройства -> доступность -> Приложение для этой службы.

Рам Бхаван Кушваха
источник
Это уже рассматривается в вопросе, и вы только что скопировали код из исходного источника в StackOverflow.
Сэм
-2

Пожалуйста, попробуйте использовать getRunningServices()вместо getRunningAppProcesses()метода.

 ActivityManager mActivityManager = (ActivityManager) getSy stemService(Context.ACTIVITY_SERVICE);

 List<ActivityManager.RunningServiceInfo> appProcessInfoList = mActivityManager.getRunningServices(Integer.MAX_VALUE);
Джо
источник
3
Добро пожаловать в Stack Overflow! Я не думаю, что это сработает, поскольку приложение переднего плана может не запускать службу.
Сэм,