¿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)?
Intent
difusión a la actividad – aquí hay un proyecto de muestra que demuestra este patrón PendingIntent
(por ejemplo, a través de createPendingResult()
) que el servicio invoca bindService()
, y bindService()
el servicio bindService()
un método de evento en ese objeto de callback / receptor 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:
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 .
AccessibilityService
AccessibilityService
. onAccessibilityEvent
llamada de onAccessibilityEvent
, TYPE_WINDOW_STATE_CHANGED
tipo de evento TYPE_WINDOW_STATE_CHANGED
para determinar cuándo cambia la ventana actual. PackageManager.getActivityInfo()
. GET_TASKS
. 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. 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() {} }
Combina esto en tu manifiesto:
Pon esto en res/xml/accessibilityservice.xml
:
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:
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; } }
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()
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:
ActivityManager.getRunningTasks
( ejemplo ) ActivityManager.getRunningAppProcesses
( ejemplo ) ActivityManager.RunningAppProcessInfo.processState
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); } }
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:
targetSdkVersion 26
ActivityManager.getCurrentActivity (contexto)
Espero que esto sea de alguna utilidad.