백 스택에 추가 될 때 어떻게 프래그먼트 상태를 유지할 수 있습니까? 위해 완전한 코드

두 조각 사이를 전환하는 더미 활동을 작성했습니다. FragmentA에서 FragmentB로 이동하면 FragmentA가 백 스택에 추가됩니다. 그러나 FragmentA로 돌아 가면 (뒤로 누름) 완전히 새로운 FragmentA가 만들어지고 상태가 손실됩니다. 질문 과 같은 것을 겪고 있다는 느낌 들지만 문제를 근절하기 위해 완전한 코드 샘플을 포함 시켰습니다.

public class FooActivity extends Activity {
  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    final FragmentTransaction transaction = getFragmentManager().beginTransaction();
    transaction.replace(android.R.id.content, new FragmentA());
    transaction.commit();
  }

  public void nextFragment() {
    final FragmentTransaction transaction = getFragmentManager().beginTransaction();
    transaction.replace(android.R.id.content, new FragmentB());
    transaction.addToBackStack(null);
    transaction.commit();
  }

  public static class FragmentA extends Fragment {
    @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
      final View main = inflater.inflate(R.layout.main, container, false);
      main.findViewById(R.id.next_fragment_button).setOnClickListener(new View.OnClickListener() {
        public void onClick(View v) {
          ((FooActivity) getActivity()).nextFragment();
        }
      });
      return main;
    }

    @Override public void onSaveInstanceState(Bundle outState) {
      super.onSaveInstanceState(outState);
      // Save some state!
    }
  }

  public static class FragmentB extends Fragment {
    @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
      return inflater.inflate(R.layout.b, container, false);
    }
  }
}

일부 로그 메시지가 추가 된 경우 :

07-05 14:28:59.722 D/OMG     ( 1260): FooActivity.onCreate
07-05 14:28:59.742 D/OMG     ( 1260): FragmentA.onCreateView
07-05 14:28:59.742 D/OMG     ( 1260): FooActivity.onResume
<Tap Button on FragmentA>
07-05 14:29:12.842 D/OMG     ( 1260): FooActivity.nextFragment
07-05 14:29:12.852 D/OMG     ( 1260): FragmentB.onCreateView
<Tap 'Back'>
07-05 14:29:16.792 D/OMG     ( 1260): FragmentA.onCreateView

FragmentA.onSaveInstanceState를 호출하지 않으며 되돌릴 때 새 FragmentA를 만듭니다. 그러나 FragmentA를 사용 중이고 화면을 잠그면 FragmentA.onSaveInstanceState가 호출됩니다. 너무 이상합니다 … 다시 생성 할 필요가 없도록 백 스택에 조각을 추가 할 것으로 잘못 생각합니까? 문서에서 말하는 내용은 다음과 같습니다 .

조각을 제거 할 때 addToBackStack ()을 호출하면 조각이 중지되고 사용자가 다시 탐색하면 조각이 다시 시작됩니다.



답변

당신이 다시 스택에서 단편으로 돌아 경우는 조각을 다시 만듭니다 것이 아니라 함께 같은 인스턴스 시작 재 – 사용 onCreateView()조각 라이프 사이클에서 볼 조각 수명주기를 .

따라서 상태를 저장하려면 인스턴스 변수를 사용해야하며 의존 하지 않아야 합니다 onSaveInstanceState().


답변

Apple UINavigationController과 및에 비해 UIViewControllerGoogle은 Android 소프트웨어 아키텍처에서 잘하지 않습니다. 그리고 안드로이드의 문서 Fragment는별로 도움이되지 않습니다.

FragmentA에서 FragmentB를 입력하면 기존 FragmentA 인스턴스가 삭제되지 않습니다. FragmentB에서 뒤로를 누르고 FragmentA로 돌아 가면 새 FragmentA 인스턴스가 생성되지 않습니다. 기존 FragmentA 인스턴스 onCreateView()가 호출됩니다.

핵심 onCreateView()은 기존 FragmentA의 인스턴스를 사용하고 있기 때문에 FragmentA의 뷰를 다시 팽창시키지 않아야한다는 것입니다 . rootView를 저장하고 재사용해야합니다.

다음 코드는 잘 작동합니다. 조각 상태를 유지할뿐만 아니라 RAM과 CPU 부하도 줄입니다 (필요한 경우 레이아웃 만 팽창시키기 때문). 나는 구글의 샘플 코드와 문서가 그것을 언급하지는 않지만 항상 레이아웃을 팽창 시킨다고 믿을 수 없다 .

버전 1 (버전 1을 사용하지 마십시오. 버전 2를 사용하십시오)

public class FragmentA extends Fragment {
    View _rootView;
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (_rootView == null) {
            // Inflate the layout for this fragment
            _rootView = inflater.inflate(R.layout.fragment_a, container, false);
            // Find and setup subviews
            _listView = (ListView)_rootView.findViewById(R.id.listView);
            ...
        } else {
            // Do not inflate the layout again.
            // The returned View of onCreateView will be added into the fragment.
            // However it is not allowed to be added twice even if the parent is same.
            // So we must remove _rootView from the existing parent view group
            // (it will be added back).
            ((ViewGroup)_rootView.getParent()).removeView(_rootView);
        }
        return _rootView;
    }
}

—— 2005 년 5 월 3 일 업데이트 : ——-

언급 한 바와 같이 때때로 _rootView.getParent()에서 null onCreateView이 발생하여 충돌이 발생합니다. dell116이 제안한대로 버전 2는 onDestroyView ()에서 _rootView를 제거합니다. 안드로이드 4.0.3, 4.4.4, 5.1.0에서 테스트되었습니다.

버전 2

public class FragmentA extends Fragment {
    View _rootView;
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (_rootView == null) {
            // Inflate the layout for this fragment
            _rootView = inflater.inflate(R.layout.fragment_a, container, false);
            // Find and setup subviews
            _listView = (ListView)_rootView.findViewById(R.id.listView);
            ...
        } else {
            // Do not inflate the layout again.
            // The returned View of onCreateView will be added into the fragment.
            // However it is not allowed to be added twice even if the parent is same.
            // So we must remove _rootView from the existing parent view group
            // in onDestroyView() (it will be added back).
        }
        return _rootView;
    }

    @Override
    public void onDestroyView() {
        if (_rootView.getParent() != null) {
            ((ViewGroup)_rootView.getParent()).removeView(_rootView);
        }
        super.onDestroyView();
    }
}

경고!!!

이것은 해킹입니다! 내 앱에서 사용하고 있지만 주석을주의 깊게 테스트하고 읽어야합니다.


답변

당신이 찾고있는 것을 성취 할 수있는 다른 방법이 있다고 생각합니다. 나는 완전한 해결책을 말하지는 않지만 제 경우에는 목적을 달성했습니다.

내가 한 것은 방금 대상 조각을 추가 한 조각을 바꾸는 것입니다. 따라서 기본적으로 add()method 를 사용하게 됩니다 replace().

내가 뭘했는지 현재 조각을 숨기고 백 스택에 추가합니다.

따라서 뷰를 손상시키지 않고 현재 프래그먼트보다 새로운 프래그먼트와 겹칩니다 ( onDestroyView()메서드가 호출되지 않는지 확인하십시오 . 플러그를 추가 backstate하면 프래그먼트를 다시 시작할 수 있다는 이점이 있습니다.

코드는 다음과 같습니다.

Fragment fragment=new DestinationFragment();
FragmentManager fragmentManager = getFragmentManager();
android.app.FragmentTransaction ft=fragmentManager.beginTransaction();
ft.add(R.id.content_frame, fragment);
ft.hide(SourceFragment.this);
ft.addToBackStack(SourceFragment.class.getName());
ft.commit();

AFAIK 시스템 onCreateView()은 뷰가 파괴되거나 생성되지 않은 경우 에만 호출 합니다. 그러나 여기서는 메모리에서 뷰를 제거하지 않고 뷰를 저장했습니다. 따라서 새로운보기를 만들지 않습니다.

그리고 당신이 Destination Fragment에서 돌아 왔을 때 가장 최근에 FragmentTransaction제거 된 Top Fragment가 나타나서 최상위 (SourceFragment) 뷰가 화면 위에 나타납니다.

주석 : 내가 말했듯이 소스 조각의 관점을 제거하지 않으므로 평소보다 더 많은 메모리를 차지하므로 완벽한 솔루션이 아닙니다. 그러나 여전히 목적을 달성하십시오. 또한 기존 방식이 아닌 뷰를 대체하는 대신 완전히 다른 뷰 숨기기 메커니즘을 사용하고 있습니다.

따라서 실제로 상태를 유지 관리하는 방법이 아니라 뷰를 유지 관리하는 방법입니다.


답변

매우 간단한 해결책을 제안합니다.

View 참조 변수를 가져 와서 OnCreateView에서보기를 설정하십시오. 이 변수에 뷰가 이미 있는지 확인한 다음 동일한 뷰를 반환하십시오.

   private View fragmentView;

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

        if (fragmentView != null) {
            return fragmentView;
        }
        View view = inflater.inflate(R.layout.yourfragment, container, false);
        fragmentView = view;
        return view;
    }


답변

저장 / 다시로드 할 설정 정보가 너무 많은 맵이 포함 된 조각에서이 문제를 발견했습니다. 내 해결책은 기본적 으로이 조각을 항상 활성 상태로 유지하는 것입니다 (@kaushal이 언급 한 것과 유사).

현재 단편 A가 있고 단편 B를 표시하려고한다고 가정하십시오. 결과 요약 :

  • replace ()-Fragment A를 제거하고 Fragment B로 교체합니다. Fragment A는 다시 전면으로 가져온 후 다시 생성됩니다.
  • add ()-(생성 및) Fragment B를 추가하면 Fragment A와 겹쳐지며 백그라운드에서 여전히 활성화됩니다.
  • remove ()-조각 B를 제거하고 A로 돌아 오는 데 사용할 수 있습니다. 조각 B는 나중에 호출 될 때 다시 작성됩니다.

따라서 두 조각을 모두 “저장 한”상태로 유지하려면 hide () / show ()를 사용하여 조각을 전환하십시오.

장점 : 여러 조각을 계속 실행하는 쉽고 간단한 방법
단점 : 모든 메모리를 계속 사용하려면 더 많은 메모리를 사용하십시오. 많은 큰 비트 맵 표시와 같은 문제가 발생할 수 있음


답변

onSaveInstanceState() 구성 변경이있는 경우에만 호출됩니다.

한 프래그먼트에서 다른 프래그먼트로 변경하기 때문에 구성 변경이 없으므로 호출 onSaveInstanceState()이 없습니다. 어떤 상태가 저장되지 않습니까? 지정할 수 있습니까?

EditText에 텍스트를 입력하면 자동으로 저장됩니다. ID가없는 UI 항목은보기 상태가 저장되지 않는 항목입니다.


답변

onSaveInstanceState백 스택에 프래그먼트를 추가하면 프래그먼트가 호출되지 않기 때문에 여기에서 . 가기 backstack의 단편 라이프 사이클 복원을 시작할 때 onCreateView와 끝 onDestroyView동안은 onSaveInstanceState사이라고 onDestroyView하고 onDestroy. 내 솔루션은 인스턴스 변수를 만들고 init입니다 onCreate. 샘플 코드 :

private boolean isDataLoading = true;
private ArrayList<String> listData;
public void onCreate(Bundle savedInstanceState){
     super.onCreate(savedInstanceState);
     isDataLoading = false;
     // init list at once when create fragment
     listData = new ArrayList();
}

그리고 그것을 확인하십시오 onActivityCreated:

public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    if(isDataLoading){
         fetchData();
    }else{
         //get saved instance variable listData()
    }
}

private void fetchData(){
     // do fetch data into listData
}