findFragmentByTag null для фрагмента A, если setRetain (true) для фрагмента B

Моя проблема связана с деятельностью, содержащей три фрагмента поддержки. Один - это обычный программный фрагмент (назовем его домашним фрагментом). Один - это фрагмент портрета, добавляемый поверх исходного фрагмента при ориентации устройства, а второй - «без головы» для продолжения асинхронной задачи независимо от изменений конфигурации. Очень просто, я работал над этим прекрасным примером.

public class HeadlessCustomerDetailFetchFragment extends Fragment{
private RequestCustomerDetails mRequest;
private AsyncFetchCustomerDetails mAsyncFetchCustomerDetails;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setRetainInstance(true);

    mRequest = (RequestCustomerDetails)getActivity();
}

public void startFetching(String scannedBarcode) {
    if(mAsyncFetchCustomerDetails != null && mAsyncFetchCustomerDetails.getStatus() == AsyncTask.Status.RUNNING) return;

    if(mAsyncFetchCustomerDetails == null || mAsyncFetchCustomerDetails.getStatus() == AsyncTask.Status.FINISHED)
        mAsyncFetchCustomerDetails = new AsyncFetchCustomerDetails(getActivity(), mRequest, mPartner, scannedBarcode);
}

public void stopFetching() {
    if(mAsyncFetchCustomerDetails != null && mAsyncFetchCustomerDetails.getStatus() != AsyncTask.Status.RUNNING) return;
    mAsyncFetchCustomerDetails.cancel(true);
}

}

В моем действии onCreate () я создаю и при необходимости добавляю фрагмент без заголовка.

 mHeadlessCustomerDetailFetchFragment = (HeadlessCustomerDetailFetchFragment)getSupportFragmentManager()
            .findFragmentByTag(HeadlessCustomerDetailFetchFragment.class.getSimpleName());

if(mHeadlessCustomerDetailFetchFragment == null) {
         mHeadlessCustomerDetailFetchFragment = HeadlessCustomerDetailFetchFragment.instantiate(this, HeadlessCustomerDetailFetchFragment.class.getName());
    getSupportFragmentManager().beginTransaction()
            .add(mHeadlessCustomerDetailFetchFragment, mHeadlessCustomerDetailFetchFragment.getClass().getSimpleName())
            .commit();
    getSupportFragmentManager().executePendingTransactions();
        id = null;
    }

Затем я запускаю асинхронную задачу (через мою функцию startFetching ()) после 6-секундной задержки (для тестирования), запущенной в onCreateView () фрагмента портрета, который добавляется при изменении ориентации на портретную. Изменение ориентации обнаруживается в onCreate () действия:

if (savedInstanceState == null) { 
   // Do some initial stuff for the home fragment
} 
else {
    getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
    if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
        //Launch portrait fragment
        FragmentLauncher.launchPortraitFragment(this);
    }

Когда задача завершена, я возвращаюсь к действию и пытаюсь обновить пользовательский интерфейс активного фрагмента портрета, но диспетчер фрагментов не может его найти, findFragmentByTag () возвращает null.

Чтобы было ясно:

  • Тег правильный
  • Фрагмент обнаруживается, если я не ориентирую устройство, а вместо этого запускаю асинхронную задачу в другом месте, например, во время действия onResume ().
  • Если я не скажу фрагменту без головы, чтобы он сохранил себя - таким образом, теряя возможность не воссоздавать его, фрагмент портрета также будет правильно найден.
  • Отладка. Я вижу все 3 фрагмента в диспетчере если безголовый не настроен на сохранение самого себя. Если это так, я могу только увидеть фрагмент без головы.

Может быть, сохранение фрагмента агрессивно убивает другие фрагменты, которые не сохраняются, или что-то в этом роде?


person Daniel Wilson    schedule 30.10.2015    source источник
comment
Почему нельзя просто выполнить асинхронную задачу в сервисе? Я считаю, что это устраняет проблему с ротацией, а также является более изящным способом ее решения?   -  person Smashing    schedule 03.11.2015
comment
Я тоже так думаю - я мало знаю об услугах, и это казалось излишним для одной задачи, хотя, возможно, это был мой единственный вариант - разберусь, спасибо   -  person Daniel Wilson    schedule 03.11.2015
comment
Холодные бобы. Дай мне знать, если тебе понадобится помощь.   -  person Smashing    schedule 03.11.2015
comment
Может быть, setRetained изменит способ popBackStackImmediate(name, flags) работы? Я могу попробовать удалить или изменить getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);   -  person Leo Landau    schedule 05.11.2015
comment
попробуйте stackoverflow.com/questions/33311354/   -  person Mohammad Hossein Gerami    schedule 05.11.2015


Ответы (1)


Корень проблемы заключается в том, как вы поддерживаете ссылку на активность внутри фрагмента без заголовка.
Из предоставленного кода не ясно, как вы обновляете пользовательский интерфейс после завершения AsyncTask, допустим, вы используете mRequest из первого фрагмента кода. Вы передаете mRequest конструктору, когда вам нужна новая AsyncTask, и используете эту ссылку после завершения AsyncTask.
Это нормально, когда у вас нет поворота экрана между моментом создания активности и обновлением пользовательского интерфейса. Это потому, что вы используете ссылку на действие, которое все еще активно.
Это не нормально, если вы поворачиваете экран. Каждый раз после ротации у вас появляется новая активность. Но mRequest назначается только один раз, когда вы создаете фрагмент без заголовка при первом вызове активности onCreate(). Таким образом, он содержит ссылку на первый экземпляр активности, который не активен после вращения. В вашем случае есть 2 экземпляра активности после поворота: первый - на который ссылается mRequest, а второй - который является видимым и активным. Вы можете подтвердить это, зарегистрировав ссылку на активность внутри onCreate: Log.i(TAG, "onCreate: this=" + this); и внутри метода активности, который обновляет пользовательский интерфейс после асинхронной задачи: Log.i(TAG, "updating UI: this=" + this);
Кроме того, первое действие находится в состоянии «Уничтожено». Все фрагменты отделяются от этой активности, а несохраненные фрагменты уничтожаются. Вот почему findFragmentByTag возвращает значение null.
Если фрагмент без заголовка не настроен на сохранение самого себя, то onCreate() активности воссоздает его при каждом вызове. Таким образом, mRequest всегда ссылается на последнее созданное действие со всеми фрагментами. В этом случае findFragmentByTag возвращает не null.

Чтобы избежать этой проблемы, я предлагаю:

  1. Используйте слабую ссылку для хранения ссылки на Activity. Примерно так:
    private WeakReference<RequestCustomerDetails> mRequest;
  2. Создайте в HeadlessCustomerDetailFetchFragment метод для обновления этого справочника.
    public void updateResultProcessor(RequestCustomerDetails requestCustomerDetails) { mRequest = new WeakReference(requestCustomerDetails); // Update ui if there is stored result of AsyncTask (see p.4b) }
  3. Вызывайте этот метод из onCreate () действия каждый раз.
  4. По завершении AsyncTask:
    a) если mRequest.get() не null, обновите пользовательский интерфейс.
    b) если mRequest.get() равно null, сохраните результат внутри фрагмента без заголовка и используйте его на стр.2.

    Слабая ссылка будет разрешить GC обрабатывать уничтоженную активность и установить ноль внутри слабой ссылки. Нулевое значение внутри слабой ссылки будет сигнализировать о том, что пользовательского интерфейса нет и обновлять нечего. Сохранение результата AsyncTask в безголовом фрагменте позволит использовать этот результат для обновления пользовательского интерфейса после его воссоздания.

    Надеюсь, это поможет. Извините за мой английский. Если что-то непонятно, постараюсь объяснить.
person Nick S    schedule 08.11.2015
comment
Мы рассмотрим это позже, но вот награда, это очень похоже на правильный ответ, поэтому спасибо - person Daniel Wilson; 10.11.2015