Existe-t-il un moyen Android natif d'obtenir une référence à l'activité en cours d'exécution à partir d'un service?
J'ai un service en tâche de fond et je souhaite mettre à jour mon activité actuelle lorsqu'un événement se produit (dans le service). Y a-t-il un moyen facile de faire cela (comme celui que j'ai suggéré ci-dessus)?
Existe-t-il un moyen Android natif d'obtenir une référence à l'activité en cours d'exécution à partir d'un service?
Vous ne pouvez pas posséder "l'activité en cours".
J'ai un service en tâche de fond et je souhaite mettre à jour mon activité actuelle lorsqu'un événement se produit (dans le service). Y a-t-il un moyen facile de faire cela (comme celui que j'ai suggéré ci-dessus)?
Intent
à l'activité - voici un exemple de projet démontrant ce modèlePendingIntent
(par exemple, via createPendingResult()
) que le service appellebindService()
et demandez au service d'appeler une méthode d'événement sur cet objet callback/listener.Intent
à l'activité, avec une BroadcastReceiver
de priorité basse en tant que sauvegarde (pour générer une Notification
si l'activité n'est pas à l'écran) - voici un article de blog avec plus d'informations sur ce modèleVoici un bon moyen de le faire en utilisant le gestionnaire d'activités . Vous obtenez essentiellement les tâches en cours d'exécution du gestionnaire d'activités. Il retournera toujours d'abord la tâche actuellement active. De là, vous pouvez obtenir le topActivity.
Il existe un moyen simple d'obtenir une liste des tâches en cours d'exécution à partir du service ActivityManager . Vous pouvez demander un nombre maximal de tâches exécutées sur le téléphone. Par défaut, la tâche actuellement active est renvoyée en premier.
Une fois que vous avez cela, vous pouvez obtenir un objet ComponentName en demandant topActivity à votre liste.
Voici un exemple.
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();
Vous aurez besoin de l'autorisation suivante sur votre manifeste:
<uses-permission Android:name="Android.permission.GET_TASKS"/>
Google a menacé de supprimer des applications du Play Store s’ils utilisent des services d’accessibilité à des fins autres que l’accessibilité. Cependant, ceci serait en train d'être reconsidéré .
AccessibilityService
AccessibilityService
.onAccessibilityEvent
callback, recherchez le type d'événement TYPE_WINDOW_STATE_CHANGED
pour déterminer quand la fenêtre en cours change.PackageManager.getActivityInfo()
.GET_TASKS
.AccessibilityService
, il ne peut pas appuyer sur le bouton OK si une application a placé une superposition à l'écran. Velis Auto Brightness et Lux sont quelques-unes de ces applications. Cela peut être déroutant, car l'utilisateur peut ne pas savoir pourquoi il ne peut pas appuyer sur le bouton ou comment le contourner. AccessibilityService
ne saura pas l'activité en cours avant le premier changement d'activité.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() {}
}
Fusionnez ceci dans votre manifeste:
<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>
Mettez ceci dans 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"/>
Chaque utilisateur de l'application devra activer explicitement le AccessibilityService
pour pouvoir l'utiliser. Voir cette réponse StackOverflow pour savoir comment faire cela.
Notez que l'utilisateur ne pourra pas appuyer sur le bouton OK lorsqu'il tentera d'activer le service d'accessibilité si une application a placé une superposition à l'écran, telle que Velis Auto Brightness ou Lux.
Cela peut être fait par:
Implémentez votre propre classe d'application, inscrivez-vous à ActivityLifecycleCallbacks - de cette façon, vous pourrez voir ce qui se passe avec notre application. À chaque reprise, le rappel affecte l'activité visible en cours à l'écran et, en pause, supprime l'affectation. Il utilise la méthode registerActivityLifecycleCallbacks()
qui a été ajoutée à l'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;
}
}
Dans votre service, appelez getApplication()
et transmettez-le au nom de la classe de votre application (App dans ce cas). Ensuite, vous pouvez appeler app.getActiveActivity()
- cela vous donnera une activité visible en cours (ou nulle si aucune activité n’est visible). Vous pouvez obtenir le nom de l'activité en appelant activeActivity.getClass().getSimpleName()
Je ne pouvais pas trouver une solution qui plairait à notre équipe et nous avons donc lancé la nôtre. Nous utilisons ActivityLifecycleCallbacks
pour suivre l’activité en cours et l’exposer via un service:
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
}
});
}
}
Configurez ensuite votre conteneur DI pour renvoyer l’instance de MyApplication
pour ContextProvider
, par exemple.
public class ApplicationModule extends AbstractModule {
@Provides
ContextProvider provideMainActivity() {
return MyApplication.getCurrent();
}
}
(Notez que l'implémentation de getCurrent()
est omise du code ci-dessus. Il s'agit simplement d'une variable statique définie à partir du constructeur de l'application.)
ActivityManager
Si vous voulez seulement connaître le application contenant l'activité en cours, vous pouvez le faire avec ActivityManager
. La technique que vous pouvez utiliser dépend de la version d'Android:
ActivityManager.getRunningTasks
( exemple )ActivityManager.getRunningAppProcesses
( exemple )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<ActivityManager.RunningTaskInfo> tasks =
activityManager().getRunningTasks(1);
ActivityManager.RunningTaskInfo currentTask = tasks.get(0);
ComponentName currentActivity = currentTask.topActivity;
return 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) {
String[] processNameParts = process.processName.split(":");
String packageName = processNameParts[0];
/*
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 packageName;
}
}
}
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
return null;
}
private ActivityManager activityManager() {
return (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
}
}
Ajoutez le GET_TASKS
permission à AndroidManifest.xml
:
<!--suppress DeprecatedClassUsageInspection -->
<uses-permission Android:name="Android.permission.GET_TASKS" />
J'utilise ceci pour mes tests. C'est API> 19, et seulement pour les activités de votre application, cependant.
@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");
}
Utilisez ce code pour l'API 21 ou supérieure. Cela fonctionne et donne un meilleur résultat par rapport aux autres réponses, il détecte parfaitement le processus de premier plan.
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();
}
}
Voici ma réponse qui fonctionne très bien ...
Vous devriez pouvoir obtenir l'activité actuelle de cette manière .......... Si vous structurez votre application avec quelques activités comportant de nombreux fragments et que vous souhaitez garder une trace de votre activité actuelle, il vous faudra beaucoup de travail. . Mon sénario était j'ai une activité avec plusieurs fragments. Je peux donc garder une trace de l'activité en cours via l'objet Application, qui peut stocker tout l'état actuel des variables globales.
Voici un moyen. Lorsque vous démarrez votre activité, vous enregistrez celle-ci par Application.setCurrentActivity (getIntent ()); Cette application va le stocker . Sur votre classe de service, vous pouvez simplement faire comme Intention currentIntent = Application.getCurrentActivity (); getApplication (). startActivity (currentIntent);
Je ne sais pas si c'est une réponse stupide, mais j'ai résolu ce problème en stockant un indicateur dans les préférences partagées chaque fois que je suis entré dans onCreate () de n'importe quelle activité. J'ai ensuite utilisé la valeur des préférences shered pour déterminer en quoi consiste l'activité de premier plan.