[android] 백 스택에 Fragments의 인스턴스 상태를 올바르게 저장하는 방법은 무엇입니까?
나는 SO에 대해 비슷한 질문을 많이 발견했지만 불행히도 내 요구 사항을 충족시키는 대답은 없습니다.
세로 및 가로에 대한 레이아웃이 다르고 백 스택을 사용하고 있습니다. 백 스택을 사용 setRetainState()
하면 구성 변경 루틴 을 사용하지 못하고 트릭합니다.
TextViews에서 사용자에게 특정 정보를 표시하지만 기본 핸들러에는 저장되지 않습니다. 활동만을 사용하여 신청서를 작성할 때 다음이 잘 작동했습니다.
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
.
또한 내 참조를 유지 TextViews
하는 것이 Fragment
s에 괜찮더라도 s에 대한 좋은 아이디어가 아니라는 것을 알았 습니다 Activity
. 이 경우 onSaveInstanceState()
실제로 상태를 저장하지만 조각이 숨겨져있을 때 화면이 두 번 회전 하면 조각이 onCreateView()
새 인스턴스에서 호출되지 않으므로 문제가 다시 나타납니다 .
상태 onDestroyView()
를 일부 Bundle
유형의 클래스 멤버 요소 (실제로 하나의 데이터가 아니라 더 많은 데이터 TextView
)에 저장하고 저장 하는onSaveInstanceState()
데 다른 단점이 있다고 생각 했습니다 . 기본적으로 프래그먼트 가 현재 표시되어 있으면 두 함수를 호출하는 순서가 반대이므로 두 가지 상황을 고려해야합니다. 더 깨끗하고 정확한 해결책이 있어야합니다!
답변
인스턴스 상태를 올바르게 저장하려면 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);
}
}
도움이 되었기를 바랍니다.
답변
이것이 내가 지금 사용하는 방식입니다 … 매우 복잡하지만 적어도 가능한 모든 상황을 처리합니다. 누군가가 관심이 있다면.
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());
}
/* (...) */
}
또는 수동으로 표시된 데이터 View
를 변수 로 유지하고 데이터 View
를 표시하는 데만 s를 사용 하여 두 항목을 동기화하는 것이 항상 가능합니다. 그래도 마지막 부분은 매우 깨끗하지 않습니다.
답변
최신 지원 라이브러리에서는 여기서 논의 된 솔루션이 더 이상 필요하지 않습니다. 를 Activity
사용하여 원하는대로 조각을 재생할 수 있습니다 FragmentTransaction
. 조각 또는 ID로 태그를 식별 할 수 있는지 확인하십시오.
를 호출 할 때마다 조각을 다시 만들지 않으면 조각이 자동으로 복원됩니다 onCreate()
. 대신, savedInstanceState
null이 아닌지 확인 하고이 경우 생성 된 조각에 대한 이전 참조를 찾아야합니다.
예를 들면 다음과 같습니다.
@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);
}
...
}
그러나 조각의 숨겨진 상태를 복원 할 때 현재 버그 가 있습니다 . 활동에서 단편을 숨기고있는 경우이 경우이 상태를 수동으로 복원해야합니다.
답변
방금 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에 저장된 동일한 번들을 나가는 번들과 재사용 하여이 문제를 해결합니다.
@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);
}
...
}
답변
Fragment가 onCreateView ()를 실행하지 않으면 뷰가 인스턴스화되지 않는다는 것을 알고 있습니다. 따라서 백 스택의 조각이 뷰를 만들지 않으면 마지막 저장 상태를 저장하고, 그렇지 않으면 저장 / 복원하려는 데이터로 자체 번들을 빌드합니다.
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
}
답변
final FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.hide(currentFragment);
ft.add(R.id.content_frame, newFragment.newInstance(context), "Profile");
ft.addToBackStack(null);
ft.commit();