Android: Comment puis-je obtenir l'activité de premier plan actuelle (à partir d'un service)?
Existe-t-il un moyen natif android pour obtenir une référence à L'activité en cours d'exécution à partir d'un service?
j'ai un service en cours d'exécution sur le fond, et je voudrais mettre à jour mon activité actuelle quand un événement se produit (dans le service). Est-il un moyen facile de le faire (comme je l'ai suggéré ci-dessus)?
11 réponses
y a-t-il un moyen natif android pour 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 d'exécution".
j'ai un service en cours d'exécution sur le fond, et je voudrais mettre à jour mon activité actuelle quand un événement se produit (dans le service). Est-il un moyen facile de le faire (comme je l'ai suggéré ci-dessus)?
- envoyer une émission
Intent
à l'activité -- voici un exemple de projet démontrant ce modèle - avoir l'activité de fournir un
PendingIntent
(par exemple, viacreatePendingResult()
) que le service invoque - ont l'activité enregistrer un objet callback ou listener avec le service via
bindService()
, et avoir le service appeler une méthode d'événement sur cet objet callback /listener - envoyer une émission ordonnée
Intent
à l'activité, avec une faible prioritéBroadcastReceiver
comme sauvegarde (pour soulever unNotification
si l'activité n'est pas à l'écran) -- voici un billet de blog avec plus sur ce modèle
Voici une bonne façon de le faire en utilisant le gestionnaire d'activité. Vous obtenez essentiellement les tâches de fonctionnement du gestionnaire d'activités. Il retournera toujours la tâche active en premier. De là, vous pouvez obtenir la topactivité.
il y a un moyen facile d'obtenir une liste des tâches d'exécution du service ActivityManager. Vous pouvez demander un nombre maximum de tâches exécutées sur le téléphone, et par défaut, le actuellement la tâche active est retournée en premier.
une fois que vous avez que vous pouvez obtenir un objet ComponentName en demandant la topactivité de 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 la permission suivante sur votre manifeste:
<uses-permission android:name="android.permission.GET_TASKS"/>
avertissement: Violation de Google Play
Google a menacé de supprimer les applications de Play Store si elles utilisent des services d'accessibilité à des fins de non-accessibilité. Cependant, ce serait l'objet d'un réexamen .
utiliser un AccessibilityService
- vous pouvez détecter la fenêtre actuellement active par utilisation d'un
AccessibilityService
. - dans le
onAccessibilityEvent
callback, vérifier pour leTYPE_WINDOW_STATE_CHANGED
type d'événement pour déterminer quand la fenêtre courante change. - vérifiez si la fenêtre est une activité en appelant
PackageManager.getActivityInfo()
.
prestations
- testé et travailler sur Android 2.2 (API 8) via Android 7.1 (API 25).
- ne nécessite pas de sondage.
- ne nécessite pas la permission
GET_TASKS
.
désavantages
- chaque utilisateur doit activer le service dans les paramètres D'accessibilité D'Android.
- le service est toujours en cours.
- Lorsqu'un utilisateur essaie d'activer la
AccessibilityService
, ils ne peuvent pas appuyer sur le bouton OK si une application a placé une superposition sur l'écran. Certaines applications qui font cela sont Velis Auto Brightness et Lux. Cela peut être déroutant parce que l'utilisateur pourrait ne pas savoir pourquoi ils ne peuvent pas appuyer sur le bouton ou la façon de travailler autour de lui. - le
AccessibilityService
ne connaîtra pas l'activité actuelle jusqu'au premier changement de activité.
exemple
Service
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
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>
Info Service
Mettre cela 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"/>
activer le Service
chaque utilisateur de l'application devra explicitement activer le AccessibilityService
pour son utilisation. Voir cette réponse à la question pour savoir comment procéder.
notez que l'utilisateur ne pourra pas appuyer sur le bouton OK pour activer le service d'accessibilité si une application a placé une superposition sur l'écran, comme Velis Auto Brightness ou Lux.
cela peut être fait par:
-
implémentez votre propre classe d'application, Enregistrez - vous pour ActivityLifecycleCallbacks-de cette façon vous pouvez voir ce qui se passe avec notre application. À chaque reprise le rappel affecte la activité visible sur l'écran et sur pause il supprime l'affectation. Il utilise la méthode
registerActivityLifecycleCallbacks()
qui a été ajoutée dans 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 appel de service
getApplication()
et le lancer à votre nom de classe app (App dans ce cas). Que vous pouvez appelerapp.getActiveActivity()
- qui vous donnera une activité visible courante (ou null quand aucune activité n'est visible). Vous pouvez obtenir le nom de l'Activité en appelantactiveActivity.getClass().getSimpleName()
Utilisation ActivityManager
si vous voulez seulement connaître le application contenant l'activité courante, vous pouvez le faire en utilisant ActivityManager
. La technique que vous pouvez utiliser dépend de la version D'Android:
- Pre-Lollipop:
ActivityManager.getRunningTasks
( exemple ) - Lollipop:
ActivityManager.getRunningAppProcesses
( exemple )
prestations
- devrait fonctionner dans toutes les versions Android à ce jour.
désavantages
- Ne fonctionne pas sous Android M (basé sur des tests à l'aide de la pré-version de l'émulateur)
- la documentation pour ces IPA dit qu'ils sont uniquement destinés au débogage et à la gestion des interfaces utilisateur.
- si vous voulez des mises à jour en temps réel, vous devez utiliser polling.
- S'appuie sur une API cachée:
ActivityManager.RunningAppProcessInfo.processState
- cette implémentation ne capte pas l'activité de commutateur d'application.
exemple (basé sur code 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<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);
}
}
manifeste
ajouter le GET_TASKS
permission de AndroidManifest.xml
:
<!--suppress DeprecatedClassUsageInspection -->
<uses-permission android:name="android.permission.GET_TASKS" />
Je n'ai pas pu trouver une solution avec laquelle notre équipe serait heureuse alors nous avons roulé notre propre. Nous utilisons ActivityLifecycleCallbacks
pour suivre l'activité actuelle et l'exposer ensuite à travers 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
}
});
}
}
puis configurer votre conteneur DI pour retourner l'instance de MyApplication
pour ContextProvider
, par exemple
public class ApplicationModule extends AbstractModule {
@Provides
ContextProvider provideMainActivity() {
return MyApplication.getCurrent();
}
}
(notez que la mise en œuvre de getCurrent()
est omise du code ci-dessus. C'est juste une variable statique qui est définie à partir de la application constructeur)
j'utilise ça 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");
}
Voici ma réponse qui fonctionne très bien...
Vous devriez être en mesure d'obtenir l'Activité actuelle de cette façon... Si vous structurez votre application avec quelques activités avec beaucoup de fragments et vous voulez garder une trace de ce qui est votre activité actuelle, il faudrait beaucoup de travail cependant. Mon senario était que j'avais une activité avec plusieurs Fragments. Donc je peux garder une trace de L'activité actuelle à travers L'Application Object, qui peut stocker tous les états actuels des variables globales.
Voici un. Lorsque vous commencez votre activité, vous stockez cette activité par Application.setCurrentActivity(getIntent)()); Cette Application va le stocker. Sur votre classe de service, vous pouvez simplement faire comme Intention actuelle = Application.obteniractivité actuelle(); getApplication().activité de départ (actuelle);
utilisez ce code pour API 21 ou plus. 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();
}
}
Je ne sais pas si c'est une réponse stupide, mais j'ai résolu ce problème en stockant un drapeau dans les préférences partagées chaque fois que j'entrais dans onCreate() de n'importe quelle activité, puis j'ai utilisé la valeur de shered preferences pour trouver ce que c'est l'activité de premier plan.
vient de l'apprendre. Avec les api comme:
- minSdkVersion 19
-
targetSdkVersion 26
ActivityManager.getactiviteencours(contexte)
l'Espoir c'est de toute utilisation.