programacion - Android: ¿Cómo puedo obtener la actividad de primer plano actual(desde un servicio)?
manual android studio avanzado (11)
¿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)?
- Enviar una
Intent
difusión a la actividad - aquí hay un proyecto de muestra que demuestra este patrón - Haga que la actividad proporcione un
PendingIntent
(por ejemplo, a través decreatePendingResult()
) que el servicio invoca - Haga que la actividad registre un objeto de devolución de llamada u objeto de escucha con el servicio a través de
bindService()
, ybindService()
el serviciobindService()
un método de evento en ese objeto de devolución de llamada / receptor - Enviar una transmisión ordenada de la
Intent
de la actividad, con unBroadcastReceiver
baja prioridad como copia de seguridad (para generar unaNotification
si la actividad no está en pantalla) - aquí hay una publicación de blog con más información sobre este patrón
¿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)?
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 deonAccessibilityEvent
,TYPE_WINDOW_STATE_CHANGED
tipo de eventoTYPE_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:
<application>
<service
android:label="@string/accessibility_service_name"
android:name=".WindowChangeDetectingService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibilityservice"/>
</service>
</application>
Información de servicio
Pon esto en res/xml/accessibilityservice.xml
:
<?xml version="1.0" encoding="utf-8"?>
<!-- These options MUST be specified here in order for the events to be received on first
start in Android 4.1.1 -->
<accessibility-service
xmlns:tools="http://schemas.android.com/tools"
android:accessibilityEventTypes="typeWindowStateChanged"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagIncludeNotImportantViews"
android:description="@string/accessibility_service_description"
xmlns:android="http://schemas.android.com/apk/res/android"
tools:ignore="UnusedAttribute"/>
Habilitando el Servicio
Cada usuario de la aplicación deberá habilitar explícitamente AccessibilityService
para que se pueda utilizar. Vea esta respuesta de 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.
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
( example ) - Lollipop:
ActivityManager.getRunningAppProcesses
( example )
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)
- ActivityManager.getRunningTasks 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 example )
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<ActivityManager.RunningTaskInfo> 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<ActivityManager.RunningAppProcessInfo> 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
:
<!--suppress DeprecatedClassUsageInspection -->
<uses-permission android:name="android.permission.GET_TASKS" />
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);
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.
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<ActivityManager.RunningTaskInfo> 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:
<uses-permission android:name="android.permission.GET_TASKS"/>
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");
}
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)
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.
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 devolución de llamada 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()
ygetApplication()
al nombre de su clase de aplicación (aplicación en este caso).app.getActiveActivity()
puedes llamar aapp.getActiveActivity()
, que te dará una Actividad visible actual (o null cuando no esté visible ninguna actividad). Puede obtener el nombre de la Actividad llamando aactiveActivity.getClass().getSimpleName()
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<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();
}
}