Переменные-члены против setArguments во фрагментах

Я заметил это в справочнике Android по фрагментам (в частности, DialogFragment), что они делают несколько вещей, отличных от того, что я ожидал:

1). Используйте метод public static foo newInstance(), а не конструктор.
2). Передавайте значения в onCreateDialog, используя setArguments, а не переменные-члены.

Я читал, что newInstance предпочтительнее при использовании отражения. Однако я действительно не понимаю, почему они передают параметры через пакет. Я бы предпочел, чтобы использование переменных-членов было бы безопаснее (без использования строки для извлечения из карты) и имело бы меньше накладных расходов.

Есть предположения?


person oobayly    schedule 13.07.2011    source источник


Ответы (4)


Я также наткнулся на это и обнаружил несколько преимуществ использования аргументов Bundle над полями экземпляра:

  • Если он находится в Bundle, система Android знает об этом и может создать и уничтожить ваш Fragment (используя обязательный конструктор без параметров/по умолчанию и обычные методы жизненного цикла) и просто снова передать пакет аргументов. Таким образом, никакие аргументы не теряются из-за нехватки памяти или возможного изменения ориентации (это часто поражает меня при первом развертывании на реальном устройстве после разработки в эмуляторе с меньшей ротацией).

  • Вы можете просто передать дополнительные функции Bundle из Activity как есть в Fragment, встроенные в макет; например Я часто использую это, когда у меня есть Activity, который отображает Fragment «полноэкранный режим» и нуждается в каком-то идентификаторе (или ContentProvider URI), чтобы знать, что отображать/делать. Иногда я даже добавляю еще что-то в Bundle (или копию), прежде чем передать его, например.

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
    
      if (savedInstanceState == null) { // not a re-creation
        final Bundle args = new Bundle(getIntent().getExtras());
        args.putInt(CoverImageFragment.BACKGROUND_RESOURCE, android.R.color.black);
        final Fragment fragment = CoverImageFragment.newInstance(args);
        getSupportFragmentManager()
          .beginTransaction()
          .add(android.R.id.content, fragment)
          .commit();
      }
    }
    
  • Он поддерживает способ разработки Fragment, близкий к Activity, то есть Bundle как «входные параметры, без исключений».

Что касается недостатков, о которых вы упомянули:

  • Я думаю, что накладные расходы минимальны, потому что вы, скорее всего, не будете запрашивать Bundle в тесном цикле, поэтому получение данных аргумента один раз в onCreate(), onViewCreate() и т. д. не так уж плохо.

  • Для безопасности типов Bundle имеет все различные методы getXXXX() и даже перегружается, чтобы предоставить значение по умолчанию, если что-то отсутствует/необязательно :)

Что касается методов newInstance(), я думаю о них как о простом способе инкапсулировать вызовы new и setArguments() для моего Fragment; Иногда я предоставляю дополнительный MyFragment newInstance(String singleIdOfWhatToDisplay), который создает Bundle и Fragment за один раз и возвращает готовый к работе экземпляр Fragment.

person Philipp Reichart    schedule 23.08.2011
comment
Что делает метод newInstance, чего не может достичь конструктор? Я могу вызвать setArguments из конструктора. - person Matthias; 28.11.2012
comment
@Matthias Я думаю, что в таких ситуациях лучше всего создавать фабричные методы ... - person Alex Lockwood; 24.02.2013
comment
newInstance не является фабричным методом (как в шаблоне фабричный метод, если вы это имеете в виду — см. en.wikipedia .org/wiki/Factory_method_pattern); он статичен и, следовательно, не может быть переопределен, а также не является универсальным по отношению к типу, который он возвращает. Из того, что я могу сказать, это не имеет никаких преимуществ перед использованием конструктора напрямую, кроме личных предпочтений. Однако я смутно помню, что фрагментам нужен конструктор по умолчанию? Возможно, идея здесь в том, чтобы не определять 2 ctor, один из которых принимает аргументы пакета, а другой пустой. Лучшая догадка. - person Matthias; 24.02.2013
comment
Я не понимаю этого. Если мы используем setArguments, Bundle, который мы передаем, каким-то образом сохраняется? КАК? Когда Activity и Fragment воссоздаются после поворота, ПОЧЕМУ Bundle, который мы создали в нашем host Activity, снова волшебным образом будет доступен для создания экземпляра Fragment? Если пакет, используемый для создания экземпляра фрагмента, не был вручную сохранен в действии, не будет ли он естественным образом потерян при воссоздании действия? Я все еще не вижу разницы между вызовом setArguments и написанием сеттера во фрагменте. - person rmirabelle; 01.07.2013
comment
не могли бы вы рассказать, как я могу передать пакет как есть во фрагмент, встроенный в макет xml? - person Konstantin Milyutin; 08.08.2013
comment
@Matthias Я думаю, что уместно назвать newInstance «фабричным» методом - любой метод, который возвращает «новый» экземпляр, может быть правильно признан «фабричным» методом (хотя я не уверен в «фабричном методе» ;-). - person rmirabelle; 28.01.2014
comment
@rmirabelle: 1) Пакет, который вы передаете с помощью setArguments, на самом деле не сохраняется, а просто является отправной точкой для фрагмента/активности (которые вызываются без параметра). 2) До того, как ваша активность/фрагмент будет уничтожена, например, из-за будет вызвано изменение ориентации onSaveInstanceState (которое вы должны перезаписать и сохранить все, что хотите сохранить (еще в другом пакете (пакет savedInstanceState ;-)))) 3) При создании (onCreate) вы проверяете если вы получили параметры из newInstance (getArguments()!=null) или сохранили параметры (savedInstanceState!=null). Легко (Дж/К) ;-) - person Levite; 17.02.2015
comment
@Levit 1) Я почти уверен, что набор аргументов сохранен. Из источника: Укажите аргументы конструкции для этого фрагмента. Это можно вызвать только до того, как фрагмент будет присоединен к его активности; то есть вы должны вызвать его сразу после построения фрагмента. Представленные здесь аргументы будут сохранены при уничтожении и создании фрагмента. - person tir38; 13.05.2015
comment
@Levit 2) Пакет аргументов - это способ сказать, что это вещи, необходимые для создания этого фрагмента. Сохраненное состояние экземпляра — это способ сказать, что это текущее состояние моего фрагмента. Пакет аргументов не может быть изменен после присоединения фрагмента к действию. Таким образом, вы можете своего рода думать о пакете аргументов как о наборе констант, а о пакете сохраненного состояния экземпляра как о наборе переменных. Вы можете использовать переменные для хранения констант, но зачем? - person tir38; 13.05.2015
comment
@Philipp Reichart Я бы поостерегся просто передавать пакет от активности к фрагменту. Если вы сделаете это, фрагмент должен будет знать о ключах из нескольких других классов. Тогда вы действительно тесно связали свой фрагмент с действием, которое его запустило, и с тем, что запустило ваше действие. - person tir38; 13.05.2015
comment
@ tir38: Вы правы, исходный пакет также сохраняется (только что проверил). Но, как я уже сказал, он все еще служит лишь отправной точкой (или, как вы сказали, как набор констант (образно)), а savedInstanceState должен хранить текущее (переменное) состояние. Итак, за исключением первой части моего комментария (она не сохранена), я думаю, что это правильное изложение вопроса, не так ли? Но, может быть, вы могли бы дать более подробный ответ по этому вопросу, я бы точно получил мой голос! Многие люди до сих пор не понимают проблему с пакетами, а вы предоставляете здесь полезную информацию! - person Levite; 28.05.2015

Я обнаружил, что это ОЧЕНЬ запутанная проблема (одна из многих, которые засоряют ландшафт Android).

setArguments() — это обходной путь для очень бесполезной потребности Android иметь конструктор без параметров, доступный для фрагментов.

Мое замешательство пришло волнами. Во-первых, методы, которые вы естественным образом переопределяете в своем Fragment (например, onCreate, onCreateView), получают параметр Bundle, который представляет savedInstanceState вашего Fragment. Это состояние экземпляра, по-видимому, не имеет НИЧЕГО общего со значениями, которые вы сохраняете через setArguments() и извлекаете через getArguments(). Оба используют Bundle, оба Bundles, скорее всего, будут доступны в рамках одного и того же переопределенного метода, и ни один из них не имеет ничего общего друг с другом.

Во-вторых, непонятно, как Android использует setArguments(). Android вызывает ваш конструктор без параметров для перестроения вашего Fragment при повороте, но, по-видимому, ТАКЖЕ вызовет тот метод setArguments(), который последний раз вызывался при создании Fragment.

Хм????

Удивительно, но правда. Все это создание Bundles с setArguments() безумием существует, чтобы компенсировать потребность в конструкторе Fragment без параметров.

Короче говоря, я использую статический метод newInstance для создания своего Fragment.

public MyFragment() {
    //satisfy Android
}

public static MyFragment newInstance(long record_id) {
    Log.d("MyFragment", "Putting " + record_id + " into newInstance");
    MyFragment f = new MyFragment();
    Bundle args = new Bundle();
    args.putLong("record_id", record_id);
    f.setArguments(args);
    return f;
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    /**
     * Perform an immediate check of arguments,
     * which ARE NOT the same as the bundle used
     * for saved instance state.
     */
    Bundle args = getArguments();
    if(args != null) {
        record_id = args.getLong("record_id");
        Log.d("MyFragment", "found record_id of " + String.valueOf(record_id));
    }
    if(savedInstanceState != null) {
        //now do something with savedInstanceState
    }
}
person rmirabelle    schedule 01.07.2013

Я довольно новичок в программировании для Android, но это мое текущее понимание проблемы:

Конструктор фрагментов не может иметь никаких параметров. Когда ваша деятельность приостановлена, ваш фрагмент может быть выпущен. Прежде чем ваша активность будет возобновлена, система создаст новую версию вашего Фрагмента, вызвав конструктор. Если используется конструктор не по умолчанию, как Android должен знать, какие типы и значения используются для аргументов вашего конструктора Fragments?

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

Филипп Райхарт ускользнул от этого в своем посте (на самом деле более чем ускользнул).

person WayneJ    schedule 27.06.2014
comment
Вы уверены, что вы новичок в Android? Это краткий, хорошо продуманный ответ. - person tir38; 13.05.2015

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

person havexz    schedule 12.12.2011
comment
Вы можете получить доступ к аргументам активности из фрагмента с помощью getActivity().getIntent().getExtras(), и это работает хорошо. - person laalto; 02.11.2012
comment
Думаю для передачи аргументов нужно создавать фрагменты в коде. Если вы определяете фрагмент в xml, я не помню, есть ли способ передать аргументы. Есть? - person havexz; 03.11.2012
comment
Я экспериментирую с выполнением setArguments(new Bundle()) в моем конструкторе с нулевым параметром для моего фрагмента, чтобы обойти проблему, связанную с невозможностью использования get/setArguments в фрагменте с экземпляром xml. - person JohnnyLambada; 11.04.2014