web-dev-qa-db-fra.com

Comment obtenir le contexte actuel de l'activité de premier plan dans Android?

Chaque fois que ma diffusion est exécutée, je souhaite afficher une alerte sur les activités de premier plan.

133
Deepali

Sachant que ActivityManager gère Activity, nous pouvons obtenir des informations à partir de ActivityManager. Nous obtenons l’activité courante de premier plan par

ActivityManager am = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
ComponentName cn = am.getRunningTasks(1).get(0).topActivity;

MISE À JOUR 2018/10/03
getRunningTasks () est DEPRECATED. voir les solutions ci-dessous.

Cette méthode était obsolète dans l'API de niveau 21 . À partir de Build.VERSION_CODES.Lollipop, cette méthode n'est plus disponible pour les applications tierces: l'introduction de recents centrés sur le document signifie qu'elle peut transmettre des informations personnelles à l'appelant. Pour assurer la compatibilité ascendante, il retournera toujours un petit sous-ensemble de ses données: au moins les tâches propres à l'appelant et éventuellement d'autres tâches, telles que la tâche home, dont on sait qu'elles ne sont pas sensibles.

31
KAlO2

(Remarque: Une API officielle a été ajoutée dans l'API 14: voir cette réponse https://stackoverflow.com/a/29786451/119733 )

NE PAS UTILISER Previous (waqas716) answer.

Vous aurez un problème de fuite de mémoire en raison de la référence statique à l'activité. Pour plus de détails, voir le lien suivant http://Android-developers.blogspot.fr/2009/01/avoiding-memory-leaks.html

Pour éviter cela, vous devez gérer les références aux activités . Ajoutez le nom de l'application dans le fichier manifeste:

<application
    Android:name=".MyApp"
    ....
 </application>

Votre classe d'application:

  public class MyApp extends Application {
        public void onCreate() {
              super.onCreate();
        }

        private Activity mCurrentActivity = null;
        public Activity getCurrentActivity(){
              return mCurrentActivity;
        }
        public void setCurrentActivity(Activity mCurrentActivity){
              this.mCurrentActivity = mCurrentActivity;
        }
  }

Créer une nouvelle activité:

public class MyBaseActivity extends Activity {
    protected MyApp mMyApp;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mMyApp = (MyApp)this.getApplicationContext();
    }
    protected void onResume() {
        super.onResume();
        mMyApp.setCurrentActivity(this);
    }
    protected void onPause() {
        clearReferences();
        super.onPause();
    }
    protected void onDestroy() {        
        clearReferences();
        super.onDestroy();
    }

    private void clearReferences(){
        Activity currActivity = mMyApp.getCurrentActivity();
        if (this.equals(currActivity))
            mMyApp.setCurrentActivity(null);
    }
}

Donc, maintenant, au lieu d’étendre la classe d’activité pour vos activités, il suffit d’allonger MyBaseActivity. Maintenant, vous pouvez obtenir votre activité actuelle à partir d'une application ou d'un contexte d'activité comme ça:

Activity currentActivity = ((MyApp)context.getApplicationContext()).getCurrentActivity();
185
gezdy

J'étends au sommet de la réponse de @ gezdy.

Dans toutes les activités, au lieu de devoir "s'enregistrer" avec Application avec codage manuel, nous pouvons utiliser l'API suivante depuis le niveau 14 pour nous aider à atteindre le même objectif avec moins de codage manuel.

public void registerActivityLifecycleCallbacks (Application.ActivityLifecycleCallbacks callback)

http://developer.Android.com/reference/Android/app/Application.html#registerActivityLifecycleCallbacks%28Android.app.Application.ActivityLifecycleCallbacks%29

Dans Application.ActivityLifecycleCallbacks, vous pouvez savoir quelle Activity est "attachée" ou "détachée" à cette Application.

Cependant, cette technique n'est disponible que depuis l'API de niveau 14.

57
Cheok Yan Cheng

Update 2: une API officielle a été ajoutée à cet effet. Veuillez utiliser ActivityLifecycleCallbacks à la place.

METTRE À JOUR:

Comme l'a souligné @gezdy, et j'en suis reconnaissant. définit la référence sur null aussi pour l'activité en cours, au lieu de mettre à jour chaque onResume, elle est définie sur null pour chaque activité onDestroy afin d'éviter un problème de fuite de mémoire.

Il y a quelque temps, j'avais besoin de la même fonctionnalité et voici la méthode pour y parvenir ... Dans toutes vos activités, remplacez ces méthodes de cycle de vie.

@Override
protected void onResume() {
    super.onResume();
    appConstantsObj.setCurrentActivity(this);

}

@Override
protected void onPause() {
   clearReferences();
   super.onPause();
}

@Override
protected void onDestroy() {        
   clearReferences();
   super.onDestroy();
}

private void clearReferences(){
          Activity currActivity = appConstantsObj.getCurrentActivity();
          if (this.equals(currActivity))
                appConstantsObj.setCurrentActivity(null);
}

Désormais, dans votre classe de diffusion, vous pouvez accéder à l'activité en cours pour y afficher une alerte.

50
Waqas

@lockwobr Merci pour la mise à jour

Cela ne fonctionne pas 100% du temps dans la version 16 de l’API, si vous lisez le fichier code sur github la fonction "currentActivityThread" a été modifiée dans KitKat, donc je veux dire la version 19ish, un peu difficile à égaler api version à libérer dans github.

Avoir accès à la version actuelle Activity est très pratique. Ne serait-il pas agréable d’avoir une méthode statique getActivity renvoyant l’activité en cours sans questions inutiles?

La classe Activity est très utile. Il donne accès au fil d’interface utilisateur de l’application, aux vues, aux ressources, etc. De nombreuses méthodes nécessitent un Context, mais comment obtenir le pointeur? Voici quelques façons:

  • Suivi de l’état de l’application à l’aide de méthodes de cycle de vie surchargées . Vous devez stocker l’activité en cours dans une variable statique et vous devez disposer d’un accès au code de toutes les activités.
  • Suivi de l’état de l’application à l’aide de Instrumentation. Déclarez Instrumentation dans le manifeste, implémentez-le et utilisez ses méthodes pour Suivre les changements d'activité. Passer un pointeur d'activité aux méthodes et aux classes Utilisées dans vos activités. Injecter le pointeur en utilisant l’une des bibliothèques d’injection de code Toutes ces approches sont plutôt gênantes; heureusement, il existe un moyen beaucoup plus facile d'obtenir l'activité courante.
  • On dirait que le système doit avoir accès à toutes les activités sans les problèmes Mentionnés ci-dessus. Il est donc fort probable qu’il existe un moyen d’obtenir Des activités utilisant uniquement des appels statiques. J'ai passé beaucoup de temps à creuser Dans les sources Android de grepcode.com, et j'ai trouvé ce que je cherchais Il existe une classe appelée ActivityThread. Cette classe dispose d’un accès À toutes les activités et, mieux encore, d’une méthode statique Pour obtenir le ActivityThread actuel. Il n'y a qu'un seul petit problème La liste d'activités a un accès au package. 

Facile à résoudre en utilisant la réflexion:

public static Activity getActivity() {
    Class activityThreadClass = Class.forName("Android.app.ActivityThread");
    Object activityThread = activityThreadClass.getMethod("currentActivityThread").invoke(null);
    Field activitiesField = activityThreadClass.getDeclaredField("mActivities");
    activitiesField.setAccessible(true);

    Map<Object, Object> activities = (Map<Object, Object>) activitiesField.get(activityThread);
    if (activities == null)
        return null;

    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);
            Activity activity = (Activity) activityField.get(activityRecord);
            return activity;
        }
    }

    return null;
}

Une telle méthode peut être utilisée n’importe où dans l’application, ce qui est beaucoup plus pratique que toutes les approches mentionnées. De plus, il semble que ce n’est pas aussi dangereux qu’il le semble. Il n'introduit pas de nouvelles fuites potentielles ni de pointeurs nuls.

L'extrait de code ci-dessus manque de gestion des exceptions et suppose naïvement que la première activité en cours d'exécution est celle que nous recherchons. Vous voudrez peut-être ajouter des vérifications supplémentaires.

Article de blog

43
AZ_

getCurrentActivity () est également dans ReactContextBaseJavaModule.
(Depuis que cette question a été posée à l’origine, de nombreuses applications Android possèdent également un composant ReactNative - application hybride.)

la classe ReactContext dans ReactNative contient l'ensemble de la logique permettant de conserver mCurrentActivity, qui est renvoyée dans getCurrentActivity ().

Remarque: je souhaite que getCurrentActivity () soit implémenté dans la classe d'applications Android.

5
朱梅寧

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 ensuite via un service. Plus de détails ici: https://stackoverflow.com/a/38650587/10793

3
Muxa

Personnellement, j'ai fait ce que "Cheok Yan Cheng" avait dit, mais j'ai utilisé une "liste" pour avoir un "backstack" de toutes mes activités.

Si vous voulez vérifier quelle est l'activité actuelle, il vous suffit d'obtenir le dernier cours d'activité de la liste.

Créez une application qui étend "Application" et procédez comme suit:

public class MyApplication extends Application implements Application.ActivityLifecycleCallbacks,
EndSyncReceiver.IEndSyncCallback {

private List<Class> mActivitiesBackStack;
private EndSyncReceiver mReceiver;
    private Merlin mMerlin;
    private boolean isMerlinBound;
    private boolean isReceiverRegistered;

@Override
    public void onCreate() {
        super.onCreate();
        [....]
RealmHelper.initInstance();
        initMyMerlin();
        bindMerlin();
        initEndSyncReceiver();
        mActivitiesBackStack = new ArrayList<>();
    }

/* START Override ActivityLifecycleCallbacks Methods */
    @Override
    public void onActivityCreated(Activity activity, Bundle bundle) {
        mActivitiesBackStack.add(activity.getClass());
    }

    @Override
    public void onActivityStarted(Activity activity) {
        if(!isMerlinBound){
            bindMerlin();
        }
        if(!isReceiverRegistered){
            registerEndSyncReceiver();
        }
    }

    @Override
    public void onActivityResumed(Activity activity) {

    }

    @Override
    public void onActivityPaused(Activity activity) {

    }

    @Override
    public void onActivityStopped(Activity activity) {
        if(!AppUtils.isAppOnForeground(this)){
            if(isMerlinBound) {
                unbindMerlin();
            }
            if(isReceiverRegistered){
                unregisterReceiver(mReceiver);
            }
            if(RealmHelper.getInstance() != null){
                RealmHelper.getInstance().close();
                RealmHelper.getInstance().logRealmInstanceCount("AppInBackground");
                RealmHelper.setMyInstance(null);
            }
        }
    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {

    }

    @Override
    public void onActivityDestroyed(Activity activity) {
        if(mActivitiesBackStack.contains(activity.getClass())){
            mActivitiesBackStack.remove(activity.getClass());
        }
    }
    /* END Override ActivityLifecycleCallbacks Methods */

/* START Override IEndSyncCallback Methods */
    @Override
    public void onEndSync(Intent intent) {
        Constants.SyncType syncType = null;
        if(intent.hasExtra(Constants.INTENT_DATA_SYNC_TYPE)){
            syncType = (Constants.SyncType) intent.getSerializableExtra(Constants.INTENT_DATA_SYNC_TYPE);
        }
        if(syncType != null){
            checkSyncType(syncType);
        }
    }
    /* END IEndSyncCallback Methods */

private void checkSyncType(Constants.SyncType){
    [...]
    if( mActivitiesBackStack.contains(ActivityClass.class) ){
         doOperation()     }
}

}

Dans mon cas, j'ai utilisé "Application.ActivityLifecycleCallbacks" pour:

  • Bind/Unbind Merlin Instance (utilisé pour obtenir un événement lorsque l'application perd ou obtient une connexion, par exemple lorsque vous fermez des données mobiles ou lorsque vous les ouvrez). Il est utile après la désactivation de l'action d'intention "OnConnectivityChanged" . Pour plus d'informations sur MERLIN, voir: MERLIN INFO LINK

  • Fermez ma dernière instance de royaume à la fermeture de l'application. Je l'initierai dans une BaseActivity qui est étendue à toutes les autres activités et qui a une instance privée de RealmHelper. Pour plus d'informations sur REALM, voir: REALM INFO LINK Par exemple, j'ai une instance statique "RealmHelper" dans ma classe "RealmHelper" qui est instanciée dans mon application "onCreate". J'ai un service de synchronisation dans lequel je crée un nouveau "RealmHelper" car Realm est "lié au thread" et une instance de realm ne peut pas fonctionner dans un autre thread .. Pour pouvoir suivre la documentation du realm, vous devez fermer Toutes les instances du domaine ouvert pour éviter les fuites des ressources système ", pour ce faire, j'ai utilisé" Application.ActivityLifecycleCallbacks "comme vous pouvez le voir. 

  • Enfin, j'ai un récepteur qui est déclenché quand je termine la synchronisation de mon application, puis quand la fin de la synchronisation est appelée, elle appellera la méthode "IEndSyncCallback" "onEndSync" dans laquelle je regarde si j'ai une classe d'activité spécifique dans ma liste ActivitiesBackStack parce que j'ai besoin de faire une opération si j'ai déjà passé ce point dans mon application.

C'est tout, espérons que cela vous sera utile. A plus :)

0
Z3R0

Pour la compatibilité ascendante:

ComponentName cn;
ActivityManager am = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
if (Android.os.Build.VERSION.SDK_INT >= Android.os.Build.VERSION_CODES.M) {
    cn = am.getAppTasks().get(0).getTaskInfo().topActivity;
} else {
    //noinspection deprecation
    cn = am.getRunningTasks(1).get(0).topActivity;
}
0
Martin Zeitler

J'ai fait la suite à Kotlin

  1. Créer une classe d'application
  2. Modifier la classe d'application comme suit

    class FTApplication: MultiDexApplication() {
    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        MultiDex.install(this)
    }
    
    init {
        instance = this
    }
    
    val mFTActivityLifecycleCallbacks = FTActivityLifecycleCallbacks()
    
    override fun onCreate() {
        super.onCreate()
    
        registerActivityLifecycleCallbacks(mFTActivityLifecycleCallbacks)
    }
    
    companion object {
        private var instance: FTApplication? = null
    
        fun currentActivity(): Activity? {
    
            return instance!!.mFTActivityLifecycleCallbacks.currentActivity
        }
    }
    
     }
    
  3. Créez la classe ActivityLifecycleCallbacks 

    class FTActivityLifecycleCallbacks: Application.ActivityLifecycleCallbacks {
    
    var currentActivity: Activity? = null
    
    override fun onActivityPaused(activity: Activity?) {
        currentActivity = activity
    }
    
    override fun onActivityResumed(activity: Activity?) {
        currentActivity = activity
    }
    
    override fun onActivityStarted(activity: Activity?) {
        currentActivity = activity
    }
    
    override fun onActivityDestroyed(activity: Activity?) {
    }
    
    override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) {
    }
    
    override fun onActivityStopped(activity: Activity?) {
    }
    
    override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) {
        currentActivity = activity
    }
    
    }
    
  4. vous pouvez maintenant l'utiliser dans n'importe quelle classe en appelant le numéro suivant: FTApplication.currentActivity()

0
rashad.z