Programing

Android Fragment 백 스택 문제

lottogame 2020. 7. 18. 10:28
반응형

Android Fragment 백 스택 문제


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

3 개의 파편이 있다고 상상해보십시오

[1] [2] [3]

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

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

이것의 현실은 [2]사용자가 뒤로 버튼을 눌렀을 때 다시 나타나지 않기를 원한다면 조각을 보여주는 트랜잭션을 [3]호출해서는 안됩니다 . 이것은 완전히 직관적이지 않은 것 같습니다 (아마도 iOS 세계에서 온 것 같습니다).addToBackStack[3]

어쨌든 내가 이런 식으로하면 나가서 [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]

많은 감사


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

우리 가 문서에서 알고있는 .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()방법에 논리를 적용하도록 구현 고려하십시오 .


권리!!! 많은 머리카락을 끈 후에 마침내이 작업을 올바르게 수행하는 방법을 알아 냈습니다.

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

우선 replace ()를 사용하지 말고 remove와 add를 별도로 사용하십시오. 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();

}

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


우선 눈을 뜨는 설명에 대해 @Arvis에게 감사드립니다.

이 문제에 대해 여기에서 허용되는 답변에 다른 솔루션을 선호합니다. 나는 절대적으로 필요한 것 이상으로 다시 동작을 무시하는 것을 좋아하지 않으며 뒤로 버튼을 눌렀을 때 기본 백 스택이 나타나지 않고 내 자신의 조각을 추가하고 제거하려고 시도했을 때 조각 지옥에서 내 자신을 발견했다. 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();

이것이 작동 해야하는 경우 문서를 읽음으로써 확실하지 않습니다.이 poping 트랜잭션 메소드는 비동기식이라고하며 아마도 더 나은 방법은 popBackStackImmediate ()를 호출하는 것입니다. 그러나 지금까지 장치에서 완벽하게 작동한다고 말할 수 있습니다.

언급 된 대안은 다음과 같습니다.

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

여기 f3으로 넘어 가기 전에 f1로 돌아가는 간단한 방법이 있으므로 약간의 결함이 있습니다.

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


나는 그것이 오래된 질문이라는 것을 알고 있지만 같은 문제가 있고 다음과 같이 수정하십시오.

먼저 이름을 사용하여 Fragment1을 BackStack에 추가합니다 (예 : "Frag1").

frag = new Fragment1();

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

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

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

그것이 누군가를 도울 수 있기를 바랍니다 :)


After @Arvis reply i decided to dig even deeper and I've written a tech article about this here: http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping-due-to-backstack-nightmare-in-android/

For the lazy developers around. My solution consists in always adding the transactions to the backstack and perform an extra FragmentManager.popBackStackImmediate() when needed (automatically).

The code is very few lines of code and, in my example, I wanted to skip from C to A without jumping back to "B" if the user didn't went deeper in the backstack (ex from C navigates to D).

Hence the code attached would work as follow A -> B -> C (back) -> A & A -> B -> C -> D (back) -> C (back) -> B (back) -> A

where

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

were issued from "B" to "C" as in the question.

Ok,Ok here is the code :)

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

If you are Struggling with addToBackStack() & popBackStack() then simply use

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

In your Activity In OnBackPressed() find out fargment by tag and then do your stuff

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

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

For more Information https://github.com/DattaHujare/NavigationDrawer I never use addToBackStack() for handling fragment.


I think, when I read your story that [3] is also on the backstack. This explains why you see it flashing up.

Solution would be to never set [3] on the stack.


I had a similar issue where I had 3 consecutive fragments in the same Activity [M1.F0]->[M1.F1]->[M1.F2] followed by a call to a new Activity[M2]. If the user pressed a button in [M2] I wanted to return to [M1,F1] instead of [M1,F2] which is what back press behavior already did.

In order to accomplish this I remove [M1,F2], call show on [M1,F1], commit the transaction, and then add [M1,F2] back by calling it with hide. This removed the extra back press that would have otherwise been left behind.

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

Hi After doing this code: I'm not able to see value of Fragment2 on pressing Back Key. My Code:

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

executePendingTransactions() , commitNow() not worked (

Worked in 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);
    }
}

참고URL : https://stackoverflow.com/questions/12529499/problems-with-android-fragment-back-stack

반응형