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

Перекрытие скрытых фрагментов после приложения будет убито и восстановлено

Я переключаю между фрагментами скрываю последний фрагмент и добавляет новый (см. код ниже) - добавив его в back-stack. Таким образом, пользователи могут быстро переключаться между фрагментами без перезагрузки данных фрагмента.

Это хорошо работает до тех пор, пока приложение не будет убито (сценарий: пользователи используют несколько других приложений, и мое приложение постоянно сохраняется и убивается).

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

Вопрос: Как восстановить восстановленные фрагменты со скрытым состоянием? Возможно, мне не хватает какого-то флага? где-то? Возможно, есть лучшее решение для быстрого переключения между фрагментами (без перезагрузки данных)?

Пример кода добавления фрагментов - вызывается несколько раз с разными фрагментами при нажатии на него:

FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.hide(lastFragment);
fragmentTransaction.add(newFragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
lastFragment = newFragment;
4b9b3361

Ответ 1

Надеюсь, что кто-то найдет лучшее решение. Я подожду, пока не приму решение:

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

В деталях я генерирую уникальный тег для каждого фрагмента (StackEntry) и складываю теги, поскольку сами фрагменты укладываются. Я сохраняю стек в пакете и загружаю его, когда приложение восстанавливается, чтобы продолжить его использование. Затем я использую список тегов, чтобы найти все фрагменты unhidden и скрыть их - кроме последнего.

Вот пример кода:

public class FragmentActivity extends Activity {

    private static final String FRAGMENT_STACK_KEY = "FRAGMENT_STACK_KEY";

    private Stack<StackEntry> fragmentsStack = new Stack<StackEntry>();

    public FragmentActivity() {
    }

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

        setContentView(R.layout.content_frame);

        if (savedInstanceState == null) {
            // Init for the first time - not restore
            // ...
        } else {
            Serializable serializable = savedInstanceState.getSerializable(FRAGMENT_STACK_KEY);
            if (serializable != null) {
                // Workaround Android bug.
                // See: http://stackoverflow.com/questions/13982192/when-using-an-android-bundle-why-does-a-serialised-stack-deserialise-as-an-arra
                // And: https://code.google.com/p/android/issues/detail?id=3847
                @SuppressWarnings("unchecked")
                List<StackEntry> arrayList = (List<StackEntry>) serializable;
                fragmentsStack = new Stack<StackEntry>();
                fragmentsStack.addAll(arrayList);
            }

            // Hide all the restored fragments instead of the last one
            if (fragmentsStack.size() > 1) {
                FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
                for (int i = 0; i < fragmentsStack.size()-1; i++) {
                    String fragTag = fragmentsStack.get(i).getFragTag();
                    Fragment fragment = getFragmentManager().findFragmentByTag(fragTag);
                    fragmentTransaction.hide(fragment);
                }
                fragmentTransaction.commit();
            }
        }
        getFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {
            @Override
            public void onBackStackChanged() {
                Fragment lastFragment = getLastFragment();
                if (lastFragment.isHidden()) {
                    FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
                    fragmentTransaction.show(lastFragment);
                    fragmentTransaction.commit();
                }
            }
        });
    }

    private Fragment getLastFragment() {
        if (fragmentsStack.isEmpty()) return null;
        String fragTag = fragmentsStack.peek().getFragTag();
        Fragment fragment = getFragmentManager().findFragmentByTag(fragTag);
        return fragment;
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putSerializable(FRAGMENT_STACK_KEY, fragmentsStack);
    }

    @Override
    public void onBackPressed() {
        if (!fragmentsStack.isEmpty()) {
            fragmentsStack.pop();
        }
    }

    public void switchContent(Fragment fragment) {
        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        Fragment lastFragment = getLastFragment();
        if (lastFragment != null) {
            fragmentTransaction.hide(lastFragment);
        }
        String fragTag;
        if (fragment.isAdded()) {
            fragmentTransaction.show(fragment);
            fragTag = fragment.getTag();
        } else {
            fragTag = Long.toString(System.currentTimeMillis());
            fragmentTransaction.add(R.id.content_frame, fragment, fragTag);
        }
        if (!isFirstFragment()) {
            // Add to backstack only the first content fragment and not the state before (that has nothing)
            fragmentTransaction.addToBackStack(null);
        }
        fragmentTransaction.commit();

        fragmentsStack.push(new StackEntry(fragTag));
    }

    public boolean isFirstFragment() {
        return fragmentsStack.size() == 0;
    }

    private static class StackEntry implements Serializable {
        private static final long serialVersionUID = -6162805540320628024L;

        private String fragTag = null;
        public StackEntry(String fragTag) {
            super();
            this.fragTag = fragTag;
        }
        public String getFragTag() {
            return fragTag;
        }
    }


    public static class Intent extends android.content.Intent {
        public Intent(Context packageContext) {
            super(packageContext, FragmentActivity.class);
        }
    }
}

Ответ 2

У меня также была эта проблема, вот одно из возможных решений: каждый фрагмент сохраняет свое собственное состояние о том, скрыта ли она или нет, а затем скрывается в своем onCreate.

@Override
public void onSaveInstanceState(Bundle bundle) {
    super.onSaveInstanceState(bundle);
    if (this.isHidden()) {
        bundle.putBoolean("hidden",  true);
    }
}

@Override
public void onCreate(Bundle bundle) {
    super.onCreate(bundle);
    if (bundle != null) {
        if (bundle.getBoolean("hidden",  false)) {
            getFragmentManager()
                .beginTransaction()
                .hide(this)
                .commit();
        }
    }
}

Ответ 3

Наконец, я нашел самый простой способ исправить эту проблему: Измените content_frame с FramLayout на LinearLayout.

Ответ 4

У меня была такая же проблема, и я решил ее, установив setRetainInstance(true); в методе onCreate() для каждого фрагмента.

Ответ 5

У меня была такая же проблема. К сожалению, единственным хорошим решением было перейти на использование fragmentTransaction.replace вместо fragmentTransaction.hide и добавить.

Это сосало в то время, но я действительно рад, что сделал это. Это заставило меня подумать о savedInstanceState и правильно разобраться с ним. Вы упомянули, что при навигации назад фрагмент перезагружается. У меня была та же самая проблема, которая заставляла меня правильно обращаться с savedInstanceState. Там есть 2 случая.

  • Если действие не было уничтожено, единственное, что нужно было воссоздать, - это представление (через onCreateView), все остальное все еще находится в памяти, поэтому речь идет только о подключении адаптера к представлению, и вы сделано.

  • Если действие было уничтожено, мне нужно было воссоздать представление и адаптер. Чтобы свести к минимуму время загрузки, я сохранил данные, необходимые для воссоздания адаптера в savedInstanceState

Однако ваша проблема правильна, я не знаю, почему Android не поддерживает возврат с правильным скрытым состоянием из разрушенного Действия с фрагментами, которые использовали add и hide.

Ответ 6

Поскольку вы упомянули о том, что не возражаете, чтобы фрагменты перезагружались с нуля, почему бы не использовать намерение и снова начать основной процесс фрагмента?

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

Ниже описанное решение работает, по крайней мере, для меня, поэтому поделитесь им, если кто-то закончит этот сценарий

В onSaveInstanceState в родительском фрагменте я устанавливаю маркер, чтобы убедиться, что в пакете что-то есть.

public void onSaveInstanceState(Bundle outState) 
{
    // TODO Auto-generated method stub
    super.onSaveInstanceState(outState);

    outState.putString(TAG, "Get ready to be terminated");
};

И внутри onCreate вы можете указать свой класс для загрузки с помощью Intent, если состояние сохраненного экземпляра не равно null,

@Override
protected void onCreate(Bundle savedInstanceState)
{       
    super.onCreate(savedInstanceState);
    this.setContentView(R.layout.layout);

    if(savedInstanceState != null)
    {   
        Intent myIntent = new Intent(this, your.class);

        // Closing the parent fragment
        finish();

        this.startActivity(myIntent);
    }
    else
    {
        // your main code
        ...........
        ...........
    };
};

Это гарантирует, что ваши фрагменты будут воссозданы с нуля.

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

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

Примечание. Вам нужно убедиться, что ваш дочерний фрагмент не имеет свободных концов в onCreateView.

Ответ 7

Я встретил ту же проблему, я думаю, что это фреймворк Android. Вот issue.

Однако мой способ будет работать для вас, мы должны переопределить метод onSaveInstanceState(Bundle outState), сохранить наши пользовательские данные в outState, но никогда не называть super.onSaveInstanceState(outState);.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ......

    if (savedInstanceState != null) {
        mCustomVariable = savedInstanceState.getInt("variable", 0);
    }
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    //super.onSaveInstanceState(outState);
    outState.putInt("variable", mCustomVariable);
}