Android 5.1.1 i nowsze - getRunningAppProcesses () zwraca tylko mój pakiet aplikacji

100

Wygląda na to, że Google w końcu zamknęło wszystkie drzwi do uzyskania obecnego pakietu aplikacji pierwszego planu.

Po aktualizacji Lollipopa, która zabiła getRunningTasks(int maxNum)i dzięki tej odpowiedzi użyłem tego kodu do pobrania pakietu aplikacji pierwszego planu od Lollipopa:

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;

Wygląda na to, że Android 5.1.1 i nowszy (6.0 Marshmallow) również został zabity getRunningAppProcesses(). Teraz zwraca listę własnych pakietów aplikacji.


UsageStatsManager

Możemy używać nowego UsageStatsManagerAPI zgodnie z opisem tutaj, ale nie działa on dla wszystkich aplikacji. Niektóre aplikacje systemowe zwracają ten sam pakiet

com.google.android.googlequicksearchbox

AccessibilityService (grudzień 2017: zostanie zbanowany do użytku przez Google)

Niektóre aplikacje używają AccessibilityService(jak widać tutaj ), ale ma pewne wady.


Czy istnieje inny sposób uzyskania aktualnie uruchomionego pakietu aplikacji?

Lior Iluz
źródło
1
Wydaje się, że Du Speed ​​Booster działa. Nie jestem pewien, czy używa UsageStatsManager.
thecr0w
3
Należy pamiętać, że kilku głównych dostawców usunęło aktywność systemową, która zapewnia dostęp do interfejsu API UsageStats ze swoich urządzeń. Oznacza to, że aplikacje na tych urządzeniach nigdy nie mogą uzyskać niezbędnych uprawnień.
Kevin Krumwiede
3
Samsung jest jednym z nich. To ponad 60% rynku.
Kevin Krumwiede
3
Oto zgłoszenie od narzędzia do śledzenia błędów Androida dotyczące tego problemu: code.google.com/p/android-developer-preview/issues/ ...
Florian Barth
2
Jeśli przeanalizuję dane wyjściowe działania psw powłoce i zasada jest, "fg"a zawartość /proc/[pid]/oom_adj_scorejest równa, 0to aplikacja jest aplikacją pierwszego planu. Niestety wydaje się, że /proc/[pid]/oom_adj_scorenie można go już odczytać w systemie Android 6.0. gist.github.com/jaredrummler/7d1498485e584c8a120e
Jared Rummler

Odpowiedzi:

93

Aby uzyskać listę uruchomionych procesów na Androidzie 1.6 - Android 6.0 możesz skorzystać z tej biblioteki, którą napisałem: https://github.com/jaredrummler/AndroidProcesses Biblioteka czyta / proc, aby uzyskać informacje o procesie.

Google znacznie ograniczył dostęp do / proc w Androidzie Nougat. Aby uzyskać listę uruchomionych procesów na Androidzie Nougat, musisz użyć UsageStatsManager lub mieć uprawnienia administratora.

Kliknij historię edycji, aby zobaczyć poprzednie alternatywne rozwiązania.

Jared Rummler
źródło
4
@androiddeveloper nie, użyłem tylko libsuperuser, ponieważ zapewnia łatwy sposób uruchomienia polecenia powłoki (innego niż root lub root) i uzyskania danych wyjściowych. Mogę go ponownie napisać bez libsuperuser, jeśli jest wystarczające zapotrzebowanie.
Jared Rummler,
1
Przepraszam za to. Byłem po prostu ciekawy. :(
programista Androida,
1
@JaredRummler Jestem obecnie w trakcie wdrażania Twojego rozwiązania do mojej aplikacji. Wydaje się, że działa dobrze, o ile wiem
guy.gc
1
@androiddeveloper, Jeśli chcesz posortować go na podstawie innego atrybutu, zrób Processimplementację Comparablei zastąp compareTometodę. Następnie, gdy użyjesz Collections.sort(processesList), użyje określonej przez Ciebie kolejności.
Srini,
2
@BruceWayne Myślę, że Jared odnosi się do tego linku: https://source.android.com/devices/tech/security/selinux/index.html
Eduardo Herzer
24
 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żyj tej metody, aby uzyskać zadanie pierwszego planu. Będziesz potrzebować uprawnień systemowych „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;
    }
}

JEŚLI użytkownik włączy to w ustawieniach -> Bezpieczeństwo-> aplikacja z dostępem do użytkowania. Po tym otrzymasz zadanie pierwszego planu. Podobny proces Wyczyść matser przez Cheetahamobile link do Google Play

Tarun Sharma
źródło
Jak wskazano w PO i komentarzach, używanie ma poważne wady UsageStatsManager. Samsung usunął żądania, a przyznanie uprawnień systemowych jest dość denerwujące.
Jared Rummler
Testowałem na urządzeniu moto e2. Działa dobrze i tak, musimy wdrożyć to zezwolenie. wszyscy mamy te same problemy. Wszyscy naprawdę potrzebujemy lepszego podejścia. Powodzenia stary
Tarun Sharma
2
To podejście nie działa przynajmniej na LG G3, ponieważ nie ma w menu „Ustawienia -> Bezpieczeństwo-> Aplikacja z dostępem do użytkowania”
Dmitry
2
To nie jest odpowiedź na moje pytanie. Ponadto w tej odpowiedzi nie podano żadnych nowych informacji.
Lior Iluz,
1
Działa dobrze, ale nie prawidłowo w ptasie mleczko. jeśli nadeszło powiadomienie, bieżącym uruchomionym procesem będzie otrzymane powiadomienie o pakiecie. w rzeczywistości aplikacja nie działa na pierwszym planie ani w tle :(. potrzebujesz rozwiązania.
WonderSoftwares
6

Spójrz na https://github.com/ricvalerio/foregroundappchecker , może to być to, czego potrzebujesz. Dostarcza przykładowy kod i eliminuje ból związany z koniecznością implementacji wykrywacza pierwszego planu między wersjami.

Oto dwie próbki:

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

Lub regularnie sprawdzaj:

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
źródło
2
Dzięki, ale nie ma tu nic nowego. Właśnie zapakował API UsageStatsManager, o którym jest mowa w OP. Użytkownik będzie musiał włączyć dostęp, podobnie jak inne metody od Androida 5.1.1
Lior Iluz
@ Jérémy czy poprosiłeś o wymagane uprawnienia?
rvalerio
Wygląda na to, że cały czas sonduje, prawda?
programista Androida
4

Firma Google ograniczyła tę funkcjonalność tylko do aplikacji systemowych. Jak informowaliśmy w zgłoszeniu błędu , będziesz potrzebować uprawnienia REAL_GET_TASKS, aby uzyskać do niego dostęp.

Aplikacje muszą mieć teraz ... uprawnienia.REAL_GET_TASKS, aby móc uzyskać informacje o procesach dla wszystkich aplikacji. Tylko informacje o procesie dla aplikacji wywołującej zostaną zwrócone, jeśli aplikacja nie ma pozwolenia. Aplikacje z uprawnieniami będą tymczasowo mogły uzyskiwać informacje o procesach dla wszystkich aplikacji, jeśli nie mają nowych uprawnień, ale mają wycofane uprawnienia ... GET_TASKS Ponadto tylko aplikacje systemowe mogą uzyskać uprawnienie REAL_GET_TASKS.

Ilya Gazman
źródło
Widziałem, że blokada nadal działa w wersji 5.1.1, gdy aplikacja uzyska uprawnienia w zakresie bezpieczeństwa -> „aplikacje z dostępem do użytkowania” ... To trochę zaskakujące
mahesh ren
„Uprawnienia z sygnaturą poziomu ochrony, uprzywilejowanym lub podpisemOrSystem są przyznawane tylko aplikacjom systemowym. Jeśli aplikacja jest zwykłą aplikacją niesystemową, nigdy nie będzie mogła korzystać z tych uprawnień” - mówi Android Studio. Powodzenia w negocjowaniu Google jako „aplikacja systemowa”.
Jérémy
2

Po prostu rzucam potencjalną optymalizację tego, co sobie wyobrażam, to mocno skopiowany fragment kodu do wykrywania najpopularniejszej aplikacji na Androida M.

To

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

Można to uprościć

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

Odkryłem, że używam tego kodu w 2-sekundowej pętli i zastanawiałem się, dlaczego używam złożonego rozwiązania, które było O (n * log (n)), skoro prostsze rozwiązanie było dostępne w Collections.max (), czyli O (n ).

bstar55
źródło
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" />

Następnie uruchom usługę i dostępność aplikacji w ustawieniach swojego urządzenia-> dostępność-> Aplikacja w tej usłudze.

Ram Bhawan Kushwaha
źródło
Jest to już omówione w pytaniu, a właśnie skopiowałeś i wkleiłeś kod z jego oryginalnego źródła na StackOverflow.
Sam,
-2

Spróbuj użyć getRunningServices()zamiast getRunningAppProcesses()metody.

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

 List<ActivityManager.RunningServiceInfo> appProcessInfoList = mActivityManager.getRunningServices(Integer.MAX_VALUE);
Joe
źródło
3
Witamy w Stack Overflow! Myślę, że to nie zadziała, ponieważ aplikacja na pierwszym planie może nie uruchamiać usługi.
Sam