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.