Подтвердить что ты не робот

Андроид factory метод против перегрузки конструктора

Прежде всего, я уже знаю, что FragmentManager часто уничтожает, а затем повторно создает Фрагмент, используя конструктор по умолчанию. Кодеры должны сохранять важные вещи в Bundle аргументов один раз в методе factory, а затем вынимать их каждый раз, когда фрагмент воссоздается в onCreate (Bundle).

public class MyFragment extends Fragment {
    private static final String MY_STRING_CONSTANT = "param";
    private int mIntegerMember;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mIntegerMember= getArguments().getInt(MY_STRING_CONSTANT);
    }
}

Мой вопрос в том, есть ли разница между этим:

// Inside MyFragment.java
public MyFragment() {
    // No-argument constructor required by the FragmentManager.
}
public static MyFragment newInstance(int param) {
    // Factory method
    MyFragment fragment = new MyFragment();
    Bundle args = new Bundle();
    args.putInt(MY_STRING_CONSTANT, param);
    fragment.setArguments(args);
    return fragment;
}

// Somewhere else
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.add(R.id.frame_layout, MyFragment.newInstance(123)).commit();

И это:

// Inside MyFragment.java
public MyFragment() {
    // No-argument constructor required by the FragmentManager.
}
public MyFragment(int param) {
    // Parameterized constructor
    Bundle args = new Bundle();
    args.putInt(MY_STRING_CONSTANT, param);
    setArguments(args);
}

// Somewhere else
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.add(R.id.frame_layout, new MyFragment(123)).commit();

Я не вижу ничего, что мешает FragmentManager вызывать конструктор без аргументов. И данные, которые я сохраняю в параметризованном конструкторе (в объекте Bundle), будут сохраняться и восстанавливаться во время onCreate(), как если бы я использовал метод factory.

4b9b3361

Ответ 1

Android никогда напрямую не вызывает конструктор не по умолчанию (или метод factory) - технически, на самом деле не имеет значения, что вы используете. Вы можете вызвать setArguments (в произвольном методе, даже в конструкторе) в любое время до того, как вы добавите фрагмент, и этот пакет будет сохранен/восстановлен для вас, если будет восстановлен фрагмент. В представлениях также есть специальные конструкторы, вызываемые Android, но вы можете сделать свои собственные с произвольными аргументами, если хотите (они просто не будут вызываться Android).

Код для Fragment.setArguments:

/**
 * Supply the construction arguments for this fragment.  This can only
 * be called before the fragment has been attached to its activity; that
 * is, you should call it immediately after constructing the fragment.  The
 * arguments supplied here will be retained across fragment destroy and
 * creation.
 */
public void setArguments(Bundle args) {
    if (mIndex >= 0) {
        throw new IllegalStateException("Fragment already active");
    }
    mArguments = args;
}

Код для Fragment.instantiate:

    /**
     * Create a new instance of a Fragment with the given class name.  This is
     * the same as calling its empty constructor.
     *
     * @param context The calling context being used to instantiate the fragment.
     * This is currently just used to get its ClassLoader.
     * @param fname The class name of the fragment to instantiate.
     * @param args Bundle of arguments to supply to the fragment, which it
     * can retrieve with {@link #getArguments()}.  May be null.
     * @return Returns a new fragment instance.
     * @throws InstantiationException If there is a failure in instantiating
     * the given fragment class.  This is a runtime exception; it is not
     * normally expected to happen.
     */
    public static Fragment instantiate(Context context, String fname, Bundle args) {
        try {
            Class<?> clazz = sClassMap.get(fname);
            if (clazz == null) {
                // Class not found in the cache, see if it real, and try to add it
                clazz = context.getClassLoader().loadClass(fname);
                sClassMap.put(fname, clazz);
            }
            Fragment f = (Fragment)clazz.newInstance();
            if (args != null) {
                args.setClassLoader(f.getClass().getClassLoader());
                f.mArguments = 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);
        }
    }

Fragment.instantiate вызывается, когда Android хочет создать экземпляр вашего фрагмента. Он просто обращается к Class.newInstance, который является Java-методом для создания класса с использованием конструктора с нулевым аргументом по умолчанию. Глядя на этот код, кажется, нет проблем с созданием дополнительного конструктора и вызовом setArguments внутри него.

В качестве условного обозначения типично использовать метод factory при работе с фрагментами. В большинстве официальный образец кода фрагмента также используются методы factory. Вот несколько возможных причин:

  • Если вы пишете пользовательский конструктор (с аргументами), вам также нужно указать конструктор с нулевым аргументом. Общей ошибкой является создание настраиваемого конструктора, но забудьте определить конструктор с нулевым аргументом - это приведет к сбою, когда Android попытается вызвать конструктор нуля-arg при воссоздании вашего фрагмента.

  • При создании настраиваемого конструктора может возникнуть соблазн напрямую назначить аргументы конструктора полям. Вот как написано практически любое другое Java-класс (и, следовательно, как вы, естественно, захотите написать классы). Поскольку Android будет только ссылаться на конструктор с нулевым аргументом на фрагменте, эти данные не будут доступны никаким воссозданным экземплярам. Как вы уже знаете, использование setArguments - это способ решить эту проблему. Несмотря на то, что вы можете сделать это внутри конструктора, использование метода factory делает более очевидным, что этот класс не может быть построен обычным способом, что уменьшает возможность совершения вышеуказанной ошибки (или аналогичной).

Ответ 2

Реализация FragmentManager вызывает конструктор по умолчанию Fragment. Я бы подумал, что это потребует много накладных расходов, чтобы определить, какие аргументы передаются конструктору, отличному от стандартного, поэтому команда Android решила пойти по маршруту Bundle. Если вы используете нестандартный конструктор, данные, которые вы передадите ему, не будут сохранены во время отдыха, поэтому вы получите указатели null. Используя механизм setArguments()/getArguments(), вы гарантируете, что FragmentManager будет правильно инициализировать Fragment.

Когда вы выполните этот вызов:

transaction.add(R.id.frame_layout, new MyFragment(123));

будет гарантировано, что в первый раз будет хорошо. Теперь скажем, что пользователь поворачивает экран (setRetainInstance() не установлен), FragmentManager создаст новый экземпляр Fragment, вызвав:

new MyFragment(); //the default constructor.

Это означает, что все переменные, которые должны были быть инициализированы в конструкторе не по умолчанию, будут null.

Документы говорят нам избегать перегрузки конструктора Fragment, я бы придерживался их правил. Вы можете получить какое-то неожиданное поведение, если попытаетесь перегрузить конструктор (даже если вы выполняете setArguments() в перегруженном конструкторе).