Действительно ли фрагментам нужен пустой конструктор?

У меня есть Fragment с конструктором, который принимает несколько аргументов. Мое приложение работало нормально во время разработки, но на производстве мои пользователи иногда видят этот сбой:

android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment 
make sure class name exists, is public, and has an empty constructor that is public

Я мог бы создать пустой конструктор, как предполагает это сообщение об ошибке, но это не имеет смысла для меня, так как тогда мне пришлось бы вызывать отдельный метод для завершения настройки Fragment,

Мне любопытно, почему этот сбой случается только изредка. Может быть, я использую ViewPager неправильно? Я создаю все экземпляры Fragmentи сохранить их в списке внутри Activity, Я не пользуюсь FragmentManager транзакции, так как ViewPager примеры, которые я видел, не требовали этого, и все, казалось, работало во время разработки.

5 ответов

Решение

Да, они делают.

Вы не должны переопределять конструктор в любом случае. Вы должны иметь newInstance() статический метод определен и передает любые параметры через аргументы (связка)

Например:

public static final MyFragment newInstance(int title, String message) {
    MyFragment f = new MyFragment();
    Bundle bdl = new Bundle(2);
    bdl.putInt(EXTRA_TITLE, title);
    bdl.putString(EXTRA_MESSAGE, message);
    f.setArguments(bdl);
    return f;
}

И, конечно, схватить арги таким образом:

@Override
public void onCreate(Bundle savedInstanceState) {
    title = getArguments().getInt(EXTRA_TITLE);
    message = getArguments().getString(EXTRA_MESSAGE);

    //...
    //etc
    //...
}

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

@Override
public void onCreate(Bundle savedInstanceState) {
    if (savedInstanceState == null){
        getSupportFragmentManager()
            .beginTransaction()
            .replace(R.id.content, MyFragment.newInstance(
                R.string.alert_title,
                "Oh no, an error occurred!")
            )
            .commit();
    }
}

Таким образом, если отсоединено и повторно присоединено, состояние объекта может быть сохранено через аргументы. Очень похоже на связки, прикрепленные к Intents.

Причина - Дополнительное чтение

Я думал, что объясню, почему людям интересно, почему.

Если вы проверите: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/Fragment.java

Вы увидите instantiate(..) метод в Fragment класс называет newInstance метод:

public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
    try {
        Class<?> clazz = sClassMap.get(fname);
        if (clazz == null) {
            // Class not found in the cache, see if it's real, and try to add it
            clazz = context.getClassLoader().loadClass(fname);
            if (!Fragment.class.isAssignableFrom(clazz)) {
                throw new InstantiationException("Trying to instantiate a class " + fname
                        + " that is not a Fragment", new ClassCastException());
            }
            sClassMap.put(fname, clazz);
        }
        Fragment f = (Fragment) clazz.getConstructor().newInstance();
        if (args != null) {
            args.setClassLoader(f.getClass().getClassLoader());
            f.setArguments(args);
        }
        return f;
    } catch (ClassNotFoundException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": make sure class name exists, is public, and has an"
                + " empty constructor that is public", e);
    } catch (java.lang.InstantiationException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": make sure class name exists, is public, and has an"
                + " empty constructor that is public", e);
    } catch (IllegalAccessException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": make sure class name exists, is public, and has an"
                + " empty constructor that is public", e);
    } catch (NoSuchMethodException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": could not find Fragment constructor", e);
    } catch (InvocationTargetException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": calling Fragment constructor caused an exception", e);
    }
}

http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html Объясняет, почему после создания экземпляра он проверяет, что средство доступа public и что этот загрузчик классов разрешает доступ к нему.

Это довольно неприятный метод, но он позволяет FragmentManger убить и воссоздать Fragments с государствами. (Подсистема Android делает аналогичные вещи с Activities).

Пример класса

Меня часто спрашивают о звонке newInstance,(не путайте это с методом класса. Весь этот пример класса должен показать использование.

/**
 * Created by chris on 21/11/2013
 */
public class StationInfoAccessibilityFragment extends BaseFragment implements JourneyProviderListener {

    public static final StationInfoAccessibilityFragment newInstance(String crsCode) {
        StationInfoAccessibilityFragment fragment = new StationInfoAccessibilityFragment();

        final Bundle args = new Bundle(1);
        args.putString(EXTRA_CRS_CODE, crsCode);
        fragment.setArguments(args);

        return fragment;
    }

    // Views
    LinearLayout mLinearLayout;

    /**
     * Layout Inflater
     */
    private LayoutInflater mInflater;
    /**
     * Station Crs Code
     */
    private String mCrsCode;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCrsCode = getArguments().getString(EXTRA_CRS_CODE);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        mInflater = inflater;
        return inflater.inflate(R.layout.fragment_station_accessibility, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mLinearLayout = (LinearLayout)view.findViewBy(R.id.station_info_accessibility_linear);
        //Do stuff
    }

    @Override
    public void onResume() {
        super.onResume();
        getActivity().getSupportActionBar().setTitle(R.string.station_info_access_mobility_title);
    }

    // Other methods etc...
}

Как отмечает CommonsWare в этом вопросе /questions/16445218/fragment-instantiationexception-net-pustogo-konstruktora-google-maps-v2/16445237#16445237, эта ошибка также может возникать, если вы создаете анонимный подкласс фрагмента, поскольку у анонимных классов не может быть конструкторов.

Не делайте анонимных подклассов фрагмента:-)

Да, как вы можете видеть, пакет поддержки также создает фрагменты (когда они уничтожаются и открываются заново). Ваши подклассы Fragemnt нуждаются в открытом пустом конструкторе, так как это то, что вызывается фреймворком.

Взгляните на официальную документацию: Фрагмент: https://developer.android.com/reference/android/app/Fragment

Все подклассы Fragment должны включать общедоступный конструктор без аргументов. Платформа часто повторно создает экземпляр класса фрагмента, когда это необходимо, в частности, во время восстановления состояния, и должна иметь возможность найти этот конструктор для его создания. Если конструктор без аргументов недоступен, во время восстановления состояния в некоторых случаях возникнет исключение времени выполнения.

Вот мое простое решение:

1 - Определите свой фрагмент

public class MyFragment extends Fragment {

    private String parameter;

    public MyFragment() {
    }

    public void setParameter(String parameter) {
        this.parameter = parameter;
    } 
}

2 - Создайте свой новый фрагмент и заполните параметр

    myfragment = new MyFragment();
    myfragment.setParameter("here the value of my parameter");

3 - Наслаждайся этим!

Очевидно, вы можете изменить тип и количество параметров. Быстро и просто.

Другие вопросы по тегам