IT story

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

hot-time 2020. 6. 13. 09:46
반응형

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


두 조각 사이를 전환하는 더미 활동을 작성했습니다. 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맨 위의 (SourceFragment 's)보기가 화면 위에 나타나게 하는 마지막 제거 상단 조각이 나타납니다.

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

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


저장 / 다시로드 할 설정 정보가 너무 많은 맵이 포함 된 조각에서이 문제를 발견했습니다. 내 해결책은 기본적 으로이 조각을 항상 활성 상태로 유지하는 것입니다 (@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 항목은보기 상태가 저장되지 않는 항목입니다.


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

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

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
}

getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener()
    {
        @Override
        public void onBackStackChanged()
        {
            if (getSupportFragmentManager().getBackStackEntryCount() == 0)
            {
                //setToolbarTitle("Main Activity");
            }
            else
            {
                Log.e("fragment_replace11111", "replace");
            }
        }
    });


YourActivity.java
@Override
public void onBackPressed()
{
 Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.Fragment_content);
  if (fragment instanceof YourFragmentName)
    {
        fragmentReplace(new HomeFragment(),"Home Fragment");
        txt_toolbar_title.setText("Your Fragment");
    }
  else{
     super.onBackPressed();
   }
 }


public void fragmentReplace(Fragment fragment, String fragment_name)
{
    try
    {
        fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.replace(R.id.Fragment_content, fragment, fragment_name);
        fragmentTransaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);
        fragmentTransaction.addToBackStack(fragment_name);
        fragmentTransaction.commitAllowingStateLoss();
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
}

My problem was similar but I overcame me without keeping the fragment alive. Suppose you have an activity that has 2 fragments - F1 and F2. F1 is started initially and lets say in contains some user info and then upon some condition F2 pops on asking user to fill in additional attribute - their phone number. Next, you want that phone number to pop back to F1 and complete signup but you realize all previous user info is lost and you don't have their previous data. The fragment is recreated from scratch and even if you saved this information in onSaveInstanceState the bundle comes back null in onActivityCreated.

Solution: Save required information as an instance variable in calling activity. Then pass that instance variable into your fragment.

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

    Bundle args = getArguments();

    // this will be null the first time F1 is created. 
    // it will be populated once you replace fragment and provide bundle data
    if (args != null) {
        if (args.get("your_info") != null) {
            // do what you want with restored information
        }
    }
}

So following on with my example: before I display F2 I save user data in the instance variable using a callback. Then I start F2, user fills in phone number and presses save. I use another callback in activity, collect this information and replace my fragment F1, this time it has bundle data that I can use.

@Override
public void onPhoneAdded(String phone) {
        //replace fragment
        F1 f1 = new F1 ();
        Bundle args = new Bundle();
        yourInfo.setPhone(phone);
        args.putSerializable("you_info", yourInfo);
        f1.setArguments(args);

        getFragmentManager().beginTransaction()
                .replace(R.id.fragmentContainer, f1).addToBackStack(null).commit();

    }
}

More information about callbacks can be found here: https://developer.android.com/training/basics/fragments/communicating.html


first: just use add method instead of replace method of FragmentTransaction class then you have to add secondFragment to stack by addToBackStack method

second :on back click you have to call popBackStackImmediate()

Fragment sourceFragment = new SourceFragment ();
final Fragment secondFragment = new SecondFragment();
final FragmentTransaction ft = getChildFragmentManager().beginTransaction();
ft.add(R.id.child_fragment_container, secondFragment );
ft.hide(sourceFragment );
ft.addToBackStack(NewsShow.class.getName());
ft.commit();

((SecondFragment)secondFragment).backFragmentInstanceClick = new SecondFragment.backFragmentNewsResult()
{
        @Override
        public void backFragmentNewsResult()
        {                                    
            getChildFragmentManager().popBackStackImmediate();                                
        }
};

Replace a Fragment using following code:

Fragment fragment = new AddPaymentFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.frame, fragment, "Tag_AddPayment")
                .addToBackStack("Tag_AddPayment")
                .commit();

Activity's onBackPressed() is :

  @Override
public void onBackPressed() {
    android.support.v4.app.FragmentManager fm = getSupportFragmentManager();
    if (fm.getBackStackEntryCount() > 1) {

        fm.popBackStack();
    } else {


        finish();

    }
    Log.e("popping BACKSTRACK===> ",""+fm.getBackStackEntryCount());

}

Public void replaceFragment(Fragment mFragment, int id, String tag, boolean addToStack) {
        FragmentTransaction mTransaction = getSupportFragmentManager().beginTransaction();
        mTransaction.replace(id, mFragment);
        hideKeyboard();
        if (addToStack) {
            mTransaction.addToBackStack(tag);
        }
        mTransaction.commitAllowingStateLoss();
    }
replaceFragment(new Splash_Fragment(), R.id.container, null, false);

Perfect solution that find old fragment in stack and load it if exist in stack.

/**
     * replace or add fragment to the container
     *
     * @param fragment pass android.support.v4.app.Fragment
     * @param bundle pass your extra bundle if any
     * @param popBackStack if true it will clear back stack
     * @param findInStack if true it will load old fragment if found
     */
    public void replaceFragment(Fragment fragment, @Nullable Bundle bundle, boolean popBackStack, boolean findInStack) {
        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();
        String tag = fragment.getClass().getName();
        Fragment parentFragment;
        if (findInStack && fm.findFragmentByTag(tag) != null) {
            parentFragment = fm.findFragmentByTag(tag);
        } else {
            parentFragment = fragment;
        }
        // if user passes the @bundle in not null, then can be added to the fragment
        if (bundle != null)
            parentFragment.setArguments(bundle);
        else parentFragment.setArguments(null);
        // this is for the very first fragment not to be added into the back stack.
        if (popBackStack) {
            fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
        } else {
            ft.addToBackStack(parentFragment.getClass().getName() + "");
        }
        ft.replace(R.id.contenedor_principal, parentFragment, tag);
        ft.commit();
        fm.executePendingTransactions();
    }

use it like

Fragment f = new YourFragment();
replaceFragment(f, null, boolean true, true); 

참고URL : https://stackoverflow.com/questions/11353075/how-can-i-maintain-fragment-state-when-added-to-the-back-stack

반응형