source

Fragments의 인스턴스 상태를 백스택에 올바르게 저장하는 방법은 무엇입니까?

manysource 2023. 6. 9. 22:08

Fragments의 인스턴스 상태를 백스택에 올바르게 저장하는 방법은 무엇입니까?

SO에서 유사한 질문의 사례를 많이 찾았지만 안타깝게도 제 요구 사항을 충족하는 답변이 없습니다.

백스택을 , 은 둘 다 할 수 되어 .setRetainState()구성 변경 루틴을 사용하는 트릭입니다.

사용자에게 텍스트 보기에서 특정 정보를 표시하지만 기본 핸들러에는 저장되지 않습니다.활동만을 사용하여 응용프로그램을 작성할 때 다음과 같은 작업이 잘 수행되었습니다.

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, 이은매특상작만서동다니합황에정우한것다▁s작▁only.특히, 끔찍하게 부서지는 것은 조각을 교체하고, 그것을 백스택에 넣은 다음, 새로운 조각이 표시되는 동안 화면을 회전시키는 것입니다.제가 이해한 바로는, 오래된 파편은 다음과 같은 전화를 받지 않습니다.onSaveInstanceState()되어 있을 때.Activity그리고 이 방법은 나중에 호출됩니다.View이상 하지 않으므로 제 더 이 존 재 하 때 않 문 에 찾 것 있 고 어 습 도 니 떤 다 나 의 상 기 지 있 습 니 다 ▁of ▁my ▁does 찾 고 ▁for ▁any 것 ▁so 도 더 ▁looking , ▁anymoreTextView결과가 a로 나타남NullPointerException.

저는 저의 한또, 나나참유조지것다발에 대한 를 유지하는 것을 발견했습니다.TextViews에 대한 좋은 생각이 아닙니다.Fragments, 설령 그것이 괜찮았다 하더라도Activity s. 그다면렇,onSaveInstanceState()실제로 상태를 저장하지만 파편이 숨겨져 있을 때 화면을 두 번 돌리면 문제가 다시 나타납니다.onCreateView()새 인스턴스에서 호출되지 않습니다.

나는 주를 구하려고 생각했습니다.onDestroyView() 일부로에.Bundleelement (는 하나의 더 TextView) 그리고 그것을 저장합니다.onSaveInstanceState()하지만 다른 단점도 있습니다.우선 현재 fragment가 표시되어 있다면 두 함수를 호출하는 순서가 뒤바뀌어 두 가지 상황을 설명해야 합니다.더 깨끗하고 올바른 해결책이 있어야 합니다!

Fragment다음을 수행해야 합니다.

조각에서 인스턴스 상태를 재정의하여 저장합니다.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
    }

}

중요한 점은, 활동에서 조각의 인스턴스를 저장해야 한다는 것입니다.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를 넣고 s를 합니다.View두 가지를 동시에 유지하면서 표시하는 데만 사용됩니다.하지만 마지막 부분은 그다지 깨끗하지 않다고 생각합니다.

최신 지원 라이브러리에서는 여기서 설명하는 솔루션이 더 이상 필요하지 않습니다.당신은 당신의 것을 가지고 놀 수 있습니다.Activity 사용할 수 있습니다.FragmentTransactionID 또는 태그로 조각을 식별할 수 있는지 확인하십시오.

호출할 때마다 조각을 재생성하지 않는 한 조각은 자동으로 복원됩니다.onCreate()대신에, 당신은 확인해야 합니다.savedInstanceState는 null이 아니며 이 경우 생성된 fragment에 대한 이전 참조를 찾습니다.

다음은 예입니다.

@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);
    }
...
}

그러나 현재 조각의 숨겨진 상태를 복원할 때 버그가 있습니다.활동에서 조각을 숨기는 경우 이 경우 이 상태를 수동으로 복원해야 합니다.

저는 제가 바섹과 개발 콘솔에서 도출한 이 게시물에 제시된 모든 사례를 처리할 수 있는 해결책을 제시하고자 합니다.이 솔루션은 파편이 보이지 않는 상태에서 전화기를 두 번 이상 돌릴 때도 특수한 경우를 처리합니다.

fragment가 표시되지 않을 때 호출되는 것은 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;
}

이제 여기서 까다로운 부분이 있습니다.my onActivityCreated 메서드에서 "myObject" 변수를 인스턴스화하지만 Activity에서 회전이 발생하고 CreateView에서 호출되지 않습니다.따라서 방향이 두 번 이상 회전할 경우 myObject는 null이 됩니다.Create에 저장된 동일한 번들을 발신 번들로 재사용하여 이 문제를 해결합니다.

    @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);
}

이제 상태를 복원하려는 곳이라면 어디든 저장된 상태 번들을 사용하십시오.

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

DroidT 덕분에 이렇게 만들었습니다.

Fragment가 CreateView()에서 실행되지 않으면 뷰가 인스턴스화되지 않습니다.따라서 백스택의 조각이 뷰를 생성하지 않은 경우 마지막으로 저장된 상태를 저장하고, 그렇지 않은 경우 저장/복원할 데이터로 자체 번들을 구축합니다.

이 클래스 확장:

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();

}

조각에는 다음이 있어야 합니다.

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

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

    //restore your data here

    return true;
}

예를 들어 ActivityCreated의 hasSavedState를 호출할 수 있습니다.

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

    if (hasSavedState()) {
        return;
    }

    //your code here
}

사용 방법:

  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)
    }
final FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.hide(currentFragment);
ft.add(R.id.content_frame, newFragment.newInstance(context), "Profile");
ft.addToBackStack(null);
ft.commit();

언급URL : https://stackoverflow.com/questions/15313598/how-to-correctly-save-instance-state-of-fragments-in-back-stack