Android 5.1.1 y superior: getRunningAppProcesses () devuelve solo el paquete de mi aplicación

100

Parece que Google finalmente cerró todas las puertas para obtener el paquete actual de aplicaciones en primer plano.

Después de la actualización de Lollipop, que mató getRunningTasks(int maxNum)y gracias a esta respuesta , usé este código para obtener el paquete de la aplicación en primer plano desde 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 y superior (6.0 Marshmallow), al parecer, también murió getRunningAppProcesses(). Ahora devuelve una lista de su propio paquete de aplicación.


UsageStatsManager

Podemos usar la nueva UsageStatsManagerAPI como se describe aquí, pero no funciona para todas las aplicaciones. Algunas aplicaciones del sistema devolverán el mismo paquete

com.google.android.googlequicksearchbox

AccessibilityService (diciembre de 2017: Google lo prohibirá)

Algunas aplicaciones usan AccessibilityService(como se ve aquí ) pero tiene algunas desventajas.


¿Existe otra forma de obtener el paquete de aplicación actual en ejecución?

Lior Iluz
fuente
1
Du Speed ​​Booster parece funcionar. No estoy seguro de si usa UsageStatsManager.
thecr0w
3
Tenga en cuenta que varios proveedores importantes han eliminado la actividad del sistema que otorga acceso a la API de UsageStats desde sus dispositivos. Esto significa que las aplicaciones en esos dispositivos nunca pueden obtener el permiso necesario.
Kevin Krumwiede
3
Samsung es uno de ellos. Eso es más del 60% del mercado.
Kevin Krumwiede
3
Aquí hay un ticket del rastreador de errores de Android con respecto a este problema: code.google.com/p/android-developer-preview/issues/…
Florian Barth
2
Si analizo el resultado de la ejecución psen un shell y la política es "fg"y el contenido /proc/[pid]/oom_adj_scorees igual, 0entonces la aplicación es la aplicación en primer plano. Desafortunadamente, parece /proc/[pid]/oom_adj_scoreque ya no se puede leer en Android 6.0. gist.github.com/jaredrummler/7d1498485e584c8a120e
Jared Rummler

Respuestas:

93

Para obtener una lista de los procesos en ejecución en Android 1.6 - Android 6.0, puede usar esta biblioteca que escribí: https://github.com/jaredrummler/AndroidProcesses La biblioteca lee / proc para obtener información del proceso.

Google ha restringido significativamente el acceso a / proc en Android Nougat. Para obtener una lista de los procesos en ejecución en Android Nougat, deberá usar UsageStatsManager o tener acceso de root.

Haga clic en el historial de edición para ver soluciones alternativas anteriores.

Jared Rummler
fuente
4
@androiddeveloper no, solo usé libsuperuser porque proporciona una manera fácil de ejecutar un comando de shell (no root o root) y obtener el resultado. Puedo volver a escribirlo sin libsuperuser si hay suficiente demanda.
Jared Rummler
1
Lo siento por eso. Solo tenía curiosidad. :(
desarrollador de Android
1
@JaredRummler Actualmente estoy en el proceso de implementar su solución en mi aplicación. Parece estar funcionando bien por lo que puedo decir
guy.gc
1
@androiddeveloper, si desea ordenarlo en función de un atributo diferente, Processimplemente Comparabley anule el compareTométodo. Luego, cuando lo use Collections.sort(processesList), usará el orden que especificó.
Srini
2
@BruceWayne Creo que Jared se está refiriendo a este enlace: 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;
}

Utilice este método para obtener la tarea en primer plano. Necesitará un permiso del sistema "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;
    }
}

SI el usuario habilita esto en la configuración -> Seguridad-> aplicación con acceso de uso. Después de eso, obtendrá la tarea de primer plano. Proceso similar Limpiar matser por Cheetahamobile google play link

Tarun Sharma
fuente
Como se señaló en el OP y los comentarios, existen importantes desventajas de usar UsageStatsManager. Samsung ha eliminado las solicitudes y es bastante molesto para un usuario otorgar un permiso del sistema.
Jared Rummler
Lo he probado en el dispositivo moto e2. Está funcionando bien y sí, necesitamos implementar este permiso. todos enfrentamos los mismos problemas. Todos realmente necesitamos un mejor enfoque. Buena suerte amigo
Tarun Sharma
2
Este enfoque no funciona al menos en LG G3 porque no hay un elemento de menú "Configuración -> Seguridad-> Aplicación con acceso de uso"
Dmitry
2
Esa no es una respuesta a mi pregunta. Además, no se proporciona información nueva en esta respuesta.
Lior Iluz
1
Funciona bien pero no correctamente en malvavisco. si llegó la notificación, el proceso en ejecución actual será la notificación del paquete recibida. en realidad, la aplicación no se está ejecutando en primer plano o en segundo plano :(. Necesito solución.
WonderSoftwares
6

Eche un vistazo a https://github.com/ricvalerio/foregroundappchecker , podría ser lo que necesita. Proporciona código de muestra y elimina la molestia de tener que implementar un detector de primer plano de versiones cruzadas.

Aquí hay dos ejemplos:

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

O verifique regularmente:

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
fuente
2
Gracias, pero no hay nada nuevo aquí. Él acaba de envolver la API de UsageStatsManager que se menciona en OP. El usuario deberá habilitar el acceso, como otros métodos desde Android 5.1.1
Lior Iluz
@ Jérémy, ¿solicitó los permisos necesarios?
rvalerio
Parece que también está votando todo el tiempo, ¿verdad?
desarrollador de Android
4

Google limitó esta funcionalidad solo para aplicaciones del sistema. Como se informó en un ticket de error , necesitará el permiso REAL_GET_TASKS para acceder allí.

Las aplicaciones ahora deben tener ... permiso.REAL_GET_TASKS para poder obtener información de proceso para todas las aplicaciones. Solo se devolverá la información del proceso de la aplicación que realiza la llamada si la aplicación no tiene el permiso. Las aplicaciones de privilegios podrán obtener temporalmente información de proceso para todas las aplicaciones si no tienen el nuevo permiso, pero han desaprobado el permiso ....GET_TASKS Además, solo las aplicaciones del sistema pueden adquirir el permiso REAL_GET_TASKS.

Ilya Gazman
fuente
Vi que applock todavía funciona en 5.1.1, una vez que la aplicación obtiene el permiso en seguridad -> "aplicaciones con acceso de uso" ... Esto es algo sorprendente
mahesh ren
"Los permisos con el nivel de protección de firma, privilegiado o signatureOrSystem solo se otorgan a las aplicaciones del sistema. Si una aplicación es una aplicación normal que no pertenece al sistema, nunca podrá usar estos permisos", dice Android Studio. Buena suerte para negociar con Google será aceptado como una "aplicación del sistema".
Jérémy
2

Simplemente lanzando una optimización potencial de lo que imagino es un fragmento de código copiado y pegado para detectar la aplicación más importante en Android M.

Esta

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

Puede simplificarse a esto

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

Me encontré usando este código en un ciclo de 2 segundos, y me pregunté por qué estaba usando una solución compleja que era O (n * log (n)) cuando había una solución más simple disponible en Collections.max () que es O (n ).

bstar55
fuente
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" />

Luego, inicie el servicio y la accesibilidad de la aplicación en la configuración de su dispositivo-> accesibilidad-> Aplicación en ese servicio.

Ram Bhawan Kushwaha
fuente
Esto ya está cubierto en la pregunta, y acaba de copiar y pegar el código de su fuente original en StackOverflow.
Sam
-2

Intente utilizar en getRunningServices()lugar de getRunningAppProcesses()método.

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

 List<ActivityManager.RunningServiceInfo> appProcessInfoList = mActivityManager.getRunningServices(Integer.MAX_VALUE);
Joe
fuente
3
¡Bienvenido a Stack Overflow! No creo que esto funcione ya que es posible que la aplicación en primer plano no esté ejecutando un servicio.
Sam