web-dev-qa-db-fra.com

La classe interne de fragment devrait être statique

J'ai une classe FragmentActivity avec une classe interne qui devrait afficher Dialog. Mais je suis obligé de le faire static. Eclipse me propose de supprimer les erreurs avec @SuppressLint("ValidFragment"). Est-ce un mauvais style si je le fais et quelles sont les conséquences possibles?

public class CarActivity extends FragmentActivity {
//Code
  @SuppressLint("ValidFragment")
  public class NetworkConnectionError extends DialogFragment {
    private String message;
    private AsyncTask task;
    private String taskMessage;
    @Override
    public void setArguments(Bundle args) {
      super.setArguments(args);
      message = args.getString("message");
    }
    public void setTask(CarActivity.CarInfo task, String msg) {
      this.task = task;
      this.taskMessage = msg;
    }
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
      // Use the Builder class for convenient dialog construction
      AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
      builder.setMessage(message).setPositiveButton("Go back", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int id) {
          Intent i = new Intent(getActivity().getBaseContext(), MainScreen.class);
          startActivity(i);
        }
      });
      builder.setNegativeButton("Retry", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int id) {
          startDownload();
        }
      });
      // Create the AlertDialog object and return it
      return builder.create();
    }
  }

startDownload() démarre Asynctask.

69
user2176737

Les classes internes non statiques contiennent une référence à leurs classes parentes. Le problème avec la création d'une classe interne Fragment non statique est que vous avez toujours une référence à l'activité . Le GarbageCollector ne peut pas collecter votre activité . Ainsi, vous pouvez "divulguer" l'activité si, par exemple, l'orientation change. Parce que le fragment peut encore vivre et est inséré dans une nouvelle activité .

EDIT:

Comme certaines personnes m'ont demandé un exemple, j'ai commencé à en écrire un, mais j'ai rencontré d'autres problèmes lors de l'utilisation de fragments non statiques:

  • Ils ne peuvent pas être utilisés dans un fichier xml car ils n'ont pas de constructeur vide (ils peuvent avoir un constructeur vide, mais vous instanciez généralement des classes imbriquées non statiques en faisant myActivityInstance.new Fragment()), ce qui est différent du seul appel d'un constructeur vide. )
  • Ils ne peuvent pas du tout être réutilisés - puisque FragmentManager appelle parfois aussi ce constructeur vide. Si vous avez ajouté le fragment dans une transaction.

Donc, pour que mon exemple fonctionne, je devais ajouter le

wrongFragment.setRetainInstance(true);

Ligne pour ne pas bloquer l'application lors du changement d'orientation.

Si vous exécutez ce code, vous aurez une activité avec du texte et 2 boutons - les boutons augmentent le compteur. Et les fragments montrent l'orientation qu'ils pensent avoir dans leur activité. Au début, tout fonctionne correctement. Mais après avoir changé l’orientation de l’écran, seul le premier fragment fonctionne correctement - le second appelle toujours des éléments à leur ancienne activité.

Mon cours d'activité:

package com.example.fragmenttest;

import Android.annotation.SuppressLint;
import Android.app.Activity;
import Android.app.Fragment;
import Android.app.FragmentTransaction;
import Android.content.res.Configuration;
import Android.os.Bundle;
import Android.view.LayoutInflater;
import Android.view.View;
import Android.view.ViewGroup;
import Android.widget.Button;
import Android.widget.LinearLayout;
import Android.widget.TextView;

public class WrongFragmentUsageActivity extends Activity
{
private String mActivityOrientation="";
private int mButtonClicks=0;
private TextView mClickTextView;


private static final String WRONG_FRAGMENT_TAG = "WrongFragment" ;

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    int orientation = getResources().getConfiguration().orientation;
    if (orientation == Configuration.ORIENTATION_LANDSCAPE)
    {
        mActivityOrientation = "Landscape";
    }
    else if (orientation == Configuration.ORIENTATION_PORTRAIT)
    {
        mActivityOrientation = "Portrait";
    }

    setContentView(R.layout.activity_wrong_fragement_usage);
    mClickTextView = (TextView) findViewById(R.id.clicksText);
    updateClickTextView();
    TextView orientationtextView = (TextView) findViewById(R.id.orientationText);
    orientationtextView.setText("Activity orientation is: " + mActivityOrientation);

    Fragment wrongFragment = (WrongFragment) getFragmentManager().findFragmentByTag(WRONG_FRAGMENT_TAG);
    if (wrongFragment == null)
    {
        wrongFragment = new WrongFragment();
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.add(R.id.mainView, wrongFragment, WRONG_FRAGMENT_TAG);
        ft.commit();
        wrongFragment.setRetainInstance(true); // <-- this is important - otherwise the fragment manager will crash when readding the fragment
    }
}

private void updateClickTextView()
{
    mClickTextView.setText("The buttons have been pressed " + mButtonClicks + " times");
}

private String getActivityOrientationString()
{
    return mActivityOrientation;
}


@SuppressLint("ValidFragment")
public class WrongFragment extends Fragment
{


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        LinearLayout result = new LinearLayout(WrongFragmentUsageActivity.this);
        result.setOrientation(LinearLayout.VERTICAL);
        Button b = new Button(WrongFragmentUsageActivity.this);
        b.setText("WrongFragmentButton");
        result.addView(b);
        b.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                buttonPressed();
            }
        });
        TextView orientationText = new TextView(WrongFragmentUsageActivity.this);
        orientationText.setText("WrongFragment Activities Orientation: " + getActivityOrientationString());
        result.addView(orientationText);
        return result;
    }
}

public static class CorrectFragment extends Fragment
{
    private WrongFragmentUsageActivity mActivity;


    @Override
    public void onAttach(Activity activity)
    {
        if (activity instanceof WrongFragmentUsageActivity)
        {
            mActivity = (WrongFragmentUsageActivity) activity;
        }
        super.onAttach(activity);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        LinearLayout result = new LinearLayout(mActivity);
        result.setOrientation(LinearLayout.VERTICAL);
        Button b = new Button(mActivity);
        b.setText("CorrectFragmentButton");
        result.addView(b);
        b.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                mActivity.buttonPressed();
            }
        });
        TextView orientationText = new TextView(mActivity);
        orientationText.setText("CorrectFragment Activities Orientation: " + mActivity.getActivityOrientationString());
        result.addView(orientationText);
        return result;
    }
}

public void buttonPressed()
{
    mButtonClicks++;
    updateClickTextView();
}

}

Notez que vous ne devriez probablement pas lancer l'activité dans onAttach si vous souhaitez utiliser votre fragment dans différentes activités - mais, ici, cela fonctionne. pour l'exemple.

Le activity_wrong_fragement_usage.xml:

<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:tools="http://schemas.Android.com/tools"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:orientation="vertical"
tools:context=".WrongFragmentUsageActivity" 
Android:id="@+id/mainView">

<TextView
    Android:id="@+id/orientationText"
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:text="" />

<TextView
    Android:id="@+id/clicksText"
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:text="" />



<fragment class="com.example.fragmenttest.WrongFragmentUsageActivity$CorrectFragment"
          Android:id="@+id/correctfragment"
          Android:layout_width="wrap_content"
          Android:layout_height="wrap_content" />


</LinearLayout>
93
Simon Meyer

Je ne parlerai pas de fragments internes, mais plus spécifiquement d'un DialogFragment défini dans une activité car c'est 99% du cas pour cette question.
De mon point de vue, je ne veux pas que mon DialogFragment (votre NetworkConnectionError) soit statique parce que je veux pouvoir appeler des variables ou des méthodes à partir de ma classe (Activity) qui le contient.
Ce ne sera pas statique, mais je ne veux pas non plus générer de memoryLeaks.
Quelle est la solution?
Simple. Lorsque vous allez sur onStop, assurez-vous de tuer votre DialogFragment. C'est aussi simple que ça. Le code ressemble à quelque chose comme ça:

public class CarActivity extends AppCompatActivity{

/**
 * The DialogFragment networkConnectionErrorDialog 
 */
private NetworkConnectionError  networkConnectionErrorDialog ;
//...  your code ...//
@Override
protected void onStop() {
    super.onStop();
    //invalidate the DialogFragment to avoid stupid memory leak
    if (networkConnectionErrorDialog != null) {
        if (networkConnectionErrorDialog .isVisible()) {
            networkConnectionErrorDialog .dismiss();
        }
        networkConnectionErrorDialog = null;
    }
}
/**
 * The method called to display your dialogFragment
 */
private void onDeleteCurrentCity(){
    FragmentManager fm = getSupportFragmentManager();
     networkConnectionErrorDialog =(DeleteAlert)fm.findFragmentByTag("networkError");
    if(networkConnectionErrorDialog ==null){
        networkConnectionErrorDialog =new DeleteAlert();
    }
    networkConnectionErrorDialog .show(getSupportFragmentManager(), "networkError");
}

Et ainsi, vous évitez les fuites de mémoire (parce que c'est mauvais) et vous vous assurez que vous n'avez pas de fragment statique [explétif] qui ne peut pas accéder aux champs et aux méthodes de votre activité. C’est la bonne façon de régler ce problème, de mon point de vue.

17

Si vous le développez dans Android studio, aucun problème si vous ne le donnez pas sous forme statique. Le projet s'exécutera sans erreur et au moment de la génération de apk, vous obtiendrez une erreur: Ce fragment intérieur class doit être statique [ValidFragment]

C'est une erreur de lint, vous construisez probablement avec gradle, pour désactiver l'avortement sur les erreurs, ajoutez:

lintOptions {
    abortOnError false
}

à build.gradle. `

5
chakri Reddy

Si vous souhaitez accéder aux membres de outer-class (Activity) et que vous ne souhaitez toujours pas rendre les membres statiques dans Activity (car fragment doit être public static), vous pouvez effectuer le remplacement sur OnActivityCreated.

public static class MyFragment extends ListFragment {

    private OuterActivityName activity; // outer Activity

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        activity = (OuterActivityName) getActivity();
        ...
        activity.member // accessing the members of activity
        ...
     }
4
Deepu