Avertissement: ne placez pas de classes de contexte Android dans des champs statiques; il s'agit d'une fuite de mémoire (et interrompt également Instant Run)

84

Studio Android:

Ne placez pas de classes de contexte Android dans des champs statiques; il s'agit d'une fuite de mémoire (et interrompt également Instant Run)

Donc 2 questions:

# 1 Comment appeler a à startServicepartir d'une méthode statique sans variable statique pour le contexte?
# 2 Comment envoyer un localBroadcast à partir d'une méthode statique (idem)?

Exemples:

public static void log(int iLogLevel, String sRequest, String sData) {
    if(iLogLevel > 0) {

        Intent intent = new Intent(mContext, LogService.class);
        intent.putExtra("UPDATE_MAIN_ACTIVITY_VIEW", "UPDATE_MAIN_ACTIVITY_VIEW");
        mContext.startService(intent);
    }
}

ou

        Intent intent = new Intent(MAIN_ACTIVITY_RECEIVER_INTENT);
        intent.putExtra(MAIN_ACTIVITY_REQUEST_FOR_UPDATE, sRequest));
        intent.putExtra(MAIN_ACTIVITY_DATA_FOR_VIEW, sData);
        intent.putExtra(MAIN_ACTIVITY_LOG_LEVEL, iLogLevel);
        LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);

Quelle serait la bonne façon de faire cela sans utiliser mContext?

REMARQUE: je pense que ma principale question pourrait être de savoir comment passer le contexte à une classe à partir de laquelle réside la méthode d'appel.

John Smith
la source
Ne pouvez-vous pas passer Context comme paramètre dans la méthode?
Juan Cruz Soler
J'appellerais cette routine dans des endroits qui n'auraient pas de contexte également.
John Smith
# 1 passez-le comme paramètre # 2 de la même manière.
njzk2
Ensuite, vous devez également transmettre le contexte à la méthode de l'appelant. Le problème est que les champs statiques ne sont pas ramassés, vous pourriez donc divulguer une activité avec toutes ses vues
Juan Cruz Soler
1
@JohnSmith Cascade depuis l'activité de lancement (via les paramètres du constructeur ou les paramètres de méthode) jusqu'au moment où vous en avez besoin.
AndroidMechanic - Viral Patel

Réponses:

56

Passez-le simplement comme paramètre à votre méthode. Il n'y a aucun sens à créer une instance statique de Contextuniquement dans le but de démarrer un Intent.

Voici à quoi devrait ressembler votre méthode:

public static void log(int iLogLevel, String sRequest, String sData, Context ctx) {
    if(iLogLevel > 0) {

        Intent intent = new Intent(ctx, LogService.class);
        intent1.putExtra("UPDATE_MAIN_ACTIVITY_VIEW", "UPDATE_MAIN_ACTIVITY_VIEW");
        ctx.startService(intent);
    }
}

Mise à jour à partir des commentaires sur la question: mettez en cascade le contexte de l'activité de lancement (via les paramètres du constructeur ou les paramètres de méthode) jusqu'au moment où vous en avez besoin.

AndroidMécanicien - Viral Patel
la source
Pouvez-vous fournir un exemple de constructeur?
John Smith
si votre nom de classe est MyClassajoutez un constructeur public comme une méthode public MyClass(Context ctx) { // put this ctx somewhere to use later }(Ceci est votre constructeur) Maintenant, créez une nouvelle instance d' MyClassutilisation de ce constructeur, par exempleMyClass mc = new MyClass(ctx);
AndroidMechanic - Viral Patel
Je ne pense pas que ce soit aussi simple de passer à la demande. Bien qu'il y ait des avantages apparents comme ne pas avoir à s'inquiéter d'un contexte périmé ou comme ici, d'un contexte statique. Disons que vous avez besoin de contexte [peut-être voulez-vous écrire dans prefs] dans un rappel de réponse qui sera appelé de manière asynchrone. Donc, parfois, vous êtes obligé de le mettre dans un champ membre. Et maintenant, vous devez penser à ne pas le rendre statique. stackoverflow.com/a/40235834/2695276 semble fonctionner.
Rajat Sharma
1
Est-il acceptable d'utiliser ApplicationContext comme champ statique? Contrairement aux activités, l'objet d'application n'est pas détruit, non?
NeoWang
50

Assurez-vous simplement de passer context.getApplicationContext () ou d'appeler getApplicationContext () sur n'importe quel contexte qui est passé via des méthodes / constructeur à votre singleton si vous décidez de le stocker dans n'importe quel champ membre.

Exemple de preuve idiot (même si quelqu'un passait une activité, il récupérera le contexte de l'application et l'utilisera pour instancier le singleton):

public static synchronized RestClient getInstance(Context context) {
    if (mInstance == null) {
        mInstance = new RestClient(context.getApplicationContext());
    }
    return mInstance;
}

getApplicationContext () selon la documentation: "Renvoie le contexte de l'objet Application global unique du processus en cours."

Cela signifie que le contexte retourné par "getApplicationContext ()" vivra tout au long du processus et donc peu importe si vous y stockez une référence statique n'importe où, car il sera toujours là pendant l'exécution de votre application (et survivra à tous les objets / singletons instancié par lui).

Comparez cela au contexte à l'intérieur des vues / activités contenant de grandes quantités de données, si vous perdez un contexte détenu par une activité, le système ne pourra pas libérer cette ressource qui n'est évidemment pas bonne.

Une référence à une activité par son contexte doit vivre le même cycle de vie que l'activité elle-même, sinon elle tiendra le contexte en otage provoquant une fuite de mémoire (ce qui est la raison de l'avertissement de charpie).

EDIT: Pour le gars qui dénigre l'exemple de la documentation ci-dessus, il y a même une section de commentaires dans le code sur ce que je viens d'écrire:

    // getApplicationContext() is key, it keeps you from leaking the
    // Activity or BroadcastReceiver if someone passes one in.
Marcus Gruneau
la source
8
au gars qui dénigre le gars qui a critiqué l'exemple ci-dessus: le point de ce fil est l'avertissement Lint en conflit avec le modèle recommandé par Google de créer un singleton.
Raphael C
7
Lire: "Ne placez pas les classes de contexte Android dans des champs statiques; il s'agit d'une fuite de mémoire (et interrompt également Instant Run)" Savez-vous ce que sont les classes de contexte? L'activité en fait partie, et vous ne devez pas stocker l'activité sous forme de champ statique, comme vous l'avez décrit vous-même (ou cela entraînera une fuite de mémoire). Cependant, vous pouvez stocker le contexte (tant qu'il s'agit du contexte de l'application) en tant que champ statique, car il survit à tout. (Et donc ignorer l'avertissement). Je suis sûr que nous pouvons être d'accord sur ce simple fait, non?
Marcus Gruneau
en tant que vétérinaire iOS, au cours de ma première semaine d'Android ... Des explications comme celle-ci m'aident à comprendre ce contexte absurde. Donc, cet avertissement de peluche (oh, comme je n'aime pas les avertissements) va traîner, mais votre réponse résout le vrai problème .
eric
@Marcus si votre classe enfant ne sait pas qui l'instancie avec quel contexte, alors c'est juste une mauvaise pratique de la stocker en tant que membre statique. de plus, le contexte d'application vit dans le cadre de l'objet Application de votre application, l'objet d'application ne restera pas en mémoire pour toujours, il sera tué. contrairement à la croyance populaire, l'application ne sera pas redémarrée à partir de zéro. Android créera un nouvel objet Application et démarrera l'activité là où l'utilisateur se trouvait auparavant pour donner l'illusion que l'application n'a jamais été tuée en premier lieu.
Raphael C
@RaphaelC avez-vous de la documentation à ce sujet? Cela semble totalement faux car Android n'assure qu'un seul contexte d'application par exécution de chaque processus.
HaydenKai
6

C'est juste un avertissement. Ne t'inquiète pas. Si vous souhaitez utiliser un contexte d'application, vous pouvez l'enregistrer dans une classe "singleton", qui est utilisée pour enregistrer toute la classe singleton de votre projet.

Licat Julius
la source
2

Dans votre cas, cela n'a pas beaucoup de sens de l'avoir comme champ statique mais je ne pense pas que ce soit mauvais dans tous les cas. Si vous faites maintenant ce que vous faites, vous pouvez avoir un champ statique qui a un contexte et l'annuler plus tard. Je crée une instance statique pour ma classe de modèle principale qui a un contexte à l'intérieur, son contexte d'application et non un contexte d'activité et j'ai également un champ d'instance statique de classe contenant une activité que je vide lors de la destruction. Je ne vois pas que j'ai une fuite de mémoire. Donc, si un gars intelligent pense que je me trompe, n'hésitez pas à commenter ...

Instant Run fonctionne également très bien ici ...

Renetik
la source
Je ne pense pas que vous vous trompiez sur le principe, mais vous devez faire très attention à ce que l'activité dont vous parlez ne comporte au maximum qu'une seule instance à un moment donné avant de pouvoir utiliser des champs statiques. Si votre application se retrouve avec plus d'une pile arrière car elle peut être lancée à partir de différents endroits (notification, lien profond, ...), les choses vont mal tourner à moins que vous n'utilisiez un indicateur comme singleInstance dans le manifeste. Il est donc toujours plus facile d'éviter les champs statiques des activités.
BladeCoder
android: launchMode = "singleTask" devrait être suffisant, donc je passe à cela, j'ai utilisé singleTop mais je ne savais pas que ce n'était pas assez parce que je ne veux toujours que des instances uniques de mes activités principales, c'est ainsi que mes applications sont conçues.
Renetik
2
"singleTask" ne garantit qu'une seule instance par tâche. Si votre application dispose de plusieurs points d'entrée tels que les liens profonds ou son lancement à partir d'une notification, vous pouvez vous retrouver avec plusieurs tâches.
BladeCoder
1

En général, évitez d'avoir des champs de contexte définis comme statiques. L'avertissement lui-même explique pourquoi: c'est une fuite de mémoire. La rupture de la course instantanée n'est peut-être pas le plus gros problème de la planète.

Maintenant, il y a deux scénarios où vous obtiendrez cet avertissement. Pour une instance (la plus évidente):

public static Context ctx;

Et puis il y a celui un peu plus délicat, où le contexte est enveloppé dans une classe:

public class Example{
    public Context ctx;
    //Constructor omitted for brievety 
}

Et cette classe est définie comme statique quelque part:

public static Example example;

Et vous recevrez l'avertissement.

La solution elle-même est assez simple: ne placez pas de champs de contexte dans des instances statiques , qu'il s'agisse d'une classe d'encapsulation ou de la déclarer directement statique.

Et la solution à l'avertissement est simple: ne placez pas le champ de manière statique. Dans votre cas, transmettez le contexte en tant qu'instance à la méthode. Pour les classes où plusieurs appels de contexte sont effectués, utilisez un constructeur pour transmettre le contexte (ou une activité d'ailleurs) à la classe.

Notez que c'est un avertissement, pas une erreur. Si, pour une raison quelconque , vous avez besoin d' un contexte statique, vous pouvez le faire. Bien que vous créiez une fuite de mémoire lorsque vous le faites.

Zoé
la source
comment pouvons-nous le faire sans créer une fuite de mémoire?
isJulian00
1
Vous ne pouvez pas. Si vous avez absolument besoin de passer des contextes, vous pouvez regarder dans un bus événementiel
Zoe
ok c'était le problème que j'avais si vous pouviez s'il vous plaît y jeter un coup d'œil, peut-être qu'il y a une autre façon de le faire, btw la méthode doit être statique parce que je l'appelle à partir du code c ++ stackoverflow.com/questions/54683863/...
isJulian00
0

Si vous vous assurez qu'il s'agit d'un contexte d'application. Cela compte. Ajoute ça

@SuppressLint("StaticFieldLeak")
Victor Choy
la source
1
Je ne recommanderais pas de le faire de toute façon. Si vous avez besoin de contexte, vous pouvez utiliser la méthode requireContext (), si vous utilisez les bibliothèques AndroidX. Ou vous pouvez transmettre le contexte directement à la méthode qui en a besoin. Ou vous pouvez même simplement obtenir la référence de classe de l'application, mais je recommande plutôt de ne pas utiliser une telle suggestion SuppressLint.
Oleksandr Nos
0

Utilisez WeakReferencepour stocker le contexte dans les classes Singleton et l'avertissement sera parti

private WeakReference<Context> context;

//Private contructor
private WidgetManager(Context context) {
    this.context = new WeakReference<>(context);
}

//Singleton
public static WidgetManager getInstance(Context context) {
    if (null == widgetManager) {
        widgetManager = new WidgetManager(context);
    }
    return widgetManager;
}

Vous pouvez maintenant accéder au contexte comme

  if (context.get() instanceof MainActivity) {
            ((MainActivity) context.get()).startActivityForResult(pickIntent, CODE_REQUEST_PICK_APPWIDGET);
        }
Hitesh Sahu
la source