Поддержка FragmentPagerAdapter содержит ссылки на старые фрагменты

ПОСЛЕДНИЕ ИНФОРМАЦИИ:

Я сузил свою проблему до проблемы с тем, что documentManager сохранил экземпляры старых фрагментов, а мой просмотрщик не синхронизирован с моим FragmentManager. См. Эту проблему... http://code.google.com/p/android/issues/detail?id=19211#makechanges. Я до сих пор не знаю, как это решить. Любые предложения...

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

List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this, Fragment1.class.getName())); 
...
new PagerAdapter(getSupportFragmentManager(), fragments);

Реализация стандартная. Я использую библиотеки вычислений ActionBarSherlock и v4 для фрагментов.

Моя проблема заключается в том, что после выхода из приложения и открытия нескольких других приложений и возврата, фрагменты теряют ссылку на FragmentActivity (т.е. getActivity() == null). Я не могу понять, почему это происходит. Я попытался вручную установить setRetainInstance(true);, но это не поможет. Я понял, что это происходит, когда мой FragmentActivity будет уничтожен, однако это все равно, если я открою приложение, прежде чем я получу сообщение журнала. Есть ли идеи?

@Override
protected void onDestroy(){
    Log.w(TAG, "DESTROYDESTROYDESTROYDESTROYDESTROYDESTROYDESTROY");
    super.onDestroy();
}

Адаптер:

public class PagerAdapter extends FragmentPagerAdapter {
    private List<Fragment> fragments;

    public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
        super(fm);

        this.fragments = fragments;

    }

    @Override
    public Fragment getItem(int position) {

        return this.fragments.get(position);

    }

    @Override
    public int getCount() {

        return this.fragments.size();

    }

}

Один из моих фрагментов лишен, но я решил, что все это лишено и все еще не работает...

public class MyFragment extends Fragment implements MyFragmentInterface, OnScrollListener {
...

@Override
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    handler = new Handler();    
    setHasOptionsMenu(true);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    Log.w(TAG,"ATTACHATTACHATTACHATTACHATTACH");
    context = activity;
    if(context== null){
        Log.e("IS NULL", "NULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULL");
    }else{
        Log.d("IS NOT NULL", "NOTNOTNOTNOTNOTNOTNOTNOT");
    }

}

@Override
public void onActivityCreated(Bundle savedState) {
    super.onActivityCreated(savedState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.my_fragment,container, false);

    return v;
}


@Override
public void onResume(){
    super.onResume();
}

private void callService(){
    // do not call another service is already running
    if(startLoad || !canSet) return;
    // set flag
    startLoad = true;
    canSet = false;
    // show the bottom spinner
    addFooter();
    Intent intent = new Intent(context, MyService.class);
    intent.putExtra(MyService.STATUS_RECEIVER, resultReceiver);
    context.startService(intent);
}

private ResultReceiver resultReceiver = new ResultReceiver(null) {
    @Override
    protected void onReceiveResult(int resultCode, final Bundle resultData) {
        boolean isSet = false;
        if(resultData!=null)
        if(resultData.containsKey(MyService.STATUS_FINISHED_GET)){
            if(resultData.getBoolean(MyService.STATUS_FINISHED_GET)){
                removeFooter();
                startLoad = false;
                isSet = true;
            }
        }

        switch(resultCode){
        case MyService.STATUS_FINISHED: 
            stopSpinning();
            break;
        case SyncService.STATUS_RUNNING:
            break;
        case SyncService.STATUS_ERROR:
            break;
        }
    }
};

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    menu.clear();
    inflater.inflate(R.menu.activity, menu);
}

@Override
public void onPause(){
    super.onPause();
}

public void onScroll(AbsListView arg0, int firstVisible, int visibleCount, int totalCount) {
    boolean loadMore = /* maybe add a padding */
        firstVisible + visibleCount >= totalCount;

    boolean away = firstVisible+ visibleCount <= totalCount - visibleCount;

    if(away){
        // startLoad can now be set again
        canSet = true;
    }

    if(loadMore) 

}

public void onScrollStateChanged(AbsListView arg0, int state) {
    switch(state){
    case OnScrollListener.SCROLL_STATE_FLING: 
        adapter.setLoad(false); 
        lastState = OnScrollListener.SCROLL_STATE_FLING;
        break;
    case OnScrollListener.SCROLL_STATE_IDLE: 
        adapter.setLoad(true);
        if(lastState == SCROLL_STATE_FLING){
            // load the images on screen
        }

        lastState = OnScrollListener.SCROLL_STATE_IDLE;
        break;
    case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
        adapter.setLoad(true);
        if(lastState == SCROLL_STATE_FLING){
            // load the images on screen
        }

        lastState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;
        break;
    }
}

@Override
public void onDetach(){
    super.onDetach();
    if(this.adapter!=null)
        this.adapter.clearContext();

    Log.w(TAG, "DETACHEDDETACHEDDETACHEDDETACHEDDETACHEDDETACHED");
}

public void update(final int id, String name) {
    if(name!=null){
        getActivity().getSupportActionBar().setTitle(name);
    }

}

}

Метод обновления вызывается, когда пользователь взаимодействует с другим фрагментом, и getActivity возвращает значение null. Вот метод, вызываемый другим фрагментом...

((MyFragment) pagerAdapter.getItem(1)).update(id, name);

Я считаю, что когда приложение будет уничтожено, оно будет создано снова, а не только начнется приложение до фрагмента по умолчанию, запускаемого приложением, а затем viewpager перейдет на последнюю известную страницу. Это кажется странным, не следует ли приложению просто загружать фрагмент по умолчанию?

Ответ 1

У вас возникает проблема, потому что вы создаете и сохраняете ссылки на свои фрагменты вне PagerAdapter.getItem и пытаетесь использовать эти ссылки независимо от ViewPager. Как говорит Сераф, у вас есть гарантии того, что фрагмент был создан или добавлен в ViewPager в определенное время - это следует рассматривать как деталь реализации. ViewPager выполняет ленивую загрузку своих страниц; по умолчанию он загружает только текущую страницу, а другую - влево и вправо.

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

Теперь рассмотрим, что вы просмотрели несколько страниц, фрагменты A, B и C. Вы знаете, что они были добавлены в диспетчер фрагментов. Поскольку вы используете FragmentPagerAdapter, а не FragmentStatePagerAdapter, эти фрагменты будут по-прежнему добавляться (но потенциально отключены) при прокрутке на другие страницы.

Учтите, что вы затем заложили свое приложение, а затем его убили. Когда вы вернетесь, Android запомнит, что вы использовали Фрагменты A, B и C в диспетчере фрагментов и, таким образом, они воссоздавали их для вас, а затем добавляли их. Однако те, которые добавлены в диспетчер фрагментов, теперь не те, что у вас есть в списке ваших фрагментов в вашей деятельности.

FragmentPagerAdapter не будет пытаться вызвать getPosition, если уже добавлен фрагмент для этой конкретной позиции страницы. Фактически, поскольку фрагмент, воссозданный Android, никогда не будет удален, у вас нет надежды заменить его вызовом getPosition. Получить ручку на ней также довольно сложно получить ссылку на нее, потому что она была добавлена ​​с тегом, который вам неизвестен. Это по дизайну; вам не рекомендуется возиться с фрагментами, которыми управляет просмотр пейджер. Вы должны выполнять все свои действия внутри фрагмента, обмениваться информацией с активностью и запрашивать при необходимости переход на конкретную страницу.

Теперь вернемся к вашей проблеме с отсутствующей активностью. Вызов pagerAdapter.getItem(1)).update(id, name) после того, как все это произошло, возвращает фрагмент в вашем списке , который еще не добавлен в диспетчер фрагментов, и поэтому он не будет иметь ссылку на активность. Я бы предположил, что ваш метод обновления должен изменить какую-либо общую структуру данных (возможно, управляемую деятельностью), а затем, когда вы переходите на конкретную страницу, она может нарисовать себя на основе этих обновленных данных.

Ответ 2

Я нашел простое решение, которое сработало для меня.

Сделайте адаптер фрагмента, расширяет FragmentStatePagerAdapter вместо FragmentPagerAdapter и переопределяет метод onSave для возврата null

@Override
public Parcelable saveState()
{
    return null;
}

Это предотвратит андроид от воссоздания фрагмента


Через день я нашел другое и лучшее решение.

Вызвать setRetainInstance(true) для всех ваших фрагментов и сохранить ссылки на них где-нибудь. Я сделал это в статической переменной в своей деятельности, потому что он объявлен как singleTask, и фрагменты могут оставаться неизменными все время.

Таким образом, андроид не воссоздает фрагменты, а использует те же экземпляры.

Ответ 3

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

private String getFragmentTag(int pos){
    return "android:switcher:"+R.id.viewpager+":"+pos;
}

Затем я просто получаю ссылку на этот фрагмент и делаю то, что мне нужно, так...

Fragment f = this.getSupportFragmentManager().findFragmentByTag(getFragmentTag(1));
((MyFragmentInterface) f).update(id, name);
viewPager.setCurrentItem(1, true);

Внутри моих фрагментов я устанавливаю setRetainInstance(false);, чтобы вручную добавлять значения в пакет savedInstanceState.

@Override
public void onSaveInstanceState(Bundle outState) {
    if(this.my !=null)
        outState.putInt("myId", this.my.getId());

    super.onSaveInstanceState(outState);
}

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

Ответ 4

Глобальное рабочее тестируемое решение.

getSupportFragmentManager() хранит нулевую ссылку несколько раз, а пейджер View не создает новый, поскольку находит ссылку на тот же фрагмент. Таким образом, использование getChildFragmentManager() позволяет решить проблему простым способом.

Не делайте этого:

new PagerAdapter(getSupportFragmentManager(), fragments);

Сделайте это:

new PagerAdapter(getChildFragmentManager() , fragments);

Ответ 5

Не пытайтесь взаимодействовать между фрагментами в ViewPager. Вы не можете гарантировать, что другой фрагмент присоединен или даже существует. Вместо того, чтобы изменять заголовок панели действий из фрагмента, вы можете сделать это из своей активности. Для этого используйте стандартный шаблон интерфейса:

public interface UpdateCallback
{
    void update(String name);
}

public class MyActivity extends FragmentActivity implements UpdateCallback
{
    @Override
    public void update(String name)
    {
        getSupportActionBar().setTitle(name);
    }

}

public class MyFragment extends Fragment
{
    private UpdateCallback callback;

    @Override
    public void onAttach(SupportActivity activity)
    {
        super.onAttach(activity);
        callback = (UpdateCallback) activity;
    }

    @Override
    public void onDetach()
    {
        super.onDetach();
        callback = null;
    }

    public void updateActionbar(String name)
    {
        if(callback != null)
            callback.update(name);
    }
}

Ответ 6

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

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

В этот момент активность будет создана снова, и в этот момент все будет работать нормально, но я потерял ссылку на мои fragmetns, поскольку getItem не получает вызов снова.

Вот как я исправил эту проблему внутри FragmentStatePagerAdapter:

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Object aux = super.instantiateItem(container, position);

        //Update the references to the Fragments we have on the view pager
        if(position==0){
            fragTabOne = (FragOffersList)aux;
        }
        else{
            fragTabTwo = (FragOffersList) aux;
        }

        return aux;
    }

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

Надеюсь, что кто-то поможет.

Ответ 7

Вы можете удалить фрагменты при уничтожении viewpager, в моем случае я удалил их на onDestroyView() моего фрагмента:

@Override
public void onDestroyView() {

    if (getChildFragmentManager().getFragments() != null) {
        for (Fragment fragment : getChildFragmentManager().getFragments()) {
            getChildFragmentManager().beginTransaction().remove(fragment).commitAllowingStateLoss();
        }
    }

    super.onDestroyView();
}

Ответ 8

Так как FragmentManager позаботится о восстановлении ваших фрагментов для вас, как только будет вызываться метод onResume(), у меня есть фрагмент, вызывающий активность, и добавьте себя в список. В моем случае я сохраняю все это в своей реализации PagerAdapter. Каждый фрагмент знает его позицию, потому что он добавляется к аргументам фрагмента при создании. Теперь, когда мне нужно манипулировать фрагментом по определенному индексу, все, что мне нужно сделать, это использовать список из моего адаптера.

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

Адаптер

public class GrowPagerAdapter extends FragmentPagerAdapter implements OnPageChangeListener, OnScrollChangedListener {

public final String TAG = this.getClass().getSimpleName();

private final int COUNT = 4;

public static final float BASE_SIZE = 0.8f;
public static final float BASE_ALPHA = 0.8f;

private int mCurrentPage = 0;
private boolean mScrollingLeft;

private List<SummaryTabletFragment> mFragments;

public int getCurrentPage() {
    return mCurrentPage;
}

public void addFragment(SummaryTabletFragment fragment) {
    mFragments.add(fragment.getPosition(), fragment);
}

public GrowPagerAdapter(FragmentManager fm) {
    super(fm);

    mFragments = new ArrayList<SummaryTabletFragment>();
}

@Override
public int getCount() {
    return COUNT;
}

@Override
public Fragment getItem(int position) {
    return SummaryTabletFragment.newInstance(position);
}

@Override
public void onPageScrollStateChanged(int state) {}

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    adjustSize(position, positionOffset);
}

@Override
public void onPageSelected(int position) {
    mCurrentPage = position;
}

/**
 * Used to adjust the size of each view in the viewpager as the user
 * scrolls.  This provides the effect of children scaling down as they
 * are moved out and back to full size as they come into focus.
 * 
 * @param position
 * @param percent
 */
private void adjustSize(int position, float percent) {

    position += (mScrollingLeft ? 1 : 0);
    int secondary = position + (mScrollingLeft ? -1 : 1);
    int tertiary = position + (mScrollingLeft ? 1 : -1);

    float scaleUp = mScrollingLeft ? percent : 1.0f - percent;
    float scaleDown = mScrollingLeft ? 1.0f - percent : percent;

    float percentOut = scaleUp > BASE_ALPHA ? BASE_ALPHA : scaleUp;
    float percentIn = scaleDown > BASE_ALPHA ? BASE_ALPHA : scaleDown;

    if (scaleUp < BASE_SIZE)
        scaleUp = BASE_SIZE;

    if (scaleDown < BASE_SIZE)
        scaleDown = BASE_SIZE;

    // Adjust the fragments that are, or will be, on screen
    SummaryTabletFragment current = (position < mFragments.size()) ? mFragments.get(position) : null;
    SummaryTabletFragment next = (secondary < mFragments.size() && secondary > -1) ? mFragments.get(secondary) : null;
    SummaryTabletFragment afterNext = (tertiary < mFragments.size() && tertiary > -1) ? mFragments.get(tertiary) : null;

    if (current != null && next != null) {

        // Apply the adjustments to each fragment
        current.transitionFragment(percentIn, scaleUp);
        next.transitionFragment(percentOut, scaleDown);

        if (afterNext != null) {
            afterNext.transitionFragment(BASE_ALPHA, BASE_SIZE);
        }
    }
}

@Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {

    // Keep track of which direction we are scrolling
    mScrollingLeft = (oldl - l) < 0;
}
}

Фрагмент

public class SummaryTabletFragment extends BaseTabletFragment {

public final String TAG = this.getClass().getSimpleName();

private final float SCALE_SIZE = 0.8f;

private RelativeLayout mBackground, mCover;
private TextView mTitle;
private VerticalTextView mLeft, mRight;

private String mTitleText;
private Integer mColor;

private boolean mInit = false;
private Float mScale, mPercent;

private GrowPagerAdapter mAdapter;
private int mCurrentPosition = 0;

public String getTitleText() {
    return mTitleText;
}

public void setTitleText(String titleText) {
    this.mTitleText = titleText;
}

public static SummaryTabletFragment newInstance(int position) {

    SummaryTabletFragment fragment = new SummaryTabletFragment();
    fragment.setRetainInstance(true);

    Bundle args = new Bundle();
    args.putInt("position", position);
    fragment.setArguments(args);

    return fragment;
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);

    mRoot = inflater.inflate(R.layout.tablet_dummy_view, null);

    setupViews();
    configureView();

    return mRoot;
}

@Override
public void onViewStateRestored(Bundle savedInstanceState) {
    super.onViewStateRestored(savedInstanceState);

    if (savedInstanceState != null) {
        mColor = savedInstanceState.getInt("color", Color.BLACK);
    }

    configureView();
}

@Override
public void onSaveInstanceState(Bundle outState)  {

    outState.putInt("color", mColor);

    super.onSaveInstanceState(outState);
}

@Override
public int getPosition() {
    return getArguments().getInt("position", -1);
}

@Override
public void setPosition(int position) {
    getArguments().putInt("position", position);
}

public void onResume() {
    super.onResume();

    mAdapter = mActivity.getPagerAdapter();
    mAdapter.addFragment(this);
    mCurrentPosition = mAdapter.getCurrentPage();

    if ((getPosition() == (mCurrentPosition + 1) || getPosition() == (mCurrentPosition - 1)) && !mInit) {
        mInit = true;
        transitionFragment(GrowPagerAdapter.BASE_ALPHA, GrowPagerAdapter.BASE_SIZE);
        return;
    }

    if (getPosition() == mCurrentPosition && !mInit) {
        mInit = true;
        transitionFragment(0.00f, 1.0f);
    }
}

private void setupViews() {

    mCover = (RelativeLayout) mRoot.findViewById(R.id.cover);
    mLeft = (VerticalTextView) mRoot.findViewById(R.id.title_left);
    mRight = (VerticalTextView) mRoot.findViewById(R.id.title_right);
    mBackground = (RelativeLayout) mRoot.findViewById(R.id.root);
    mTitle = (TextView) mRoot.findViewById(R.id.title);
}

private void configureView() {

    Fonts.applyPrimaryBoldFont(mLeft, 15);
    Fonts.applyPrimaryBoldFont(mRight, 15);

    float[] size = UiUtils.getScreenMeasurements(mActivity);
    int width = (int) (size[0] * SCALE_SIZE);
    int height = (int) (size[1] * SCALE_SIZE);

    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
    mBackground.setLayoutParams(params);

    if (mScale != null)
        transitionFragment(mPercent, mScale);

    setRandomBackground();

    setTitleText("Fragment " + getPosition());

    mTitle.setText(getTitleText().toUpperCase());
    mLeft.setText(getTitleText().toUpperCase());
    mRight.setText(getTitleText().toUpperCase());

    mLeft.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            mActivity.showNextPage();
        }
    });

    mRight.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            mActivity.showPrevPage();
        }
    });
}

private void setRandomBackground() {

    if (mColor == null) {
        Random r = new Random();
        mColor = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255));
    }

    mBackground.setBackgroundColor(mColor);
}

public void transitionFragment(float percent, float scale) {

    this.mScale = scale;
    this.mPercent = percent;

    if (getView() != null && mCover != null) {

        getView().setScaleX(scale);
        getView().setScaleY(scale);

        mCover.setAlpha(percent);
        mCover.setVisibility((percent <= 0.05f) ? View.GONE : View.VISIBLE);
    }
}

@Override
public String getFragmentTitle() {
    return null;
}
}

Ответ 9

Мое решение: я устанавливаю почти каждый Вид как static. Теперь мое приложение прекрасно взаимодействует. Возможность называть статические методы повсюду, может быть, не очень хороший стиль, но зачем играть с кодом, который не работает? Я прочитал много вопросов и их ответы здесь на SO, и ни одно решение не принесло успеха (для меня).

Я знаю, что это может привести к утечке памяти и пустой кучи, и мой код не подойдет для других проектов, но я не чувствую страха об этом - я тестировал приложение на разных устройствах и условиях, никаких проблем вообще, платформа Android, похоже, справляется с этим. Пользовательский интерфейс обновляется каждую секунду и даже на устройстве S2 ICS (4.0.3), приложение может обрабатывать тысячи геомаркеров.

Ответ 10

Я столкнулся с той же проблемой, но мой ViewPager находился внутри TopFragment, который создал и установил адаптер с помощью setAdapter(new FragmentPagerAdapter(getChildFragmentManager())).

Я исправил эту проблему, переопределив onAttachFragment(Fragment childFragment) в TopFragment следующим образом:

@Override
public void onAttachFragment(Fragment childFragment) {
    if (childFragment instanceof OnboardingDiamondsFragment) {
        mChildFragment = (ChildFragment) childFragment;
    }

    super.onAttachFragment(childFragment);
}

Как уже известно (см. ответы выше), когда childFragmentManager воссоздает себя, он также создает фрагменты, которые были внутри viewPager.
Важная часть состоит в том, что после этого он называет onAttachFragment, и теперь у нас есть ссылка на новый воссозданный фрагмент!

Надеюсь, это поможет любому, кто получит этот старый Q, как я:)

Ответ 11

Я решил проблему, сохранив фрагменты в SparceArray:

public abstract class SaveFragmentsPagerAdapter extends FragmentPagerAdapter {

    SparseArray<Fragment> fragments = new SparseArray<>();

    public SaveFragmentsPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = (Fragment) super.instantiateItem(container, position);
        fragments.append(position, fragment);
        return fragment;
    }

    @Nullable
    public Fragment getFragmentByPosition(int position){
        return fragments.get(position);
    }

}

Ответ 12

Просто чтобы ты знал...

В дополнение к списку проблем с этими классами, есть довольно интересная ошибка, которой стоит поделиться.

Я использую ViewPager для навигации по дереву элементов (выберите элемент, и пейджер просмотра анимирует прокрутку вправо, и появится следующая ветвь, вернитесь назад, и ViewPager прокручивает в обратном направлении, чтобы вернуться к предыдущему узлу),

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

У меня нет чистого обходного пути. Я использовал что-то вроде этого:

  public void onSaveInstanceState(Bundle outState) {
    IFragmentListener listener = (IFragmentListener)getActivity();
    if (listener!= null)
    {
        if (!listener.isStillInTheAdapter(this.getAdapterItem()))
        {
            return; // return empty state.
        }

    }
    super.onSaveInstanceState(outState);

    // normal saving of state for flips and 
    // paging out of the activity follows
    ....
  }

Несовершенное решение, потому что новый экземпляр фрагмента все еще получает Bundle SavedState, но по крайней мере он не несет устаревших данных.

Ответ 13

Поскольку люди не склонны читать комментарии, вот ответ, который в основном повторяет то, что я написал здесь:

Основной причиной проблемы является тот факт, что система Android не вызывает getItem для получения фрагментов, которые на самом деле отображаются, но instantiateItem. Этот метод сначала пытается найти и повторно использовать экземпляр фрагмента для данной вкладки в FragmentManager. Только если этот поиск завершается неудачно (что происходит только в первый раз, когда FragmentManager создается заново), вызывается getItem. По понятным причинам нельзя воссоздавать фрагменты (которые могут быть тяжелыми), например, каждый раз, когда пользователь поворачивает свое устройство.
Чтобы решить эту проблему, вместо создания фрагментов с Fragment.instantiate в вашей деятельности, вы должны сделать это с pagerAdapter.instantiateItem и все эти вызовы должны быть окружены startUpdate/finishUpdate метода startUpdate/finishUpdate которые startUpdate/finishUpdate транзакцию фрагмента соответственно. getItem должен быть местом, где фрагменты действительно создаются с использованием их соответствующих конструкторов.

List<Fragment> fragments = new Vector<Fragment>();

@Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.myLayout);
        ViewPager viewPager = (ViewPager) findViewById(R.id.myViewPager);
        MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(adapter);
        ((TabLayout) findViewById(R.id.tabs)).setupWithViewPager(viewPager);

        adapter.startUpdate(viewPager);
        fragments.add(adapter.instantiateItem(viewPager, 0));
        fragments.add(adapter.instantiateItem(viewPager, 1));
        // and so on if you have more tabs...
        adapter.finishUpdate(viewPager);
}

class MyPagerAdapter extends FragmentPagerAdapter {

        public MyPagerAdapter(FragmentManager manager) {super(manager);}

        @Override public int getCount() {return 2;}

        @Override public Fragment getItem(int position) {
            if (position == 0) return new Fragment0();
            if (position == 1) return new Fragment1();
            return null;  // or throw some exception
        }

        @Override public CharSequence getPageTitle(int position) {
            if (position == 0) return getString(R.string.tab0);
            if (position == 1) return getString(R.string.tab1);
            return null;  // or throw some exception
        }
}