Внутренний класс фрагмента должен быть статическим

У меня есть класс FragmentActivity с внутренним классом, который должен отображать Dialog. Но я должен сделать это static. Eclipse предлагает мне подавить ошибку с помощью @SuppressLint("ValidFragment"). Это плохой стиль, если я это делаю и каковы возможные последствия?

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() запускает Asynctask.

Ответ 1

Нестационарные внутренние классы содержат ссылку на их родительские классы. Проблема с созданием внутреннего класса Fragment non-static заключается в том, что вы всегда держите ссылку на Activity. GarbageCollector не может собирать вашу деятельность. Таким образом, вы можете "пропустить" действие, если, например, изменения ориентации. Поскольку Фрагмент может по-прежнему жить и вставляется в новое действие.

EDIT:

Поскольку некоторые люди спрашивали меня о каком-то примере, я начал писать один, делая это, я обнаружил еще несколько проблем при использовании нестатических фрагментов:

  • Они не могут использоваться в XML файле, поскольку у них нет пустого конструктора (у них может быть пустой конструктор, но вы обычно создаете нестатические вложенные классы, делая myActivityInstance.new Fragment(), и это отличается от вызова только пустого конструктора)
  • Они не могут быть повторно использованы вообще, так как FragmentManager иногда вызывает этот пустой конструктор. Если вы добавили фрагмент в какую-либо транзакцию.

Итак, чтобы сделать мой пример работы, мне пришлось добавить

wrongFragment.setRetainInstance(true);

Строка не приведет к сбою приложения при изменении ориентации.

Если вы выполните этот код, у вас будет активность с некоторыми текстовыми изображениями и двумя кнопками - кнопки увеличивают некоторый счетчик. И Фрагменты показывают, какую ориентацию они считают своей деятельностью. В начале все работает правильно. Но после изменения ориентации экрана только первый фрагмент работает корректно - второй по-прежнему вызывает материал при его старой активности.

My Activity class:

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();
}

}

Обратите внимание, что вы, вероятно, не должны приводить активность в onAttach, если хотите использовать свой фрагмент в разных действиях, но здесь он работает для примера.

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>

Ответ 2

Я не буду говорить о внутреннем фрагменте, но более конкретно о DialogFragment, определенном в рамках действия, потому что это 99% случай для этого вопроса.
С моей точки зрения, я не хочу, чтобы мой DialogFragment (ваш NetworkConnectionError) был статичным, потому что я хочу иметь возможность вызывать переменные или методы моего содержащего класса (Activity). Так что это не будет статично. Но я не хочу генерировать memoryLeaks. Итак, каково решение?
Простой, когда вы заходите на onStop, забудьте, что вы убили свой DialogFragment, это так просто. Таким образом, код выглядит примерно так:

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");
}

И таким образом вы избегаете утечек памяти (потому что это плохо), и вы гарантируете, что у вас нет [expletive] статического фрагмента, который не может получить доступ к вашим полям и методам активности. Это хороший способ справиться с этой проблемой, с моей точки зрения.

Ответ 3

Если вы разрабатываете его в андроид-студии, тогда нет проблем, если вы не дадите его как static.The проект будет работать без каких-либо ошибок и во время генерации apk вы получите Ошибка: этот внутренний фрагмент должен быть статичным [ValidFragment ]

Thats lint error, вы, вероятно, строите с помощью gradle, чтобы отключить прерывание при ошибках, добавьте:

lintOptions {
    abortOnError false
}

для сборки .gradle. `

Ответ 4

Если вы хотите получить доступ к членам внешнего класса (Activity) и по-прежнему не хотите ставить элементы static в Activity (поскольку фрагмент должен быть public static), вы можете сделать переопределение 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
        ...
     }

Ответ 5

добавить аннотацию перед внутренним классом

@SuppressLint ( "validFragment" )