Android Fragments 如何正确保存后台堆栈中片段的示例状态?

rkkpypqq  于 2023-02-13  发布在  Android
关注(0)|答案(7)|浏览(186)

我发现许多类似的问题,但不幸的是没有答案符合我的要求。
我有不同的布局为纵向和横向,我正在使用回栈,这两个都阻止我使用setRetainState()和技巧使用配置更改例程。
我在TextView中向用户显示了某些信息,但这些信息不会保存在默认处理程序中。当我只使用Activities编写应用程序时,以下代码运行良好:

TextView vstup;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.whatever);
    vstup = (TextView)findViewById(R.id.whatever);
    /* (...) */
}

@Override
public void onSaveInstanceState(Bundle state) {
    super.onSaveInstanceState(state);
    state.putCharSequence(App.VSTUP, vstup.getText());
}

@Override
public void onRestoreInstanceState(Bundle state) {
    super.onRestoreInstanceState(state);
    vstup.setText(state.getCharSequence(App.VSTUP));
}

对于Fragment s,这只在非常特定的情况下才起作用,具体来说,可怕的断裂是替换一个片段,将其放入后栈,然后在显示新片段的同时旋转屏幕,从我的理解来看,旧片段在被替换时不接收对onSaveInstanceState()的调用,而是以某种方式保持与Activity的链接,并且该方法稍后在其View已经不存在了,所以查找我的任何TextView结果都会得到一个NullPointerException
此外,我发现在Fragment s中保留对TextViews的引用不是一个好主意,即使在Activity s中也是可以的。在这种情况下,onSaveInstanceState()实际上保存了状态,但如果在隐藏片段时旋转屏幕 * 两次 *,问题会再次出现,因为它的onCreateView()不会在新示例中调用。
我考虑将onDestroyView()中的状态保存到某个Bundle类型的类成员元素中(实际上是更多的数据,而不仅仅是一个TextView)并将其保存在onSaveInstanceState()中,但还有其他缺点,主要是如果片段当前显示,则两个函数的调用顺序相反,所以我需要考虑两种不同的情况。一定有一个更干净和正确的解决方案!

pbwdgjma

pbwdgjma1#

要正确保存Fragment的示例状态,应执行以下操作:

**1.**在片段中,通过覆盖onSaveInstanceState()保存示例状态,并在onActivityCreated()中恢复:

class MyFragment extends Fragment {

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        ...
        if (savedInstanceState != null) {
            //Restore the fragment's state here
        }
    }
    ...
    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        
        //Save the fragment's state here
    }

}

2.还有要点,在练习中,您必须将片段的示例保存在onSaveInstanceState()中,并在onCreate()中恢复。

class MyActivity extends Activity {

    private MyFragment 

    public void onCreate(Bundle savedInstanceState) {
        ...
        if (savedInstanceState != null) {
            //Restore the fragment's instance
            mMyFragment = getSupportFragmentManager().getFragment(savedInstanceState, "myFragmentName");
            ...
        }
        ...
    }
    
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
            
        //Save the fragment's instance
        getSupportFragmentManager().putFragment(outState, "myFragmentName", mMyFragment);
    }

}
8e2ybdfx

8e2ybdfx2#

这是一个非常古老的答案。

我不再为Android编写代码,因此不能保证最新版本的功能,也不会有任何更新。

这就是我现在使用的方法......它非常复杂,但至少它处理了所有可能的情况。如果有人感兴趣的话。

public final class MyFragment extends Fragment {
    private TextView vstup;
    private Bundle savedState = null;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.whatever, null);
        vstup = (TextView)v.findViewById(R.id.whatever);

        /* (...) */

        /* If the Fragment was destroyed inbetween (screen rotation), we need to recover the savedState first */
        /* However, if it was not, it stays in the instance from the last onDestroyView() and we don't want to overwrite it */
        if(savedInstanceState != null && savedState == null) {
            savedState = savedInstanceState.getBundle(App.STAV);
        }
        if(savedState != null) {
            vstup.setText(savedState.getCharSequence(App.VSTUP));
        }
        savedState = null;

        return v;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        savedState = saveState(); /* vstup defined here for sure */
        vstup = null;
    }

    private Bundle saveState() { /* called either from onDestroyView() or onSaveInstanceState() */
        Bundle state = new Bundle();
        state.putCharSequence(App.VSTUP, vstup.getText());
        return state;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        /* If onDestroyView() is called first, we can use the previously savedState but we can't call saveState() anymore */
        /* If onSaveInstanceState() is called first, we don't have savedState, so we need to call saveState() */
        /* => (?:) operator inevitable! */
        outState.putBundle(App.STAV, (savedState != null) ? savedState : saveState());
    }

    /* (...) */

}

或者,也可以将passive View中显示的数据保存在变量中,并将View仅用于显示它们,从而保持这两件事的同步。

vlju58qv

vlju58qv3#

在最新的支持库中,这里讨论的解决方案都不再是必要的了。你可以用你喜欢的Activity来玩你的FragmentTransaction的片段。只要确保你的片段可以用id或者标签来标识。
只要你不尝试在每次调用onCreate()时重新创建片段,片段就会自动恢复,相反,你应该检查savedInstanceState是否不为空,并在这种情况下找到对创建片段的旧引用。
下面是一个例子:

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

    if (savedInstanceState == null) {
        myFragment = MyFragment.newInstance();
        getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.my_container, myFragment, MY_FRAGMENT_TAG)
                .commit();
    } else {
        myFragment = (MyFragment) getSupportFragmentManager()
                .findFragmentByTag(MY_FRAGMENT_TAG);
    }
...
}

不过请注意,在恢复片段的隐藏状态时,当前存在bug。如果您在Activity中隐藏片段,则需要手动恢复此状态。

mrwjdhj3

mrwjdhj34#

我只想给予我从Vasek和devconsole中得到的解决方案,它可以处理本文中提到的所有情况。这个解决方案还可以处理手机多次旋转而碎片不可见的特殊情况。
下面是我存储包的位置,以备将来使用,因为onCreate和onSaveInstanceState是在片段不可见时进行的唯一调用

MyObject myObject;
private Bundle savedState = null;
private boolean createdStateInDestroyView;
private static final String SAVED_BUNDLE_TAG = "saved_bundle";

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState != null) {
        savedState = savedInstanceState.getBundle(SAVED_BUNDLE_TAG);
    }
}

由于destroyView在特殊的旋转情况下没有被调用,我们可以确定,如果它创建了状态,我们应该使用它。

@Override
public void onDestroyView() {
    super.onDestroyView();
    savedState = saveState();
    createdStateInDestroyView = true;
    myObject = null;
}

这部分也一样。

private Bundle saveState() { 
    Bundle state = new Bundle();
    state.putSerializable(SAVED_BUNDLE_TAG, myObject);
    return state;
}

现在这里是棘手的部分。在我的onActivityCreated方法中,我示例化了“myObject”变量,但是旋转发生在onActivity,并且没有调用onCreateView。因此,在这种情况下,当方向旋转不止一次时,myObject将为null。我通过重用保存在onCreate中的同一个bundle作为输出bundle来解决这个问题。

@Override
public void onSaveInstanceState(Bundle outState) {

    if (myObject == null) {
        outState.putBundle(SAVED_BUNDLE_TAG, savedState);
    } else {
        outState.putBundle(SAVED_BUNDLE_TAG, createdStateInDestroyView ? savedState : saveState());
    }
    createdStateInDestroyView = false;
    super.onSaveInstanceState(outState);
}

现在,只要使用savedState包就可以恢复状态

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    ...
    if(savedState != null) {
        myObject = (MyObject) savedState.getSerializable(SAVED_BUNDLE_TAG);
    }
    ...
}
0sgqnhkj

0sgqnhkj5#

感谢DroidT,我做了这个:
我意识到如果Fragment不执行onCreateView(),它的视图就不会示例化,所以,如果后栈上的Fragment没有创建它的视图,我就保存最后一次存储的状态,否则我就用我想要保存/恢复的数据构建我自己的bundle。
1)扩展此类:

import android.os.Bundle;
import android.support.v4.app.Fragment;

public abstract class StatefulFragment extends Fragment {

    private Bundle savedState;
    private boolean saved;
    private static final String _FRAGMENT_STATE = "FRAGMENT_STATE";

    @Override
    public void onSaveInstanceState(Bundle state) {
        if (getView() == null) {
            state.putBundle(_FRAGMENT_STATE, savedState);
        } else {
            Bundle bundle = saved ? savedState : getStateToSave();

            state.putBundle(_FRAGMENT_STATE, bundle);
        }

        saved = false;

        super.onSaveInstanceState(state);
    }

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

        if (state != null) {
            savedState = state.getBundle(_FRAGMENT_STATE);
        }
    }

    @Override
    public void onDestroyView() {
        savedState = getStateToSave();
        saved = true;

        super.onDestroyView();
    }

    protected Bundle getSavedState() {
        return savedState;
    }

    protected abstract boolean hasSavedState();

    protected abstract Bundle getStateToSave();

}

2)在你的片段中,你必须有这个:

@Override
protected boolean hasSavedState() {
    Bundle state = getSavedState();

    if (state == null) {
        return false;
    }

    //restore your data here

    return true;
}

3)例如,您可以在onActivityCreated中调用hasSavedState:

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

    if (hasSavedState()) {
        return;
    }

    //your code here
}
sbtkgmzw

sbtkgmzw6#

使用此:

private var mData: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (savedInstanceState != null) {
            mData= savedInstanceState.getString("Data")
        }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putString("mData", Data)
    }
zd287kbt

zd287kbt7#

final FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.hide(currentFragment);
ft.add(R.id.content_frame, newFragment.newInstance(context), "Profile");
ft.addToBackStack(null);
ft.commit();

相关问题