Чи потрібні фрагменти справді порожній конструктор?


258

У мене є 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я бачив , що приклади цього не потребували, і все, здавалося, працювало під час розробки.


22
у деяких версіях андроїда (принаймні ICS) ви можете перейти до налаштувань -> параметри розробника та включити "Не тримати діяльність". Це дасть вам детермінований спосіб перевірити випадки, коли конструктор без аргументів необхідний.
Кіт

У мене був цей самий випуск. Я призначав замість даних пакетних даних замість членських змінних (використовуючи ctor, що не використовується за замовчуванням). Моя програма не вийшла з ладу, коли я вбив додаток - це відбувалося лише тоді, коли планувальник поставив мій додаток на резервний пристрій, щоб "заощадити місце". Я знайшов це, перейшовши до завдання Mgr та відкривши тону інших програм, а потім повторно відкрити додаток у налагодженні. Він щоразу розбивався. Проблема була вирішена, коли я використав відповідь Кріса Дженкінса, щоб використовувати аргументи в комплекті.
wizurd

Можливо, вас зацікавить ця тема: stackoverflow.com/questions/15519214/…
Стефан Хауштейн

5
Побічна примітка для майбутніх читачів: якщо ваш Fragmentпідклас взагалі не оголошує жодних конструкторів, то за замовчуванням для вас неявно буде створений порожній загальнодоступний конструктор (це стандартна поведінка Java ). Вам не потрібно чітко оголошувати порожній конструктор, якщо ви також не оголосили інших конструкторів (наприклад, аргументів).
Тоні Чан

Я лише зазначу, що IntelliJ IDEA, принаймні для версії 14.1, подає попередження про те, що у фрагменті не повинно бути конструктора за замовчуванням.
RenniePet

Відповіді:


349

Так вони роблять.

Ви ні в якому разі не повинні переосмислювати конструктор. У вас повинен бути визначений 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();
    }
}

Таким чином, якщо відокремлений та повторно приєднаний стан об'єкта може зберігатися через аргументи. Наче схожі на пучки, прикріплені до Інтентів.

Причина - додаткове читання

Я думав, що поясню, чому людям цікаво, чому.

Якщо ви перевірите: 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#newInstance () Пояснює, чому при встановленні даних він перевіряє, що є аксесуаром 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...
}

2
Якщо ви призупинили діяльність або знищите її. Таким чином, ви переходите до головного екрану, а потім діяльність Android вбивається, щоб заощадити місце. Стан фрагментів буде збережено (використовуючи аргументи), потім gc об'єкт (як правило). Тож після повернення до активності фрагменти слід спробувати відтворити за допомогою збереженого стану, нового за замовчуванням (), потім onCreate і т. Д. ... Також якщо діяльність намагається зберегти ресурси (телефон з низькою пам’яттю), вона може видалити об'єкти, щойно пішли на паузу. .. Commonsguy повинен бути в змозі пояснити краще. Коротше Ви не знаєте! :)
Chris.Jenkins

1
@mahkie Дійсно, якщо вам потрібно ALOT з об'єктів / моделей, ви повинні захопити їх асинхронно з бази даних або ContentProvider.
Chris.Jenkins

1
@ Chris.Jenkins Вибачте, якщо мені не було зрозуміло ... я висловив те, що на відміну від Activity, фрагменти не дають зрозуміти, що конструктори не повинні використовуватися для передачі / обміну даними. І хоча демпінг / відновлення добре, я вважаю, що зберігання кількох копій даних іноді може займати більше пам’яті, ніж може відновити знищення перегляду. У деяких випадках може бути корисно мати можливість розглядати колекцію діяльності / фрагментів як одиницю, знищувати в цілому або взагалі - тоді ми можемо передавати дані через конструктори. Наразі щодо цього питання порожній конструктор є єдиним.
kaay

3
Чому ви тримаєте кілька копій даних? Пакети | Парцелябельність насправді передає посилання на пам'ять, коли вона може знаходитися між станами / фрагментами / видами діяльності (це фактично спричиняє деякі дивні проблеми стану). Єдиний раз, коли Parcelable фактично ефективно "копіює" дані між процесами та повним життєвим циклом. Наприклад, якщо ви передаєте об'єкт вашим фрагментам з вашої діяльності, ваша посилання не є клоном. Ваша єдина реальна додаткова накладна - це додаткові об'єкти фрагмента.
Chris.Jenkins

1
@ Chris.Jenkins Добре, що тоді було моїм незнанням Parcelable. Прочитавши короткий javadoc Parcelable і частину посилки недалеко від слова "реконструйований", я не дійшов до частини "Активні об'єкти", зробивши висновок, що це був лише низький рівень, більш оптимізований, але менш універсальний серіалізаційний. Я цим надягаю капелюх сорому та бурмотіння "Досі не можу ділитися нерозбірливими виробами, а створення парцеляни може бути занепокоєнням" :)
каяк

17

Як зазначає CommonsWare в цьому питанні https://stackoverflow.com/a/16064418/1319061 , ця помилка також може виникнути, якщо ви створюєте анонімний підклас фрагменту, оскільки анонімні класи не можуть мати конструкторів.

Не робіть анонімні підкласи фрагмента :-)


1
Або, як згадується у цій публікації CommonsWare, переконайтеся, що ви оголосили внутрішню активність / фрагмент / приймач як "статичну", щоб уникнути цієї помилки.
Тоні Вікхем

7

Так, як ви бачите, що пакет підтримки також створює фрагменти (коли вони знищуються та знову відкриваються). Вашим Fragmentпідкласам потрібен публічний порожній конструктор, оскільки це називається рамкою.


Порожній фрагмент Конструктор повинен викликати супер () Конструктор чи ні? Я запитую це, розуміючи, що порожній громадський конструктор є обов'язковим. якщо дзвінок super () не має сенсу для порожнього громадського конструктора
TNR

@TNR, оскільки всі абстракції фрагмента мають порожній конструктор, super()було б безрезультатним, оскільки батьківський клас порушив правило порожнього публічного конструктора. Тож ні вам не потрібно проходити super()всередині вашого конструктора.
Chris.Jenkins

4
Насправді це не вимога чітко визначати порожній конструктор у фрагменті. Кожен клас Java все одно має неявний конструктор за замовчуванням. Взято з: docs.oracle.com/javase/tutorial/java/javaOO/constructors.html ~ "Компілятор автоматично надає конструктор без аргументів, конструктор за замовчуванням для будь-якого класу без конструкторів."
ІгорГанапольський

-6

Ось моє просте рішення:

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 - Насолоджуйтесь!

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


5
Це все ж не справляється з перезавантаженням фрагмента системою.
Vidia
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.