Android: ¿Cómo puedo obtener la actividad de primer plano actual (desde un servicio)?

¿Hay una forma nativa de Android para obtener una referencia de la actividad en ejecución de un servicio?

Tengo un servicio ejecutándose en segundo plano, y me gustaría actualizar mi Actividad actual cuando ocurre un evento (en el servicio). ¿Hay una manera fácil de hacer eso (como la que sugerí arriba)?

¿Hay una forma nativa de Android para obtener una referencia de la actividad en ejecución de un servicio?

Puede que no sea el propietario de la “Actividad en ejecución actualmente”.

Tengo un servicio ejecutándose en segundo plano, y me gustaría actualizar mi Actividad actual cuando ocurre un evento (en el servicio). ¿Hay una manera fácil de hacer eso (como la que sugerí arriba)?

  1. Enviar una Intent difusión a la actividad – aquí hay un proyecto de muestra que demuestra este patrón
  2. Haga que la actividad proporcione un PendingIntent (por ejemplo, a través de createPendingResult() ) que el servicio invoca
  3. Haga que la actividad registre un objeto de callback u objeto de escucha con el servicio a través de bindService() , y bindService() el servicio bindService() un método de evento en ese objeto de callback / receptor
  4. Enviar una transmisión ordenada de la Intent de la actividad, con un BroadcastReceiver baja prioridad como copia de seguridad (para generar una Notification si la actividad no está en pantalla) – aquí hay una publicación de blog con más información sobre este patrón

Esta es una buena forma de hacerlo usando el administrador de actividades. Básicamente obtienes las RunTasks del administrador de actividades. Siempre devolverá primero la tarea actualmente activa. Desde allí puedes obtener la actividad superior.

Ejemplo aquí

Hay una manera fácil de obtener una lista de tareas en ejecución desde el servicio ActivityManager. Puede solicitar un número máximo de tareas que se ejecutan en el teléfono y, de manera predeterminada, la tarea actualmente activa se devuelve primero.

Una vez que tenga eso, puede obtener un objeto ComponentName solicitando topActivity de su lista.

Aquí hay un ejemplo.

  ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE); List taskInfo = am.getRunningTasks(1); Log.d("topActivity", "CURRENT Activity ::" + taskInfo.get(0).topActivity.getClassName()); ComponentName componentInfo = taskInfo.get(0).topActivity; componentInfo.getPackageName(); 

Necesitará el siguiente permiso en su manifiesto:

  

Advertencia: infracción de Google Play

Google ha amenazado con eliminar aplicaciones de Play Store si usan servicios de accesibilidad para fines de no accesibilidad. Sin embargo, esto está siendo reconsiderado .


Use un servicio de AccessibilityService

  • Puede detectar la ventana actualmente activa usando un servicio de AccessibilityService .
  • En la onAccessibilityEvent llamada de onAccessibilityEvent , TYPE_WINDOW_STATE_CHANGED tipo de evento TYPE_WINDOW_STATE_CHANGED para determinar cuándo cambia la ventana actual.
  • Compruebe si la ventana es una actividad llamando a PackageManager.getActivityInfo() .

Beneficios

  • Probado y funcionando en Android 2.2 (API 8) a través de Android 7.1 (API 25).
  • No requiere sondeo.
  • No requiere el permiso GET_TASKS .

Desventajas

  • Cada usuario debe habilitar el servicio en la configuración de accesibilidad de Android.
  • El servicio siempre se está ejecutando.
  • Cuando un usuario intenta habilitar AccessibilityService , no puede presionar el botón Aceptar si una aplicación ha colocado una superposición en la pantalla. Algunas aplicaciones que lo hacen son Velis Auto Brightness y Lux. Esto puede ser confuso porque el usuario puede no saber por qué no puede presionar el botón o cómo solucionarlo.
  • AccessibilityService no conocerá la actividad actual hasta el primer cambio de actividad.

Ejemplo

Servicio

 public class WindowChangeDetectingService 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(AccessibilityEvent event) { if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { if (event.getPackageName() != null && event.getClassName() != null) { ComponentName componentName = new ComponentName( event.getPackageName().toString(), event.getClassName().toString() ); ActivityInfo activityInfo = tryGetActivity(componentName); boolean isActivity = activityInfo != null; if (isActivity) Log.i("CurrentActivity", componentName.flattenToShortString()); } } } private ActivityInfo tryGetActivity(ComponentName componentName) { try { return getPackageManager().getActivityInfo(componentName, 0); } catch (PackageManager.NameNotFoundException e) { return null; } } @Override public void onInterrupt() {} } 

AndroidManifest.xml

Combina esto en tu manifiesto:

         

Información de servicio

Pon esto en res/xml/accessibilityservice.xml :

    

Habilitando el Servicio

Cada usuario de la aplicación deberá habilitar explícitamente AccessibilityService para que se pueda utilizar. Vea esta respuesta de StackOverflow para saber cómo hacer esto.

Tenga en cuenta que el usuario no podrá presionar el botón OK cuando intente habilitar el servicio de accesibilidad si una aplicación ha colocado una superposición en la pantalla, como Velis Auto Brillo o Lux.

Se puede hacer por:

  1. Implemente su propia clase de aplicación, regístrese en ActivityLifecycleCallbacks, de esta manera usted puede ver lo que sucede con nuestra aplicación. En cada reanudación, la callback asigna la actividad visible actual en la pantalla y en pausa retira la asignación. Utiliza el método registerActivityLifecycleCallbacks() que se agregó en API 14.

     public class App extends Application { private Activity activeActivity; @Override public void onCreate() { super.onCreate(); setupActivityListener(); } private void setupActivityListener() { registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { } @Override public void onActivityStarted(Activity activity) { } @Override public void onActivityResumed(Activity activity) { activeActivity = activity; } @Override public void onActivityPaused(Activity activity) { activeActivity = null; } @Override public void onActivityStopped(Activity activity) { } @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) { } @Override public void onActivityDestroyed(Activity activity) { } }); } public Activity getActiveActivity(){ return activeActivity; } } 
  2. En su servicio, llame a getApplication() y getApplication() al nombre de su clase de aplicación (aplicación en este caso). app.getActiveActivity() puedes llamar a app.getActiveActivity() , que te dará una Actividad visible actual (o null cuando no esté visible ninguna actividad). Puede obtener el nombre de la Actividad llamando a activeActivity.getClass().getSimpleName()

Usar ActivityManager

Si solo desea conocer la aplicación que contiene la actividad actual, puede hacerlo utilizando ActivityManager . La técnica que puede usar depende de la versión de Android:

  • Pre-Lollipop: ActivityManager.getRunningTasks ( ejemplo )
  • Lollipop: ActivityManager.getRunningAppProcesses ( ejemplo )

Beneficios

  • Debería funcionar en todas las versiones de Android hasta la fecha.

Desventajas

  • No funciona en Android M (basado en pruebas con el emulador de prelanzamiento)
  • La documentación de estas API dice que solo están destinadas a la depuración y a las interfaces de usuario de administración.
  • Si desea actualizaciones en tiempo real, debe usar sondeo.
  • Se basa en una API oculta: ActivityManager.RunningAppProcessInfo.processState
  • Esta implementación no recoge la actividad de cambio de aplicación.

Ejemplo (basado en el código de KNaito )

 public class CurrentApplicationPackageRetriever { private final Context context; public CurrentApplicationPackageRetriever(Context context) { this.context = context; } public String[] get() { if (Build.VERSION.SDK_INT < 21) return getPreLollipop(); else return getLollipop(); } private String[] getPreLollipop() { @SuppressWarnings("deprecation") List tasks = activityManager().getRunningTasks(1); ActivityManager.RunningTaskInfo currentTask = tasks.get(0); ComponentName currentActivity = currentTask.topActivity; return new String[] { currentActivity.getPackageName() }; } private String[] getLollipop() { final int PROCESS_STATE_TOP = 2; try { Field processStateField = ActivityManager.RunningAppProcessInfo.class.getDeclaredField("processState"); List processes = activityManager().getRunningAppProcesses(); for (ActivityManager.RunningAppProcessInfo process : processes) { if ( // Filters out most non-activity processes process.importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND && // Filters out processes that are just being // _used_ by the process with the activity process.importanceReasonCode == 0 ) { int state = processStateField.getInt(process); if (state == PROCESS_STATE_TOP) /* If multiple candidate processes can get here, it's most likely that apps are being switched. The first one provided by the OS seems to be the one being switched to, so we stop here. */ return process.pkgList; } } } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException(e); } return new String[] { }; } private ActivityManager activityManager() { return (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); } } 

Manifiesto

Agregue el permiso GET_TASKS a AndroidManifest.xml :

   

No pude encontrar una solución con la que nuestro equipo estaría satisfecho, así que rodó la nuestra. Usamos ActivityLifecycleCallbacks para realizar un seguimiento de la actividad actual y luego exponerla a través de un servicio:

 public interface ContextProvider { Context getActivityContext(); } public class MyApplication extends Application implements ContextProvider { private Activity currentActivity; @Override public Context getActivityContext() { return currentActivity; } @Override public void onCreate() { super.onCreate(); registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { MyApplication.this.currentActivity = activity; } @Override public void onActivityStarted(Activity activity) { MyApplication.this.currentActivity = activity; } @Override public void onActivityResumed(Activity activity) { MyApplication.this.currentActivity = activity; } @Override public void onActivityPaused(Activity activity) { MyApplication.this.currentActivity = null; } @Override public void onActivityStopped(Activity activity) { // don't clear current activity because activity may get stopped after // the new activity is resumed } @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) { } @Override public void onActivityDestroyed(Activity activity) { // don't clear current activity because activity may get destroyed after // the new activity is resumed } }); } } 

A continuación, configure su contenedor DI para devolver la instancia de MyApplication para ContextProvider , por ejemplo

 public class ApplicationModule extends AbstractModule { @Provides ContextProvider provideMainActivity() { return MyApplication.getCurrent(); } } 

(Tenga en cuenta que la implementación de getCurrent() se omite en el código anterior. Es solo una variable estática que se establece desde el constructor de la aplicación)

Estoy usando esto para mis pruebas. Sin embargo, es API> 19 y solo para las actividades de tu aplicación.

 @TargetApi(Build.VERSION_CODES.KITKAT) public static Activity getRunningActivity() { try { Class activityThreadClass = Class.forName("android.app.ActivityThread"); Object activityThread = activityThreadClass.getMethod("currentActivityThread") .invoke(null); Field activitiesField = activityThreadClass.getDeclaredField("mActivities"); activitiesField.setAccessible(true); ArrayMap activities = (ArrayMap) activitiesField.get(activityThread); for (Object activityRecord : activities.values()) { Class activityRecordClass = activityRecord.getClass(); Field pausedField = activityRecordClass.getDeclaredField("paused"); pausedField.setAccessible(true); if (!pausedField.getBoolean(activityRecord)) { Field activityField = activityRecordClass.getDeclaredField("activity"); activityField.setAccessible(true); return (Activity) activityField.get(activityRecord); } } } catch (Exception e) { throw new RuntimeException(e); } throw new RuntimeException("Didn't find the running activity"); } 

Aquí está mi respuesta que funciona bien …

Debería poder obtener la Actividad actual de esta manera … Si estructura su aplicación con unas pocas Actividades con muchos fragmentos y desea realizar un seguimiento de cuál es su Actividad actual, tomaría mucho trabajo. Mi senario fue: tengo una Actividad con múltiples Fragmentos. De modo que puedo hacer un seguimiento de la actividad actual a través del objeto de aplicación, que puede almacenar todo el estado actual de las variables globales.

Aquí hay una manera. Cuando inicia su actividad, almacena esa actividad por Application.setCurrentActivity (getIntent ()); Esta aplicación lo almacenará. En su clase de servicio, puede simplemente hacer como Intent currentIntent = Application.getCurrentActivity (); getApplication (). startActivity (currentIntent);

Use este código para API 21 o superior. Esto funciona y da mejores resultados en comparación con las otras respuestas, detecta perfectamente el proceso en primer plano.

 if (Build.VERSION.SDK_INT >= 21) { String currentApp = null; UsageStatsManager usm = (UsageStatsManager) this.getSystemService(Context.USAGE_STATS_SERVICE); long time = System.currentTimeMillis(); List applist = usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - 1000 * 1000, time); if (applist != null && applist.size() > 0) { SortedMap mySortedMap = new TreeMap(); for (UsageStats usageStats : applist) { mySortedMap.put(usageStats.getLastTimeUsed(), usageStats); } if (mySortedMap != null && !mySortedMap.isEmpty()) { currentApp = mySortedMap.get(mySortedMap.lastKey()).getPackageName(); } } 

No sé si es una respuesta estúpida, pero resolví este problema almacenando un marcador en preferencias compartidas cada vez que ingresé en Crear () de cualquier actividad, luego usé el valor de las preferencias de shered para descubrir cuál es la actividad de primer plano.

Recientemente me enteré de esto. Con apis como:

  • minSdkVersion 19
  • targetSdkVersion 26

    ActivityManager.getCurrentActivity (contexto)

Espero que esto sea de alguna utilidad.

    Intereting Posts