J'ai un fragment (F1) avec une méthode publique comme celle-ci
public void asd() {
if (getActivity() == null) {
Log.d("yes","it is null");
}
}
et oui quand je l'appelle (de l'Activité), c'est nul ...
FragmentTransaction transaction1 = getSupportFragmentManager().beginTransaction();
F1 f1 = new F1();
transaction1.replace(R.id.upperPart, f1);
transaction1.commit();
f1.asd();
Ce doit être quelque chose que je fais très mal, mais je ne sais pas ce que c'est
commit
planifie la transaction, c’est-à-dire que cela ne se produit pas immédiatement, mais est planifié comme travail sur le thread principal la prochaine fois que celui-ci est prêt.
Je suggère d'ajouter un
onAttach(Activity activity)
méthode à votre Fragment
et mettre un point de rupture dessus et voir quand il est appelé par rapport à votre appel à asd()
. Vous verrez qu'il est appelé après la méthode où vous appelez asd()
exits. L'appel onAttach
est l'endroit où Fragment
est associé à son activité et à partir de ce point getActivity()
renverra une valeur non nulle (nb, il existe également un appel onDetach()
).
La meilleure solution consiste à conserver la référence d’activité lorsque l’on appelle OnAttach et à utiliser la référence d’activité chaque fois que nécessaire, par exemple.
@Override
public void onAttach(Context context) {
super.onAttach(activity);
mContext = context;
}
@Override
public void onDetach() {
super.onDetach();
mContext = null;
}
Cela s'est produit lorsque vous appelez getActivity()
dans un autre thread qui s'est terminé après la suppression du fragment. Le cas typique appelle getActivity()
(par exemple pour un Toast
) lorsqu'une demande HTTP est terminée (par exemple, dans onResponse
.
Pour éviter cela, vous pouvez définir un nom de champ mActivity
et l'utiliser à la place de getActivity()
. Ce champ peut être initialisé dans la méthode onAttach () de Fragment comme suit:
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof Activity){
mActivity =(Activity) context;
}
}
Dans mes projets, je définis généralement une classe de base pour tous mes fragments avec cette fonctionnalité:
public abstract class BaseFragment extends Fragment {
protected FragmentActivity mActivity;
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof Activity){
mActivity =(Activity) context;
}
}
}
Bonne codage,
Depuis le niveau 23 de l'API Android, onAttach (activité d'activité) est obsolète. Vous devez utiliser onAttach (contexte de contexte). http://developer.Android.com/reference/Android/app/Fragment.html#onAttach(Android.app.Activity)
L'activité est un contexte, donc si vous pouvez simplement vérifier que le contexte est une activité et le lancer si nécessaire.
@Override
public void onAttach(Context context) {
super.onAttach(context);
Activity a;
if (context instanceof Activity){
a=(Activity) context;
}
}
PJL a raison. J'ai utilisé sa suggestion et voici ce que j'ai fait:
variables globales définies pour fragment:
private final Object attachingActivityLock = new Object();
private boolean syncVariable = false;
mis en œuvre
@Override public void onAttach(Activity activity) { super.onAttach(activity); synchronized (attachingActivityLock) { syncVariable = true; attachingActivityLock.notifyAll(); } }
3 J'ai bouclé ma fonction, où je dois appeler getActivity (), en thread, car si elle s'exécutait sur le thread principal, je bloquerais le thread avec l'étape 4. et onAttach () ne serait jamais appelé.
Thread processImage = new Thread(new Runnable() {
@Override
public void run() {
processImage();
}
});
processImage.start();
4 dans ma fonction où j'ai besoin d'appeler getActivity (), j'utilise ceci (avant l'appel getActivity ())
synchronized (attachingActivityLock) {
while(!syncVariable){
try {
attachingActivityLock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Si vous avez des mises à jour d'interface utilisateur, n'oubliez pas de les exécuter sur le thread d'interface utilisateur. Je dois mettre à jour ImgeView, ce que j'ai fait:
image.post(new Runnable() {
@Override
public void run() {
image.setImageBitmap(imageToShow);
}
});
L'ordre dans lequel les rappels sont appelés après commit ():
J'avais besoin de travailler sur des vues, donc onAttach () ne fonctionnait pas pour moi; il s'est écrasé. J'ai donc déplacé une partie de mon code qui définissait certains paramètres dans une méthode appelée juste après commit () (1.), puis l'autre partie du code qui gérait la vue dans onCreateView () (3.).
Les autres réponses qui suggèrent de garder une référence à l'activité dans onAttach ne font que suggérer un pansement au problème réel. Lorsque getActivity renvoie la valeur null, cela signifie que le fragment n'est pas associé à l'activité. Le plus souvent, cela se produit lorsque l'activité est terminée en raison d'une rotation ou que l'activité est terminée, mais que le fragment a une sorte d'écouteur de rappel. Lorsque l’auditeur est appelé, si vous avez besoin de faire quelque chose avec l’activité mais que celle-ci est partie, vous ne pouvez pas faire grand chose. Dans votre code, vous devriez juste vérifier getActivity() != null
et s'il n'y en a pas, alors ne faites rien. Si vous conservez une référence à l'activité qui a disparu, vous empêchez cette activité d'être collectée. Les tâches d'interface utilisateur que vous pourriez essayer de faire ne seront pas vues par l'utilisateur. Je peux imaginer des situations où, dans le programme d'écoute de rappel, vous souhaiterez peut-être définir un contexte pour quelque chose qui ne soit pas lié à l'interface utilisateur. Dans ces cas, il est probablement plus logique d'obtenir le contexte de l'application. Notez que la seule raison pour laquelle l'astuce onAttach
n'est pas une fuite de mémoire importante est que, normalement, une fois que l'auditeur de rappel a été exécuté, il ne sera plus nécessaire et peut être récupéré avec le fragment, ses vues et son contexte d'activité. Si vous setRetainInstance(true)
, le risque de fuite de mémoire est plus élevé car le champ d'activité sera également conservé, mais après la rotation, il pourrait s'agir de l'activité précédente et non de l'actuelle.
Pour la première partie @thucnguyen était sur la bonne voie .
Cela s'est produit lorsque vous appelez getActivity () dans un autre thread qui s'est terminé après la suppression du fragment. Le cas typique est d'appeler getActivity () (par exemple pour un Toast) lorsqu'une requête HTTP est terminée (dans onResponse par exemple).
Certains appels HTTP étaient en cours d'exécution même après la fermeture de l'activité (car le traitement d'une demande HTTP peut prendre un certain temps). Ensuite, par le biais de HttpCallback
, j'ai essayé de mettre à jour certains champs Fragment et j'ai obtenu une exception null
lors de la tentative de getActivity()
.
http.newCall(request).enqueue(new Callback(...
onResponse(Call call, Response response) {
...
getActivity().runOnUiThread(...) // <-- getActivity() was null when it had been destroyed already
IMO, la solution consiste à empêcher que les rappels ne se produisent lorsque le fragment n'est plus en vie (et ce n'est pas uniquement avec Okhttp).
Si vous jetez un coup d'œil au fragment lifecycle (plus d'infos ici ), vous remarquerez qu'il existe des méthodes onAttach(Context context)
et onDetach()
. Ceux-ci sont appelés lorsque le fragment appartient à une activité et juste avant de cesser de l'être.
Cela signifie que nous pouvons empêcher ce rappel de se produire en le contrôlant dans la méthode onDetach
.
@Override
public void onAttach(Context context) {
super.onAttach(context);
// Initialize HTTP we're going to use later.
http = new OkHttpClient.Builder().build();
}
@Override
public void onDetach() {
super.onDetach();
// We don't want to receive any more information about the current HTTP calls after this point.
// With Okhttp we can simply cancel the on-going ones (credits to https://github.com/square/okhttp/issues/2205#issuecomment-169363942).
for (Call call : http.dispatcher().queuedCalls()) {
call.cancel();
}
for (Call call : http.dispatcher().runningCalls()) {
call.cancel();
}
}
Faites comme suit. Je pense que cela vous sera utile.
private boolean isVisibleToUser = false;
private boolean isExecutedOnce = false;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_my, container, false);
if (isVisibleToUser && !isExecutedOnce) {
executeWithActivity(getActivity());
}
return root;
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
this.isVisibleToUser = isVisibleToUser;
if (isVisibleToUser && getActivity()!=null) {
isExecutedOnce =true;
executeWithActivity(getActivity());
}
}
private void executeWithActivity(Activity activity){
//Do what you have to do when page is loaded with activity
}
Où appelez-vous cette fonction? Si vous l'appelez dans le constructeur de Fragment
, il retournera null
.
Appelez simplement getActivity()
lorsque la méthode onCreateView()
est exécutée.
Ceux qui ont toujours le problème avec onAttach (activité d'activité), il vient de changer en contexte -
@Override
public void onAttach(Context context) {
super.onAttach(context);
this.context = context;
}
Dans la plupart des cas, il vous suffit de sauvegarder le contexte. Par exemple, si vous souhaitez utiliser getResources (), vous pouvez le faire directement à partir du contexte. Si vous devez encore intégrer le contexte à votre activité, faites-le -
@Override
public void onAttach(Context context) {
super.onAttach(context);
mActivity a; //Your activity class - will probably be a global var.
if (context instanceof mActivity){
a=(mActivity) context;
}
}
Comme suggéré par l'utilisateur1868713.
Vous pouvez utiliser onAttach ou si vous ne voulez pas mettre onAttach partout, vous pouvez mettre une méthode qui renvoie ApplicationContext sur la classe principale App:
public class App {
...
private static Context context;
@Override
public void onCreate() {
super.onCreate();
context = this;
}
public static Context getContext() {
return context;
}
...
}
Après cela, vous pourrez le réutiliser partout dans votre projet, comme ceci:
App.getContext().getString(id)
S'il vous plaît laissez-moi savoir si cela ne fonctionne pas pour vous.
Une autre solution intéressante consisterait à utiliser LiveData avec l'architecture MVVM d'Android . Vous définiriez un objet LiveData dans votre ViewModel et l'observeriez dans votre fragment. Lorsque la valeur LiveData serait modifiée, il avertirait uniquement votre observateur (fragment dans ce cas). si votre fragment est à l'état actif, il serait alors garanti que l'interface utilisateur fonctionne et que vous accédez à l'activité uniquement lorsque votre fragment est à l'état actif. Ceci est un avantage qui vient avec LiveData
Bien sûr, lorsque cette question a été posée pour la première fois, il n'y avait pas de LiveData. Je laisse cette réponse ici parce que, comme je vois, ce problème persiste et pourrait être utile à quelqu'un.