Android Fragment 백 스택 문제


121

나는 안드로이드 프래그먼트 백 스택이 작동하는 것처럼 보이는 방식에 큰 문제가 있으며 제공되는 도움에 가장 감사 할 것입니다.

3 개의 조각이 있다고 상상해보세요

[1] [2] [3]

사용자가 탐색 할 수 [1] > [2] > [3]있지만 돌아가는 중 (뒤로 버튼 누름)을 원합니다 [3] > [1].

내가 상상했듯이 XML에 정의 된 조각 홀더로 addToBackStack(..)조각을 가져 오는 트랜잭션을 만들 때 호출하지 않음으로써이 작업을 수행 할 수 있습니다 [2].

이것의 현실은 [2]사용자가 뒤로 버튼을 눌렀을 때 다시 나타나고 싶지 [3]않다면 addToBackStack프래그먼트를 보여주는 트랜잭션을 호출해서는 안되는 것 같습니다 [3]. 이것은 완전히 반 직관적 인 것처럼 보입니다 (아마도 iOS 세계에서 온 것 같습니다).

어쨌든 이렇게하면, 나가서 [1] > [2]뒤로 누르면 [1]예상대로 돌아옵니다 .

내가 가서 [1] > [2] > [3]뒤로 누르면 [1](예상대로)로 돌아갑니다 . 이제에서 [2]다시 점프하려고하면 이상한 동작이 발생 합니다 [1]. 먼저 보기 [3]전에 잠시 표시됩니다 [2]. 이 시점에서 뒤로를 누르면 [3]표시되고 다시 한 번 뒤로 누르면 앱이 종료됩니다.

아무도 여기서 무슨 일이 일어나고 있는지 이해하도록 도울 수 있습니까?


다음은 내 주요 활동에 대한 레이아웃 xml 파일입니다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          android:orientation="vertical" >

<fragment
        android:id="@+id/headerFragment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        class="com.fragment_test.FragmentControls" >
    <!-- Preview: layout=@layout/details -->
</fragment>
<FrameLayout
        android:id="@+id/detailFragment"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"

        />



업데이트 이것은 탐색 계층 구조로 빌드하는 데 사용하는 코드입니다.

    Fragment frag;
    FragmentTransaction transaction;


    //Create The first fragment [1], add it to the view, BUT Dont add the transaction to the backstack
    frag = new Fragment1();

    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.commit();

    //Create the second [2] fragment, add it to the view and add the transaction that replaces the first fragment to the backstack
    frag = new Fragment2();

    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.addToBackStack(null);
    transaction.commit();


    //Create third fragment, Dont add this transaction to the backstack, because we dont want to go back to [2] 
    frag = new Fragment3();
    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.commit();


     //END OF SETUP CODE-------------------------
    //NOW:
    //Press back once and then issue the following code:
    frag = new Fragment2();
    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.addToBackStack(null);
    transaction.commit();

    //Now press back again and you end up at fragment [3] not [1]

많은 감사


하지만 조각 C에서 조각 A로 백 프레스 할 때 조각이 겹칩니다.
Priyanka

같은 문제가 있는데 어떻게 해결합니까?
Priyanka

내 ans를 참조하십시오 .. 도움이 될 수 있습니다. < stackoverflow.com/questions/14971780/… >
MD Khali

답변:


203

설명 : 여기서 무슨 일이 일어나고 있습니까?

문서에서 알고있는 .replace()것과 동일하다는 점을 염두에두면 .remove().add():

컨테이너에 추가 된 기존 조각을 바꿉니다. 이것은 본질적으로 동일한 인수로 remove(Fragment)추가 된 현재 추가 된 모든 프래그먼트를 호출 containerViewId한 다음 add(int, Fragment, String)여기에 제공된 동일한 인수 를 사용하는 것과 동일합니다.

그런 다음 무슨 일이 일어나고 있는지는 다음과 같습니다 (더 명확하게하기 위해 조각에 숫자를 추가합니다).

// transaction.replace(R.id.detailFragment, frag1);
Transaction.remove(null).add(frag1)  // frag1 on view

// transaction.replace(R.id.detailFragment, frag2).addToBackStack(null);
Transaction.remove(frag1).add(frag2).addToBackStack(null)  // frag2 on view

// transaction.replace(R.id.detailFragment, frag3);
Transaction.remove(frag2).add(frag3)  // frag3 on view

(여기에서 모든 오해의 소지가있는 일이 발생하기 시작합니다)

조각 자체가 아닌 트랜잭션.addToBackStack() 만 저장 한다는 것을 기억하십시오 ! 이제 우리는 레이아웃에 있습니다.frag3

< press back button >
// System pops the back stack and find the following saved back entry to be reversed:
// [Transaction.remove(frag1).add(frag2)]
// so the system makes that transaction backward!!!
// tries to remove frag2 (is not there, so it ignores) and re-add(frag1)
// make notice that system doesn't realise that there's a frag3 and does nothing with it
// so it still there attached to view
Transaction.remove(null).add(frag1) //frag1, frag3 on view (OVERLAPPING)

// transaction.replace(R.id.detailFragment, frag2).addToBackStack(null);
Transaction.remove(frag3).add(frag2).addToBackStack(null)  //frag2 on view

< press back button >
// system makes saved transaction backward
Transaction.remove(frag2).add(frag3) //frag3 on view

< press back button >
// no more entries in BackStack
< app exits >

가능한 해결책

FragmentManager.BackStackChangedListener백 스택의 변경 사항을 감시하고 onBackStackChanged()methode 에서 논리를 적용하려면 구현 을 고려하십시오 .


좋은 설명 @arvis. 그러나 DexterMoon의 방법이나 전환 애니메이션을 재생하는 동안 Fragment를 표시하는 popBackStack을 사용하는 Nemanja의 방법과 같은 해키 한 방법에 의존하지 않고이 동작을 어떻게 방지 할 수 있습니까?
모모

@momo FragmentManager.BackStackChangedListener는 백 스택의 변경 사항을 감시하기 위해 구현할 수 있습니다 . onBackStackChanged()방법으로 모든 거래를 모니터링하고 필요에 따라 행동하십시오. BackStack에서 트랜잭션 수를 추적합니다. (이름으로 특정 트랜잭션을 확인 FragmentTransaction addToBackStack (String name)등)
Arvis

대답 해줘서 고마워. 나는 실제로 오늘 초반에 리스너를 등록하고 onBackstackChange에서 조각을 제거했습니다. 터지는 전환이 재생되는 동안 조각은 흰색 빈 영역이됩니다. 팝핑이 애니메이션을 시작할 때가 아니라 끝날 때가 아니라 시작할 때 메서드가 실행되는 것 같아요 ...
momo

4
백 스택을 사용하지 마십시오! 전반적인 효율성에 실제로 도움이되지 않습니다! 일반 replace ()를 사용하거나 탐색 할 때마다 더 나은 제거 / 추가를 사용하십시오!
stack_ved

@Arvis 수 u pls는 동일한 문제가 발생하는 메신저 메신저 .... 스택 수 0이지만 여전히 내 조각을 볼 수 있습니까?
Erum

33

권리!!! 머리카락을 많이 잡아 당긴 후 마침내이 작업을 제대로하는 방법을 알아 냈습니다.

뒤로를 눌렀을 때 조각 [3]이 뷰에서 제거되지 않는 것처럼 보이므로 수동으로 수행해야합니다!

우선, replace ()를 사용하지 말고 대신 제거하고 추가하십시오. replace ()가 제대로 작동하지 않는 것 같습니다.

다음 부분은 onKeyDown 메서드를 재정의하고 뒤로 버튼을 누를 때마다 현재 조각을 제거하는 것입니다.

@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
    if (keyCode == KeyEvent.KEYCODE_BACK)
    {
        if (getSupportFragmentManager().getBackStackEntryCount() == 0)
        {
            this.finish();
            return false;
        }
        else
        {
            getSupportFragmentManager().popBackStack();
            removeCurrentFragment();

            return false;
        }



    }

    return super.onKeyDown(keyCode, event);
}


public void removeCurrentFragment()
{
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

    Fragment currentFrag =  getSupportFragmentManager().findFragmentById(R.id.detailFragment);


    String fragName = "NONE";

    if (currentFrag!=null)
        fragName = currentFrag.getClass().getSimpleName();


    if (currentFrag != null)
        transaction.remove(currentFrag);

    transaction.commit();

}

도움이 되었기를 바랍니다!


이것은 작동하지만 조각이 다시로드되기 때문에 내 프로젝트에서 사용할 수 없습니다! 어떤 제안?
TharakaNirmana 2014

하지만 내가 이러면. 내가 조각 C. 위해 A와 C에서 다시 내려왔다 때 나는 빈 화면을 얻고있다
니 감은 Patro

나는 @Arvis 대답이 문제에 어떤 빛을 던져해야 의심
크리스 버치에게

16

먼저 눈을 뜨게 해준 @Arvis에게 감사드립니다.

이 문제에 대해 여기에서 허용되는 답변보다 다른 솔루션을 선호합니다. 나는 절대적으로 필요한 것보다 더 이상 뒤로 동작을 재정의하는 것을 엉망으로 만들고 뒤로 버튼을 눌렀을 때 기본 백 스택 팝없이 나 자신의 조각을 추가하고 제거하려고 시도했을 때 조각 지옥에서 내 자신을 찾았습니다. f1을 제거 할 때 f1 위에 f2를 추가합니다. f1은 onResume, onStart 등과 같은 콜백 메서드를 호출하지 않으며 이는 매우 불행 할 수 있습니다.

어쨌든 이것이 내가하는 방법입니다.

현재 디스플레이에는 f1 조각 만 있습니다.

f1-> f2

Fragment2 f2 = new Fragment2();
this.getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content,f2).addToBackStack(null).commit();

여기에는 평범한 것이 없습니다. 이 코드는 f2 조각에서보다 f3 조각으로 이동합니다.

f2-> f3

Fragment3 f3 = new Fragment3();
getActivity().getSupportFragmentManager().popBackStack();
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();

이것이 작동하는지 문서를 읽음으로써 확실하지 않습니다.이 팝업 트랜잭션 메서드는 비동기식이라고 말하며 더 나은 방법은 popBackStackImmediate ()를 호출하는 것입니다. 그러나 내 장치에서 완벽하게 작동하고 있음을 알 수 있습니다.

상기 대안은 다음과 같습니다.

final FragmentActivity activity = getActivity();
activity.getSupportFragmentManager().popBackStackImmediate();
activity.getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();

여기서는 f3으로 이동하기 전에 f1로 돌아가는 짧은 시간이 있으므로 약간의 결함이 있습니다.

이것은 실제로 당신이해야 할 전부이며, 스택 동작을 재정의 할 필요가 없습니다.


6
하지만 문제의 글리치,
Zyoo

이것은 BackStack 변경 사항을 듣는 것보다 덜 "해커"처럼 보입니다. 감사.
ahaisting jul.

13

나는 그것이 오래된 질문이라는 것을 알고 있지만 동일한 문제가 발생하여 다음과 같이 수정합니다.

먼저 Fragment1을 이름 (예 : "Frag1")으로 BackStack에 추가합니다.

frag = new Fragment1();

transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.addToBackStack("Frag1");
transaction.commit();

그런 다음 Fragment1로 돌아가고 싶을 때 (위에 10 개의 조각을 추가 한 후에도) 이름으로 popBackStackImmediate를 호출하면됩니다.

getSupportFragmentManager().popBackStackImmediate("Frag1", 0);

누군가에게 도움이되기를 바랍니다. :)


1
좋은 대답입니다. getSupportFragmentManager (). popBackStackImmediate ( "Frag1", FragmentManager.POP_BACK_STACK_INCLUSIVE); 하지만 내 사용 사례에 대한 작업에 얻을
eliasbagley

1
이 코드를 어디에 넣어야합니까 ???? getSupportFragmentManager (). popBackStackImmediate ( "Frag1", 0); MainActivty 또는 onBackPress
pavel

작동하지 않습니다. :( 이것은 내 코드입니다. 제 경우에는 Fragment가 Overlaping입니다. Fragment A가 열리지 만 Frgment A는 Fragment B에서 겹칩니다.
Priyanka

FragmentManager fragmentManager = getActivity (). getSupportFragmentManager (); fragmentManager.popBackStack (FragmentA.class.getName (), FragmentManager.POP_BACK_STACK_INCLUSIVE); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction (); FragmentE fragmentE = 새로운 FragmentE (); fragmentTransaction.replace (R.id.fragment_content, fragmentE, fragmentE.getClass (). getName ()); fragmentTransaction.commit ();
Priyanka

5

@Arvis 회신 후 나는 심지어 깊이 파고하기로 결정하고 내가 여기에 대한 기술 문서를 작성했습니다 : http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping- 백 스택 악몽으로 인한 안드로이드 /

주변의 게으른 개발자를 위해. 내 솔루션은 항상 백 스택에 트랜잭션을 추가하고 FragmentManager.popBackStackImmediate()필요할 때 추가 작업을 수행하는 것으로 구성 됩니다 (자동).

코드는 매우 적은 코드 줄이며, 제 예에서는 사용자가 백 스택에서 더 깊이 들어 가지 않은 경우 "B"로 다시 건너 뛰지 않고 C에서 A로 건너 뛰고 싶었습니다 (예 : C에서 D로 이동).

따라서 첨부 된 코드는 A-> B-> C (뒤로)-> A & A-> B-> C-> D (뒤로)-> C (뒤로)-> B (뒤로)-> A와 같이 작동합니다.

어디

fm.beginTransaction().replace(R.id.content, new CFragment()).commit()

질문에서와 같이 "B"에서 "C"로 발행되었습니다.

Ok, 여기 코드가 있습니다.

public static void performNoBackStackTransaction(FragmentManager fragmentManager, String tag, Fragment fragment) {
  final int newBackStackLength = fragmentManager.getBackStackEntryCount() +1;

  fragmentManager.beginTransaction()
      .replace(R.id.content, fragment, tag)
      .addToBackStack(tag)
      .commit();

  fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
    @Override
    public void onBackStackChanged() {
      int nowCount = fragmentManager.getBackStackEntryCount();
      if (newBackStackLength != nowCount) {
        // we don't really care if going back or forward. we already performed the logic here.
        fragmentManager.removeOnBackStackChangedListener(this);

        if ( newBackStackLength > nowCount ) { // user pressed back
          fragmentManager.popBackStackImmediate();
        }
      }
    }
  });
}

1
이 줄에서 충돌이 발생 함 fragmentManager.popBackStackImmediate (); 오류 : java.lang.IllegalStateException : FragmentManager가 이미 com.example.myapplication.FragmentA $ 2.onBackStackChanged (FragmentA.java:43)에서 트랜잭션을 실행하고 있습니다
Priyanka

1

addToBackStack () 및 popBackStack ()으로 어려움을 겪고 있다면 간단히 사용하십시오.

FragmentTransaction ft =getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, new HomeFragment(), "Home");
ft.commit();`

OnBackPressed ()의 활동에서 태그로 fargment를 찾은 다음 작업을 수행하십시오.

Fragment home = getSupportFragmentManager().findFragmentByTag("Home");

if (home instanceof HomeFragment && home.isVisible()) {
    // do you stuff
}

자세한 내용은 https://github.com/DattaHujare/NavigationDrawer 조각 처리에 addToBackStack ()을 사용하지 않습니다.


0

나는 당신의 이야기를 읽을 때 [3]도 백 스택에 있다고 생각합니다. 이것은 당신이 그것이 깜박이는 것을 보는 이유를 설명합니다.

해결책은 스택에 [3]을 설정하지 않는 것입니다.


안녕하세요 jdekei 귀하의 의견에 감사드립니다. 문제는 내가 백 스택에 [3]을 추가하는 위치를 볼 수 없다는 것입니다. 버튼을 사용하여 수행 한 내비게이션을 (프로그래밍 방식으로) 정확하게 보여주는 또 다른 코드 덩어리를 추가했습니다.
Chris Birch

그것은 나에게도 도움이되지만 나는 당신 코드에서 removeCurFragment와 약간 다른 조각 검사기 만 사용합니다. 대체 방법이 잘 작동 s old method issue but now it합니다. 감사합니다
Viktor V.

0

동일한 [M1.F0]-> [M1.F1]-> [M1.F2]에 새 [M2]를 호출하는 3 개의 연속 조각 이있는 비슷한 문제가 발생했습니다 . 사용자가 [M2]에서 버튼을 눌렀다면 나는 이미 뒤로 누르는 동작 인 [M1, F2] 대신 [M1, F1]로 돌아가고 싶었습니다.ActivityActivity

이를 수행하기 위해 [M1, F2]를 제거하고 [M1, F1]에서 show를 호출하고 트랜잭션을 커밋 한 다음 hide로 호출하여 [M1, F2]를 다시 추가합니다. 이것은 그렇지 않으면 남겨졌을 여분의 백 프레스를 제거했습니다.

// Remove [M1.F2] to avoid having an extra entry on back press when returning from M2
final FragmentTransaction ftA = fm.beginTransaction();
ftA.remove(M1F2Fragment);
ftA.show(M1F1Fragment);
ftA.commit();
final FragmentTransaction ftB = fm.beginTransaction();
ftB.hide(M1F2Fragment);
ftB.commit();

안녕하세요이 코드를 수행 한 후 : 뒤로 키를 눌러도 Fragment2 값을 볼 수 없습니다. 내 코드 :

FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.frame, f1);
ft.remove(f1);

ft.add(R.id.frame, f2);
ft.addToBackStack(null);

ft.remove(f2);
ft.add(R.id.frame, f3);

ft.commit();

@Override
    public boolean onKeyDown(int keyCode, KeyEvent event){

        if(keyCode == KeyEvent.KEYCODE_BACK){
            Fragment currentFrag =  getFragmentManager().findFragmentById(R.id.frame);
            FragmentTransaction transaction = getFragmentManager().beginTransaction();

            if(currentFrag != null){
                String name = currentFrag.getClass().getName();
            }
            if(getFragmentManager().getBackStackEntryCount() == 0){
            }
            else{
                getFragmentManager().popBackStack();
                removeCurrentFragment();
            }
       }
    return super.onKeyDown(keyCode, event);
   }

public void removeCurrentFragment()
    {
        FragmentTransaction transaction = getFragmentManager().beginTransaction();
        Fragment currentFrag =  getFragmentManager().findFragmentById(R.id.frame);

        if(currentFrag != null){
            transaction.remove(currentFrag);
        }
        transaction.commit();
    }

0

executePendingTransactions(), commitNow()작동하지 않음 (

androidx (jetpack)에서 작업했습니다.

private final FragmentManager fragmentManager = getSupportFragmentManager();

public void removeFragment(FragmentTag tag) {
    Fragment fragmentRemove = fragmentManager.findFragmentByTag(tag.toString());
    if (fragmentRemove != null) {
        fragmentManager.beginTransaction()
                .remove(fragmentRemove)
                .commit();

        // fix by @Ogbe
        fragmentManager.popBackStackImmediate(tag.toString(), 
            FragmentManager.POP_BACK_STACK_INCLUSIVE);
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.