Determinar la aplicación en primer plano actual a partir de una tarea o servicio en segundo plano

112

Deseo tener una aplicación que se ejecute en segundo plano, que sepa cuándo se está ejecutando alguna de las aplicaciones integradas (mensajería, contactos, etc.).

Entonces mis preguntas son:

  1. Cómo debo ejecutar mi aplicación en segundo plano.

  2. Cómo mi aplicación en segundo plano puede saber cuál es la aplicación que se está ejecutando actualmente en primer plano.

Las respuestas de personas con experiencia serán muy apreciadas.

dhaiwat
fuente
No creo que haya dado una explicación adecuada de lo que está tratando de hacer. ¿Qué está intentando hacer tu aplicación en segundo plano? ¿De qué formas debería poder interactuar con el usuario? ¿Por qué necesita saber cuál es la aplicación de primer plano actual? Etc.
Charles Duffy
1
para detectar la aplicación de primer plano, puede usar github.com/ricvalerio/foregroundappchecker
rvalerio

Respuestas:

104

Con respecto a "2. Cómo mi aplicación en segundo plano puede saber cuál es la aplicación que se está ejecutando actualmente en primer plano".

NO use el getRunningAppProcesses()método ya que esto devuelve todo tipo de basura del sistema de mi experiencia y obtendrá múltiples resultados que tienen RunningAppProcessInfo.IMPORTANCE_FOREGROUND. Utilizar en su getRunningTasks()lugar

Este es el código que utilizo en mi servicio para identificar la aplicación en primer plano actual, es realmente fácil:

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

Eso es todo, luego puede acceder fácilmente a los detalles de la aplicación / actividad en primer plano:

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

Esto requiere un permiso adicional en menifest de actividad y funciona perfectamente.

<uses-permission android:name="android.permission.GET_TASKS" />
Oliver Pearmain
fuente
1
Esto debe marcarse como la respuesta correcta. Permiso requerido: android.permission.GET_TASKS es el único inconveniente menor de otros métodos que pueden evitarlo.
brandall
3
EDITAR HACIA ARRIBA: Nota: este método solo está destinado a depurar y presentar interfaces de usuario de administración de tareas. <- Acabo de ver eso en el documento de Java. Por tanto, este método podría cambiar sin previo aviso en el futuro.
brandall
47
Nota: getRunningTasks()está en desuso en API 21 (Lollipop) - developer.android.com/reference/android/app/…
dtyler
@dtyler, está bien entonces. ¿Existe algún otro método en su lugar? Aunque Google no quiere que las aplicaciones de terceros obtengan información confidencial, tengo la clave de la plataforma del dispositivo. ¿Existe algún otro método para hacer lo mismo si tengo privilegios de sistema?
Yeung
Hay una forma de utilizar la "API de estadísticas de uso" introducida el 21
KunalK
38

Tuve que encontrar la solución correcta por las malas. el siguiente código es parte de cyanogenmod7 (los ajustes de la tableta) y se prueba en Android 2.3.3 / gingerbread.

métodos:

  • getForegroundApp: devuelve la aplicación en primer plano.
  • getActivityForApp: devuelve la actividad de la aplicación encontrada.
  • isStillActive: determina si una aplicación encontrada anteriormente sigue siendo la aplicación activa.
  • isRunningService: una función auxiliar para getForegroundApp

con suerte, esto responde a este problema en toda extensión (:

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;
}
Sven Dawitz
fuente
1
si no le importa que le pregunte, ¿por qué necesita los métodos para ver si está ejecutando un servicio, si todavía está activo y para obtener actividad solo para obtener la aplicación en primer plano?
2
lo siento, pero no funciona. Algunas aplicaciones se filtran sin una razón, creo que es cuando ejecutan algún servicio. Así que isRunningService los está echando.
seb
15
Desafortunadamente, getRunningTasks () ha quedado obsoleto desde Android L (API 20). A partir de L, este método ya no está disponible para aplicaciones de terceros: la introducción de recientes centrados en documentos significa que puede filtrar información personal a la persona que llama. Para compatibilidad con versiones anteriores, todavía devolverá un pequeño subconjunto de sus datos: al menos las propias tareas de la persona que llama y posiblemente algunas otras tareas, como el hogar, que se sabe que no son confidenciales.
Sam Lu
¿Alguien sabe cómo este enfoque es mejor que simplemente hacer mActivityManager.getRunningTasks(1).get(0).topActivity?
Sam
35

Prueba el siguiente código:

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

El nombre del proceso es el nombre del paquete de la aplicación que se ejecuta en primer plano. Compárelo con el nombre del paquete de su aplicación. Si es el mismo, entonces su aplicación se está ejecutando en primer plano.

Espero que esto responda tu pregunta.

Friedrich Bhaer
fuente
7
A partir de la versión L de Android, esta es la única solución que funciona, ya que los métodos utilizados en las otras soluciones han quedado obsoletos.
androidGuy
4
sin embargo, esto no funciona en el caso de que la aplicación está en primer plano y pantalla de bloqueo es
Krit
Se debe
marcar la
1
¿Necesita permiso adicional como respuesta aceptada?
wonsuc
1
Esto no es del todo correcto. El nombre de un proceso no siempre es el mismo que el nombre del paquete de la aplicación correspondiente.
Sam
25

Desde piruleta en adelante, esto cambió. Encuentre el código a continuación, antes de que el usuario tenga que ir a Configuración -> Seguridad -> (Desplácese hacia abajo hasta el último) Aplicaciones con acceso de uso -> Otorgue los permisos a nuestra aplicación

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);
}
Suman
fuente
1
¿No se pueden establecer estas estadísticas de uso mediante programación?
rubmz
@rubmz ¿Tienes alguna solución para esto?
VVB
1
en mi doogee con Android 5.1 no hay opción "Dar los permisos a nuestra aplicación"
djdance
Esto no produce resultados 100% precisos ... es correcto la mayor parte del tiempo
CamHart
1
Esto no mostrará la última aplicación en primer plano, pero tal vez la última "usada" de alguna manera: si dejas una actividad y vas a otra, abre el menú de inicio desde arriba y suéltalo, estarás en el " actividad incorrecta "hasta que pase a otra. No importa si se desplaza por la pantalla o no, informa la actividad incorrecta hasta que inicie otra.
Azurlake
9

Para los casos en los que necesitamos verificar desde nuestro propio servicio / hilo de fondo si nuestra aplicación está en primer plano o no. Así es como lo implementé y funciona bien para mí:

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
}

Ahora para verificar desde otras clases, si la aplicación está en primer plano o no, simplemente llame a:

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

Actualización (como señaló SHS ):

No olvide registrarse para las devoluciones de llamada en el onCreatemétodo de su clase de aplicación .

@Override
public void onCreate() {
    ...
    registerActivityLifecycleCallbacks(this);
}
Sarthak Mittal
fuente
2
Eso es lo que he estado buscando desde las últimas dos horas. ¡Gracias! :)
waseefakhtar
1
Necesitamos llamar a "registerActivityLifecycleCallbacks (this);" dentro del método Override "onCreate ()" de la clase Application (TestApplication). Esto iniciará las devoluciones de llamada en nuestra clase de aplicación.
SHS
@SarthakMittal, si un dispositivo entra en modo de espera o cuando lo bloqueamos, se llamará a onActivityStopped (). Según su código, esto indicará que la aplicación está en segundo plano. Pero en realidad está en primer plano.
Ayaz Alifov
@AyazAlifov Si el teléfono está en espera o el teléfono está bloqueado, no lo consideraré en primer plano, pero nuevamente depende de la preferencia.
Sarthak Mittal
8

Para determinar la aplicación de primer plano, puede usar para detectar la aplicación de primer plano, puede usar https://github.com/ricvalerio/foregroundappchecker . Utiliza diferentes métodos según la versión de Android del dispositivo.

En cuanto al servicio, el repositorio también proporciona el código que necesita para ello. Esencialmente, deje que Android Studio cree el servicio por usted y luego onCreate agregue el fragmento que usa appChecker. Sin embargo, necesitará solicitar permiso.

rvalerio
fuente
¡¡Perfecto!! probado en Android 7
Pratik Tank
Muchas gracias. Esta es la única respuesta que resolvió el problema al que nos enfrentábamos con las notificaciones de aplicaciones. Cuando aparece una notificación, todas las demás respuestas devolverán el nombre del paquete de la aplicación que envió la notificación. Su LollipopDetector resolvió este problema. Una pista: a partir de la API 29, MOVE_TO_FOREGROUND está obsoleto en UsageEvents.Event, por lo que desde Android Q en adelante, se debe usar ACTIVITY_RESUMED. ¡Entonces funcionará a las mil maravillas!
Jorn Rigter
8

Teniendo en cuenta que getRunningTasks()está obsoleto y getRunningAppProcesses()no es confiable, tomé la decisión de combinar 2 enfoques mencionados en 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();
        }
    }
Ayaz Alifov
fuente
4
debe mencionar para agregar al manifiesto el permiso obsoleto <uses-allow android: name = "android.permission.GET_TASKS" /> de lo contrario, la aplicación se bloqueará
RJFares
1
android.permission.GET_TASKS - actualmente está prohibido en Play Store
Duna
6

El ActivityManager clase es la herramienta adecuada para ver qué procesos se están ejecutando.

Para ejecutar en segundo plano, normalmente desea utilizar un servicio .

Charles Duffy
fuente
1

Esto funcionó para mí. Pero solo da el nombre del menú principal. Es decir, si el usuario ha abierto Configuración -> Bluetooth -> Pantalla de nombre del dispositivo, RunningAppProcessInfo lo llama simplemente Configuración. No se puede profundizar más

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());
                    }               
                }
NakulSargur
fuente
4
Creo que esto solo funciona si su propia aplicación se está ejecutando en primer plano. No informa nada cuando alguna otra aplicación está en primer plano.
Alice Van Der Land
0

Haz algo como esto:

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;
}
Cristianos
fuente
Google probablemente rechazará una aplicación que use ActivityManager.getRunningTasks (). La documentación indica que es solo para propósitos de desarrollo de enemigos: developer.android.com/reference/android/app/… Vea el comentario
inicial
3
@ForceMagic: Google no rechaza aplicaciones simplemente por usar API no confiables ; Además, Google no es el único canal de distribución de aplicaciones de Android. Dicho esto, vale la pena tener en cuenta que la información de esta API no es confiable.
Chris Stratton
@ChrisStratton Interesante, pero probablemente tengas razón, aunque se desaconseja usarlo para la lógica central, no rechazarán la aplicación. Es posible que desee advertir a Sky Kelsey desde el enlace de comentarios.
ForceMagic
3
"getRunningTasks" funciona para API 21 y posteriores, por lo que podría ser una buena idea encontrar otra forma de hacerlo funcionar para LOLLIPOP (aunque yo mismo no lo he descubierto :()
amIT
0

En piruleta y más:

Agregar a mainfest:

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

Y haz algo como esto:

if( mTaskId < 0 )
{
    List<AppTask> tasks = mActivityManager.getAppTasks(); 
    if( tasks.size() > 0 )
        mTaskId = tasks.get( 0 ).getTaskInfo().id;
}
rubmz
fuente
4
Está obsoleto en malvavisco
jinkal
0

Así es como verifico si mi aplicación está en primer plano. Tenga en cuenta que estoy usando AsyncTask como lo sugiere el Android oficial documentación

'

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

y ejecute AsyncTask así. new CheckIfForeground().execute();

Developine
fuente
¿Tienes un link para esto?
user1743524
0

Combiné dos soluciones en un método y me funciona para API 24 y API 21. Otras no las probé.

El código en Kotlin:

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

y en manifiesto

<!-- Check whether app in background or foreground -->
<uses-permission android:name="android.permission.GET_TASKS" />
Denshov
fuente