IllegalStateException : ViewPager를 사용하여 onSaveInstanceState 후에이 조치를 수행 할 수 없습니다.


496

시장의 앱에서 사용자 보고서를 받고 다음 예외를 제공합니다.

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1109)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:399)
at android.app.Activity.onBackPressed(Activity.java:2066)
at android.app.Activity.onKeyUp(Activity.java:2044)
at android.view.KeyEvent.dispatch(KeyEvent.java:2529)
at android.app.Activity.dispatchKeyEvent(Activity.java:2274)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1855)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1277)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.widget.TabHost.dispatchKeyEvent(TabHost.java:297)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1855)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1277)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.java:2880)
at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2853)
at android.view.ViewRoot.handleMessage(ViewRoot.java:2028)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4028)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:844)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:602)
at dalvik.system.NativeStart.main(Native Method)

분명히 그것은 내가 사용하지 않는 FragmentManager와 관련이 있습니다. stacktrace에 내 클래스가 표시되지 않으므로이 예외가 발생하는 위치와 방지 방법을 모릅니다.

기록을 위해 : 나는 탭 호스트를 가지고 있으며 각 탭에는 활동간에 활동 그룹 전환이 있습니다.


2
나는 같은 문제에 대해이 질문을 찾았지만 해결책이 없습니다 .. stackoverflow.com/questions/7469082/…
nhaarman

3
를 사용하지 않는 동안 FragmentManagerHoneycomb은 확실히 사용됩니다. 실제 허니컴 태블릿에서 이런 일이 발생합니까? 아니면 누군가가 전화로 해킹 된 허니컴을 실행하고 있거나 해킹 된 버전 인 것이 어려울 수 있습니다.
CommonsWare

1
나도 몰라 이것은 Market Developer Console에서 얻을 수있는 유일한 정보입니다. 사용자 메시지에는 유용한 정보가 없습니다 ..
nhaarman

Flurry를 사용하고 있는데, Android 3.0.1과의 11 세션을 보여 주며이 예외에 대한 11 개의 보고서가 있습니다. 그러나 우연의 일치 일 수 있습니다. Android 3.1 및 3.2에는 각각 56 및 38 개의 세션이 있습니다.
nhaarman

마켓 오류 보고서에는 '플랫폼'섹션이 있으며 때로는 장치의 Android 버전이 있습니다.
Nikolay Elenkov

답변:


720

내 답변을 여기에서 확인 하십시오 . 기본적으로 나는 단지 :

@Override
protected void onSaveInstanceState(Bundle outState) {
    //No call for super(). Bug on API Level > 11.
}

에 전화를하지 마십시오 super()saveInstanceState방법. 이것은 엉망이되었습니다 ...

지원 패키지 의 알려진 버그 입니다.

인스턴스를 저장하고 무언가를 추가해야하는 경우 outState Bundle다음을 사용할 수 있습니다.

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
    super.onSaveInstanceState(outState);
}

결국 적절한 해결책은 (주석에서 볼 수 있듯이) 다음을 사용하는 것입니다.

transaction.commitAllowingStateLoss();

추가하거나 수행 할 때 FragmentTransaction을 일으키는 것을 Exception.


370
commit () 대신 commitAllowingStateLoss ()를 사용해야합니다.
meh

18
commitAllowingStateLoss ()에 대한이 의견은 독자적인 답변입니다.
Risadinha

20
'commitAllowingStateLoss'에 관하여-/> "나중에 활동을 상태에서 복원해야 할 경우 커밋이 유실 될 수 있으므로 위험합니다. 따라서 UI 상태가 예기치 않게 변경 될 수있는 경우에만 사용해야합니다. 사용자."
Codeversed

10
v4 소스를 popBackStackImmediate보면 상태가 저장되면 즉시 실패합니다. 이전에는 조각을 추가 commitAllowingStateLoss해도 아무런 영향 을 미치지 않습니다. 내 테스트는 이것이 사실임을 보여줍니다. 이 특정 예외에는 영향을 미치지 않습니다. 우리에게 필요한 것은 popBackStackImmediateAllowingStateLoss방법입니다.
Synesso

3
@DanieleB 예, 여기에 답변을 게시했습니다. 그러나 실제로 Otto 메시지 버스를 사용하여 더 나은 솔루션을 찾았습니다. 조각을 가입자로 등록하고 버스의 비동기 결과를 수신하십시오. 일시 중지시 등록을 취소하고 재개시 다시 등록하십시오. 비동기에는 완료되고 프래그먼트가 일시 중지 된 시간에 대해 Produce 메서드도 필요합니다. 시간이되면 답변을 더 자세히 업데이트하겠습니다.
Synesso

130

비슷한 오류 메시지와 관련된 많은 문제가 있습니다. 이 특정 스택 추적의 두 번째 줄을 확인하십시오. 이 예외는 특히에 대한 호출과 관련이 FragmentManagerImpl.popBackStackImmediate있습니다.

세션 상태가 이미 저장 되어 있으면 이 메소드 호출 popBackStack항상 실패 IllegalStateException합니다. 소스를 확인하십시오. 이 예외가 발생하는 것을 막기 위해 할 수있는 일은 없습니다.

  • 통화를 삭제 super.onSaveInstanceState해도 도움이되지 않습니다.
  • 로 조각을 만드는 commitAllowingStateLoss것은 도움이되지 않습니다.

문제를 관찰 한 방법은 다음과 같습니다.

  • 제출 버튼이있는 양식이 있습니다.
  • 버튼을 클릭하면 대화 상자가 생성되고 비동기 프로세스가 시작됩니다.
  • 사용자는 프로세스가 완료되기 전에 홈 키를 클릭합니다 onSaveInstanceState.
  • 프로세스가 완료되고 콜백이 작성되고 popBackStackImmediate시도됩니다.
  • IllegalStateException 던졌습니다.

내가 해결하기 위해 한 일은 다음과 같습니다.

IllegalStateException콜백 을 피할 수 없으므로 catch & 무시하십시오.

try {
    activity.getSupportFragmentManager().popBackStackImmediate(name);
} catch (IllegalStateException ignored) {
    // There's no way to avoid getting this if saveInstanceState has already been called.
}

앱이 중단되는 것을 막기에 충분합니다. 그러나 이제 사용자는 앱을 복원하고 자신이 눌렀다 고 생각한 버튼이 전혀 눌리지 않았 음을 알 수 있습니다 (생각합니다). 양식 조각이 여전히 표시됩니다!

이 문제를 해결하려면 대화 상자를 만들 때 프로세스가 시작되었음을 나타내는 상태를 만드십시오.

progressDialog.show(fragmentManager, TAG);
submitPressed = true;

이 상태를 번들에 저장하십시오.

@Override
public void onSaveInstanceState(Bundle outState) {
    ...
    outState.putBoolean(SUBMIT_PRESSED, submitPressed);
}

다시로드하는 것을 잊지 마십시오 onViewCreated

그런 다음 다시 시작할 때 제출이 이전에 시도 된 경우 조각을 롤백하십시오. 이를 통해 사용자는 제출되지 않은 양식으로 돌아가는 것을 방지 할 수 있습니다.

@Override
public void onResume() {
    super.onResume();
    if (submitPressed) {
        // no need to try-catch this, because we are not in a callback
        activity.getSupportFragmentManager().popBackStackImmediate(name);
        submitPressed = false;
    }
}

5
여기에 대한 흥미로운 독서 : androiddesignpatterns.com/2013/08/…
Pascal

DialogFragment를 사용하는 경우 여기에 대안을 만들었습니다. github.com/AndroidDeveloperLB/DialogShard
Android 개발자

popBackStackImmediateAndroid 자체에서 호출 한 경우 어떻게 되나요?
Kimi Chiu

1
절대적으로 좋습니다. 이 답변이 허용되어야합니다. 대단히 감사합니다! submitPressed = false를 추가 할 수도 있습니다. popBackStackInmediate 후.
Neonigma

공개 void onSaveInstanceState (Bundle outState) 메소드를 사용하지 않았습니다. public void onSaveInstanceState (Bundle outState)에 대해 빈 메소드를 설정해야합니까?
Faxriddin Abdullayev

55

isFinishing()프래그먼트를 표시하기 전에 활동을 확인 하고주의하십시오 commitAllowingStateLoss().

예:

if(!isFinishing()) {
FragmentManager fm = getSupportFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            DummyFragment dummyFragment = DummyFragment.newInstance();
            ft.add(R.id.dummy_fragment_layout, dummyFragment);
            ft.commitAllowingStateLoss();
}

1
! isFinishing () &&! isDestroyed ()가 작동하지 않습니다.
Allen Vork

! isFinishing () &&! isDestroyed ()는 나를 위해 일했지만 API 17이 필요합니다. 그러나 단순히를 표시하지 않습니다 DialogFragment. 다른 좋은 솔루션에 대해서는 stackoverflow.com/questions/15729138/… 을 참조하십시오 . stackoverflow.com/a/41813953/2914140가 도움이되었습니다.
CoolMind

29

2017 년 10 월이며 Google은 Lifecycle 구성 요소라고하는 새로운 기능으로 Android 지원 라이브러리를 만듭니다. 'onSaveInstanceState 후에이 작업을 수행 할 수 없습니다'문제에 대한 새로운 아이디어를 제공합니다.

한마디로 :

  • 수명주기 구성 요소를 사용하여 프래그먼트를 팝업하기에 적합한 시간인지 판별하십시오.

Explain이있는 더 긴 버전 :

  • 왜이 문제가 발생합니까?

    그것은 당신이 FragmentManager당신의 조각을 위해 트랜잭션을 커밋하기 위해 당신의 활동 (내가 생각하는 조각을 보유 할 것입니까?)에서 사용하려고하기 때문 입니다. 일반적으로 이것은 호스트 조각 활동이 이미 savedInstanceState메서드를 호출 하는 동안 곧 다가오는 조각에 대한 트랜잭션을 시도하는 것처럼 보입니다 (사용자는 홈 버튼을 터치하여 활동이 호출되므로 onStop()내 경우에는 이유입니다)

    일반적으로이 문제는 발생하지 않아야합니다. 우리는 항상 처음부터 조각을 활동에로드하려고합니다 onCreate(). 그러나 때로는 이런 일이 발생합니다 . 특히 해당 활동에로드 할 조각을 결정할 수 없거나 AsyncTask블록 에서 조각을로드하려고하면 (또는 시간이 조금 걸립니다). 프래그먼트 트랜잭션이 실제로 발생하기 전의 시간이지만 액티비티의 onCreate()메소드 후에 사용자는 무엇이든 할 수 있습니다. 사용자가 활동의 onSavedInstanceState()메소드 를 트리거하는 홈 단추를 누르면 can not perform this action충돌이 발생합니다.

    이 문제에 대해 더 깊이 알고 싶다면이 블로그 게시물을 살펴 보는 것이 좋습니다 . 소스 코드 레이어 내부를 자세히 살펴보고 이에 대해 많은 것을 설명합니다. 또한 commitAllowingStateLoss()이 충돌을 해결 하기 위해 방법을 사용하지 않아야하는 이유를 제공합니다 (코드에 아무런 도움이되지 않음)

  • 이 문제를 해결하는 방법?

    • commitAllowingStateLoss()조각을로드 하는 방법을 사용해야합니까 ? 아니해서는 안됩니다 .

    • onSaveInstanceState메서드를 재정의해야하고 그 super안에있는 메서드를 무시 해야합니까 ? 아니해서는 안됩니다 .

    • isFinishing호스트 활동이 프래그먼트 트랜잭션에 적합한 순간인지 확인 하기 위해 마법의 내부 활동을 사용해야 합니까? 예, 이것은 올바른 방법 처럼 보입니다 .

  • Lifecycle 구성 요소가 수행 할 수있는 작업을 살펴보십시오 .

    기본적으로 Google은 AppCompatActivity클래스 (및 프로젝트에서 사용해야하는 다른 기본 클래스) 내부에서 일부 구현을 수행 하므로 현재 수명주기 상태를 보다 쉽게 확인할 수 있습니다. 문제를 다시 살펴보십시오. 왜이 문제가 발생합니까? 잘못된 타이밍에 무언가를하기 때문입니다. 그래서 우리는 그렇게하지 않으려 고 노력하며이 문제는 사라질 것입니다.

    나는 내 자신의 프로젝트를 위해 약간의 코드를 작성한다 LifeCycle. 나는 Kotlin으로 코딩합니다.

val hostActivity: AppCompatActivity? = null // the activity to host fragments. It's value should be properly initialized.

fun dispatchFragment(frag: Fragment) {
    hostActivity?.let {
       if(it.lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)){
           showFragment(frag)
       }
    }
}

private fun showFragment(frag: Fragment) {
    hostActivity?.let {
        Transaction.begin(it, R.id.frag_container)
                .show(frag)
                .commit()
    }

위에 보여 주듯이. 호스트 활동의 수명주기 상태를 확인합니다. 지원 라이브러리 내에 Lifecycle 구성 요소가 있으면 더 구체적 일 수 있습니다. 코드 lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)는 현재 상태가 적어도 onResume늦지 않은 경우를 의미 합니다. 어떤 다른 수명 상태에서 내 메소드가 실행되지 않도록합니다.onStop .

  • 다 되었습니까?

    당연히 아니지. 내가 보여준 코드는 응용 프로그램 충돌을 막는 새로운 방법을 알려줍니다. 그러나의 상태로 전환 onStop되면 해당 코드 줄은 아무런 작업을 수행하지 않으므로 화면에 아무것도 표시되지 않습니다. 사용자가 응용 프로그램으로 돌아 오면 빈 화면이 표시됩니다. 빈 화면으로 조각이 전혀 표시되지 않습니다. 나쁜 경험입니다 (예 : 충돌보다 조금 낫습니다).

    그래서 여기에 더 좋은 것이있을 수 있기를 바랍니다. 앱이 나중에 인생 상태가되면 충돌하지 않습니다. onResume . 트랜잭션 방법은 수명 상태를 인식합니다. 또한 액티비티는 사용자가 앱으로 돌아온 후 해당 조각 트랜잭션 작업을 계속 완료하려고 시도합니다.

    이 방법에 더 많은 것을 추가합니다.

class FragmentDispatcher(_host: FragmentActivity) : LifecycleObserver {
    private val hostActivity: FragmentActivity? = _host
    private val lifeCycle: Lifecycle? = _host.lifecycle
    private val profilePendingList = mutableListOf<BaseFragment>()

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun resume() {
        if (profilePendingList.isNotEmpty()) {
            showFragment(profilePendingList.last())
        }
    }

    fun dispatcherFragment(frag: BaseFragment) {
        if (lifeCycle?.currentState?.isAtLeast(Lifecycle.State.RESUMED) == true) {
            showFragment(frag)
        } else {
            profilePendingList.clear()
            profilePendingList.add(frag)
        }
    }

    private fun showFragment(frag: BaseFragment) {
        hostActivity?.let {
            Transaction.begin(it, R.id.frag_container)
                    .show(frag)
                    .commit()
        }
    }
}

dispatcher클래스 내부에 목록을 유지 관리하여 해당 조각을 저장하면 거래 작업을 완료 할 기회가 없습니다. 사용자가 홈 화면에서 돌아와서 여전히 실행 대기중인 조각이 있음을 발견 resume()하면 @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)주석 아래의 방법으로 이동합니다 . 이제 예상대로 작동해야한다고 생각합니다.


8
Kotlin
Shchvova

1
FragmentDispatcher하나의 프래그먼트 만 복원되는 경우 목록을 사용하여 보류중인 프래그먼트를 저장하는 이유는 무엇 입니까?
fraherm

21

이 문제에 대한 다른 해결책이 있습니다.

개인 멤버 변수를 사용하면 반환 된 데이터를 의도로 설정 한 다음 super.onResume (); 이후에 처리 할 수 ​​있습니다.

이렇게 :

private Intent mOnActivityResultIntent = null; 

@Override
protected void onResume() {
    super.onResume();
    if(mOnActivityResultIntent != null){
        ... do things ...
        mOnActivityResultIntent = null;
    }
 }

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data){
    if(data != null){
        mOnActivityResultIntent = data;
    }
}

7
허용되지 않은 작업에 따라 onResume ()보다 훨씬 더 늦은 순간으로 이동해야 할 수도 있습니다. insatcne의 경우 FragmentTransaction.commit ()이 문제이면 대신 onPostResume ()으로 이동해야합니다.
pjv

1
이것이 저에게이 질문에 대한 답입니다. 수신 된 NFC 태그를 이전 활동으로 전달해야했기 때문에 이것이 바로 그 일이었습니다.
Janis Peisenieks

7
나를 위해 전화하지 않았기 때문에 그것은 일어났다 super.onActivityResult().
Sufian

20

짧고 효과적인 해결책 :

간단한 단계를 따르십시오

단계

1 단계 : onSaveInstanceState각 조각의 상태를 무시 합니다. 그리고 슈퍼 메소드를 제거하십시오.

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

2 단계 : 사용 fragmentTransaction.commitAllowingStateLoss( );

fragmentTransaction.commit( ); 조각화 작업 대신에 .


답변은 다른 곳에서 복사 또는 참조되지 않습니다. 여러 시행 착오로 얻은 내 작업 솔루션으로 사람들을 돕기 위해 게시되었습니다
Vinayak

12

주의 하여 사용 transaction.commitAllowingStateLoss()하면 사용자에게 나쁜 경험이 될 수 있습니다. 이 예외가 발생하는 이유에 대한 자세한 내용은 이 게시물을 참조하십시오 .


6
이것은 질문에 대한 답변을 제공하지 않으므로 질문에 대한 올바른 답변을 제공해야합니다.
Umar Ata

10

이런 종류의 문제에 대한 더러운 해결책을 찾았습니다. ActivityGroups어떤 이유로 든 계속 유지하고 싶다면 (시간 제한이있는 이유) 구현하면됩니다.

public void onBackPressed() {}

당신을에 Activity어떤 할 back코드가. 구형 장치에 그러한 메소드가없는 경우에도이 메소드는 최신 메소드에 의해 호출됩니다.


6

commitAllowingStateLoss ()를 사용하지 마십시오. UI 상태가 사용자에게 예기치 않게 변경 될 수있는 경우에만 사용해야합니다.

https://developer.android.com/reference/android/app/FragmentTransaction.html#commitAllowingStateLoss ()

트랜잭션이 parentFragment의 ChildFragmentManager에서 발생하는 경우 parentFragment.isResume ()을 사용 하여 대신 확인하십시오.

if (parentFragment.isResume()) {
    DummyFragment dummyFragment = DummyFragment.newInstance();
    transaction = childFragmentManager.BeginTransaction();
    trans.Replace(Resource.Id.fragmentContainer, startFragment);
}

5

비슷한 문제가 있었는데 시나리오는 다음과 같습니다.

  • 내 활동은 목록 조각을 추가 / 교체합니다.
  • 각 목록 조각에는 목록 항목을 클릭 할 때 활동을 알리기 위해 활동에 대한 참조가 있습니다 (관찰자 패턴).
  • 각 목록 조각은 setRetainInstance (true)를 호출 합니다. 그것의 에서 onCreate 방법.

활동onCreate 메소드는 다음 과 같습니다.

mMainFragment = (SelectionFragment) getSupportFragmentManager()
                .findFragmentByTag(MAIN_FRAGMENT_TAG);
        if (mMainFragment == null) {
            mMainFragment = new SelectionFragment();

            mMainFragment.setListAdapter(new ArrayAdapter<String>(this,
                    R.layout.item_main_menu, getResources().getStringArray(
                            R.array.main_menu)));
mMainFragment.setOnSelectionChangedListener(this);
            FragmentTransaction transaction = getSupportFragmentManager()
                    .beginTransaction();
            transaction.add(R.id.content, mMainFragment, MAIN_FRAGMENT_TAG);
            transaction.commit();
        }

구성이 변경 될 때 (장치 회전) 활동이 작성되고 단편 관리자의 히스토리에서 기본 단편이 검색되고 동시에 단편에 대한 OLD 참조 가 있기 때문에 예외가 발생 했습니다. 파괴 된 활동에

구현을 이것으로 변경하면 문제가 해결되었습니다.

mMainFragment = (SelectionFragment) getSupportFragmentManager()
                .findFragmentByTag(MAIN_FRAGMENT_TAG);
        if (mMainFragment == null) {
            mMainFragment = new SelectionFragment();

            mMainFragment.setListAdapter(new ArrayAdapter<String>(this,
                    R.layout.item_main_menu, getResources().getStringArray(
                            R.array.main_menu)));
            FragmentTransaction transaction = getSupportFragmentManager()
                    .beginTransaction();
            transaction.add(R.id.content, mMainFragment, MAIN_FRAGMENT_TAG);
            transaction.commit();
        }
        mMainFragment.setOnSelectionChangedListener(this);

프래그먼트가 오래된 파괴 된 활동 인스턴스를 참조하는 상황을 피하기 위해 활동이 작성 될 때마다 리스너를 설정해야합니다.


5

에서 상속받은 경우 다음 FragmentActivity에서 수퍼 클래스를 호출해야합니다 onActivityResult().

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    super.onActivityResult(requestCode, resultCode, intent);
    ...
}

이 작업을 수행하지 않고 해당 방법으로 조각 대화 상자를 표시하려고하면 OP가 표시 될 수 있습니다 IllegalStateException. (솔직히 말하면 슈퍼 호출이 문제를 해결하는 이유를모르겠습니다 . onActivityResult()전에 호출 onResume()되었으므로 여전히 조각 대화 상자를 표시해서는 안됩니다.)


1
이것이 문제를 해결하는 이유를 알고 싶습니다.
Big McLargeHuge

3

지도 조각 활동에서 인 텐트 선택기를 취소하기 위해 뒤로 버튼을 누를 때이 예외가 발생했습니다. onResume 코드를 onstart ()로 바꾸어이 문제를 해결했으며 응용 프로그램이 제대로 작동합니다.


2

사용하는 transaction.commitAllowingStateLoss();것이 최선의 해결책은 아니라고 생각 합니다. 이 예외는 활동의 구성이 변경되고 조각화 될 때 발생합니다.onSavedInstanceState() 가 호출 된 후 비동기 콜백 메소드가 프래그먼트를 커밋하려고 시도하면 합니다.

간단한 해결책은 활동이 구성을 변경하는지 여부를 확인할 수 있습니다.

예를 들어 확인 isChangingConfigurations()

if(!isChangingConfigurations()) { //commit transaction. }

체크 아웃 아니라 링크를


어떻게 든 사용자가 무언가를 클릭 할 때이 예외가 발생했습니다 (클릭은 transaction-commit을 수행하는 트리거입니다). 이것이 어떻게 될 수 있습니까? 여기에 해결책이 있습니까?
안드로이드 개발자

@androiddeveloper 사용자 클릭시 다른 작업을 수행하십시오. 어떻게 든 트랜잭션을 커밋하기 전에 프래그먼트가 상태를 저장하고 있습니다
Amol Desai

정확한 트랜잭션 커밋 라인에서 예외가 발생했습니다. 또한, 나는 이상한 오타가 있습니다 : "여기 여기"대신 "여기에서 일"을 의미했습니다.
안드로이드 개발자

@androiddeveloper 당신이 맞아요! 그러나 트랜잭션을 커밋하기 전에 백그라운드 스레드 또는 무언가를 생성합니까?
Amol Desai

나는 그렇게 생각하지 않지만 (죄송합니다. 사무실 밖입니다) 왜 중요한가요? 여기에 모든 UI가 있습니다 ... 배경 스레드에서 무언가를 만들면 예외가있을뿐만 아니라 UI와 관련된 항목을 배경 스레드에 넣지 않습니다. 너무 위험합니다.
안드로이드 개발자

2

내 경우에 찾은 가장 부드럽고 가장 간단한 해결책은 활동 결과에 대한 응답으로 문제가되는 조각을 스택에서 튀어 나오는 것을 피하는 것이 었습니다. 그래서이 전화를 내에서 변경 onActivityResult():

popMyFragmentAndMoveOn();

이에:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    public void run() {
        popMyFragmentAndMoveOn();
    }
}

내 경우에 도움이되었습니다.


2

onActivityResult에서 FragmentTransaction을 수행하는 경우 onActivityResult 내에서 부울 값을 설정할 수 있으며 onResume에서 부울 값을 기반으로 FragmentTransaction을 수행 할 수 있습니다. 아래 코드를 참조하십시오.

@Override
protected void onResume() {
    super.onResume;
    if(isSwitchFragment){
        isSwitchFragment=false;
        bottomNavigationView.getTabAt(POS_FEED).select();
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == FilterActivity.FILTER_REQUEST_EVENT && data != null) {
        isSwitchFragment=true;
    }
}

코드를 이미지로 게시하지 말고 대신 코드 형식을 사용하십시오.
Micer

1
그렇습니다 그것은 나를 도왔다 .., 이것은 마시맬로 장치를위한 정확하고 적당한 해결책 나는이 예외가 있고이 간단한 간계로 해결했다 .. !! 따라서 투표했다.
sandhya sasane

2

의례 : IllegalStateException에 대한 솔루션

이 문제는 많은 시간 동안 나를 귀찮게했지만 다행히도 구체적인 해결책을 찾았습니다. 이에 대한 자세한 설명은 여기에 있습니다 .

commitAllowStateloss ()를 사용하면이 예외를 방지 할 수 있지만 UI 불규칙성이 발생할 수 있습니다. 지금까지 Activity 상태가 손실 된 후 프래그먼트를 커밋하려고 할 때 IllegalStateException이 발생한다는 것을 이해했습니다. 따라서 상태가 복원 될 때까지 트랜잭션을 지연시켜야합니다. . 간단하게 이렇게 할 수 있습니다

두 개의 개인 부울 변수 선언

 public class MainActivity extends AppCompatActivity {

    //Boolean variable to mark if the transaction is safe
    private boolean isTransactionSafe;

    //Boolean variable to mark if there is any transaction pending
    private boolean isTransactionPending;

이제 onPostResume () 및 onPause에서 부울 변수 isTransactionSafe를 설정 및 설정 해제했습니다. 아이디어는 활동이 포 그라운드에있을 때만 trasnsaction을 안전하게 표시하여 상태 손실의 가능성이 없도록하는 것입니다.

/*
onPostResume is called only when the activity's state is completely restored. In this we will
set our boolean variable to true. Indicating that transaction is safe now
 */
public void onPostResume(){
    super.onPostResume();
    isTransactionSafe=true;
}
/*
onPause is called just before the activity moves to background and also before onSaveInstanceState. In this
we will mark the transaction as unsafe
 */

public void onPause(){
    super.onPause();
    isTransactionSafe=false;

}

private void commitFragment(){
    if(isTransactionSafe) {
        MyFragment myFragment = new MyFragment();
        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.add(R.id.frame, myFragment);
        fragmentTransaction.commit();
    }
}

-지금까지 수행 한 작업은 IllegalStateException에서 저장되지만 활동이 백그라운드로 이동 한 후 commitAllowStateloss ()와 같이 수행되면 트랜잭션이 손실됩니다. 이를 돕기 위해 isTransactionPending 부울 변수가 있습니다.

public void onPostResume(){
   super.onPostResume();
   isTransactionSafe=true;
/* Here after the activity is restored we check if there is any transaction pending from
the last restoration
*/
   if (isTransactionPending) {
      commitFragment();
   }
}


private void commitFragment(){

 if(isTransactionSafe) {
     MyFragment myFragment = new MyFragment();
     FragmentManager fragmentManager = getFragmentManager();
     FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
     fragmentTransaction.add(R.id.frame, myFragment);
     fragmentTransaction.commit();
     isTransactionPending=false;
 }else {
     /*
     If any transaction is not done because the activity is in background. We set the
     isTransactionPending variable to true so that we can pick this up when we come back to
foreground
     */
     isTransactionPending=true;
 }
}

2

조각 트랜잭션은 이후에 실행해서는 안됩니다 Activity.onStop()! 이후에 트랜잭션을 실행할 수있는 콜백이 없는지 확인하십시오 onStop(). 다음과 같은 접근 방식으로 문제를 해결하려고 시도하는 대신 이유를 해결하는 것이 좋습니다..commitAllowingStateLoss()


1

지원 라이브러리 버전에서 시작 24.0.0 당신이 호출 할 수 있습니다 FragmentTransaction.commitNow()를 호출하는 대신 동 기적으로 트랜잭션을 커밋 방법 commit()다음을 executePendingTransactions(). 마찬가지로 문서는 더 나은이 방법은 말한다 :

commitNow를 호출하는 것이 commit ()을 호출 한 다음 executePendingTransactions ()를 호출하는 것보다 선호됩니다. 후자는 원하는 동작인지 여부에 관계없이 현재 보류중인 모든 트랜잭션을 커밋하려고 시도하는 부작용이 있습니다.


1

활동에 프래그먼트를로드하려고 할 때마다 활동이 재개되고 일시 중지 상태가 아닌지 확인하십시오. 일시 중지 상태에서는 수행 된 커밋 작업이 손실 될 수 있습니다.

transaction.commit () 대신 transaction.commitAllowingStateLoss ()를 사용하여 조각을로드 할 수 있습니다.

또는

부울을 작성하고 활동이 일시 중지되지 않는지 확인하십시오.

@Override
public void onResume() {
    super.onResume();
    mIsResumed = true;
}

@Override
public void onPause() {
    mIsResumed = false;
    super.onPause();
}

그런 다음 조각 검사를로드하는 동안

if(mIsResumed){
//load the your fragment
}


1

@Anthonyeef 훌륭한 답변과 관련하여 Java의 샘플 코드는 다음과 같습니다.

private boolean shouldShowFragmentInOnResume;

private void someMethodThatShowsTheFragment() {

    if (this.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
        showFragment();
    } else {
        shouldShowFragmentInOnResume = true;
    }
}

private void showFragment() {
    //Your code here
}

@Override
protected void onResume() {
    super.onResume();

    if (shouldShowFragmentInOnResume) {
        shouldShowFragmentInOnResume = false;
        showFragment();
    }
}

1

popBackStack () 또는 popBackStackImmediate () 메소드와 충돌하는 경우 다음을 사용하여 fixt를 시도하십시오.

        if (!fragmentManager.isStateSaved()) {
            fragmentManager.popBackStackImmediate();
        }

이것은 나에게도 효과가 있습니다.


참고이 API 26 이상이 필요하다는
Itay 펠드만

1

내 경우에는 onActivityResult라는 재정의 메서드 에서이 오류가 발생했습니다. 파고 난 후에 나는 아마도 ' 슈퍼 ' 를 호출해야한다고 생각했습니다 .
나는 그것을 추가했고 그것은 효과가 있었다.

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data); //<--- THIS IS THE SUPPER CALL
    if (resultCode == Activity.RESULT_OK && requestCode == 0) {
        mostrarFragment(FiltroFragment.newInstance())
    }

}

어쩌면 코드 전에 수행중인 재정의에 '슈퍼'를 추가해야 할 수도 있습니다.


1

코 틀린 확장

fun FragmentManager?.replaceAndAddToBackStack(
    @IdRes containerViewId: Int,
    fragment: () -> Fragment,
    tag: String
) {
    // Find and synchronously remove a fragment with the same tag.
    // The second transaction must start after the first has finished.
    this?.findFragmentByTag(tag)?.let {
        beginTransaction().remove(it).commitNow()
    }
    // Add a fragment.
    this?.beginTransaction()?.run {
        replace(containerViewId, fragment, tag)
        // The next line will add the fragment to a back stack.
        // Remove if not needed.
        // You can use null instead of tag, but tag is needed for popBackStack(), 
        // see https://stackoverflow.com/a/59158254/2914140
        addToBackStack(tag)
    }?.commitAllowingStateLoss()
}

용법:

val fragment = { SomeFragment.newInstance(data) }
fragmentManager?.replaceAndAddToBackStack(R.id.container, fragment, SomeFragment.TAG)


@Claire, 감사합니다! 로 변경 fragment: Fragment하시겠습니까? 예,이 변형을 시도했지만이 경우 모든 경우에 프래그먼트가 생성됩니다 (fragmentManager == null이지만이 상황이 발생하지 않은 경우에도). 답변을 업데이트하고에서 null 태그를 변경했습니다 addToBackStack().
CoolMind


0

활동에 이것을 추가하십시오

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if (outState.isEmpty()) {
        // Work-around for a pre-Android 4.2 bug
        outState.putBoolean("bug:fix", true);
    }
}

0

또한이 문제가 발생하여 상황 FragmentActivity이 변경 될 때마다 문제가 발생합니다 (예 : 화면 방향 변경 등). 따라서 가장 좋은 해결책은에서 컨텍스트를 업데이트하는 것 FragmentActivity입니다.


0

기본 조각을 만들고 앱의 모든 조각을 확장하도록 만들었습니다.

public class BaseFragment extends Fragment {

    private boolean mStateSaved;

    @CallSuper
    @Override
    public void onSaveInstanceState(Bundle outState) {
        mStateSaved = true;
        super.onSaveInstanceState(outState);
    }

    /**
     * Version of {@link #show(FragmentManager, String)} that no-ops when an IllegalStateException
     * would otherwise occur.
     */
    public void showAllowingStateLoss(FragmentManager manager, String tag) {
        // API 26 added this convenient method
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (manager.isStateSaved()) {
                return;
            }
        }

        if (mStateSaved) {
            return;
        }

        show(manager, tag);
    }
}

그런 다음 조각을 표시하려고 할 때 showAllowingStateLoss대신show

이처럼 :

MyFragment.newInstance()
.showAllowingStateLoss(getFragmentManager(), MY_FRAGMENT.TAG);

이 PR 에서이 솔루션을 찾았습니다 : https://github.com/googlesamples/easypermissions/pull/170/files


0

모든 경우에 도움이되는지 확실하지 않은 또 다른 가능한 해결 방법은 다음과 같습니다 ( here ).

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        final View rootView = findViewById(android.R.id.content);
        if (rootView != null) {
            rootView.cancelPendingInputEvents();
        }
    }
}

0

@Ovidiu Latcu에 의해 허용되는 답변이 있지만 잠시 후에도 오류가 계속 발생합니다.

@Override
protected void onSaveInstanceState(Bundle outState) {
     //No call for super(). Bug on API Level > 11.
}

Crashlytics가 여전히이 이상한 오류 메시지를 보냅니다.

그러나 오류는 이제 버전 7 이상에서만 발생합니다 (누가) 내 수정은 commitAllowingStateLoss () 를 사용하는 것이 었습니다. fragmentTransaction에서 commit () 대신 .

포스트 은 commitAllowingStateLoss ()에 도움이되었으며 다시는 조각 문제가 발생하지 않았습니다.

요약하면 여기에서 허용되는 답변은 Nougat 이전 Android 버전에서 작동 할 수 있습니다.

이렇게하면 몇 시간의 검색 시간을 절약 할 수 있습니다. 행복한 코딩. <3 건배

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.