รับข้อยกเว้น“ IllegalStateException: ไม่สามารถทำการกระทำนี้หลังจาก onSaveInstanceState”


355

ฉันมีแอปพลิเคชั่น Live Android และจากตลาดฉันได้รับการติดตามสแต็กและฉันไม่รู้ว่าทำไมมันเกิดขึ้นเพราะมันไม่ได้เกิดขึ้นในรหัสแอปพลิเคชัน แต่มันเกิดขึ้นจากบางส่วนหรือเหตุการณ์อื่น ๆ

ฉันไม่ได้ใช้ Fragments ยังมีการอ้างอิง FragmentManager อยู่ หากร่างกายใด ๆ สามารถโยนแสงบางอย่างกับข้อเท็จจริงที่ซ่อนอยู่เพื่อหลีกเลี่ยงปัญหาประเภทนี้:

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.onKeyDown(Activity.java:1962)
at android.view.KeyEvent.dispatch(KeyEvent.java:2482)
at android.app.Activity.dispatchKeyEvent(Activity.java:2274)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
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.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:1720)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1258)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
at android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.java:2851)
at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2824)
at android.view.ViewRoot.handleMessage(ViewRoot.java:2011)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4025)
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:841)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
at dalvik.system.NativeStart.main(Native Method)  

คุณหาทางออกหรือยัง? มีปัญหาเดียวกันที่นี่: stackoverflow.com/questions/7575921/…
nhaarman


2
@phlebas ไม่คุณไม่ได้ทำ บทสนทนาของคุณกังวลและสิ่งนี้ไม่ได้ บรรทัดบนสุดของการจับคู่การติดตามสแต็กของคุณไม่เพียงพอ ที่เหลือแตกต่างกันมาก ฉันพูดแบบนี้เพราะฉันเพิ่งไปดูปัญหาของคุณและมันช่วยฉันไม่ได้เลย
themightyjon

คุณใช้เธรดหรือ AsynTask ในกิจกรรมนั้นหรือไม่?
José Castro

21
ฉันพูดถึงข้อผิดพลาดนี้ในโพสต์บล็อกของฉัน... คุณควรอ่าน :)
Alex Lockwood

คำตอบ:


455

นี่เป็นข้อผิดพลาดที่โง่ที่สุดที่ฉันเคยพบมา ฉันมีFragmentแอปพลิเคชันที่ทำงานได้อย่างสมบูรณ์แบบสำหรับAPI <11และForce ClosingบนAPI> 1111

ฉันไม่สามารถทราบได้ว่าสิ่งที่พวกเขาเปลี่ยนไปในActivityวงจรชีวิตของการโทรเป็นsaveInstanceอย่างไร แต่ฉันนี่คือวิธีที่ฉันแก้ไขสิ่งนี้:

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

ฉันไม่ได้โทร.super()และทุกอย่างใช้งานได้ดี ฉันหวังว่าสิ่งนี้จะช่วยคุณประหยัดเวลา

แก้ไข:หลังจากการวิจัยเพิ่มเติมนี่เป็นข้อผิดพลาดที่รู้จักในแพ็คเกจการสนับสนุน

หากคุณต้องการบันทึกอินสแตนซ์และเพิ่มบางสิ่งลงในของoutState Bundleคุณคุณสามารถใช้สิ่งต่อไปนี้:

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

แก้ไข 2:สิ่งนี้อาจเกิดขึ้นได้หากคุณพยายามทำธุรกรรมหลังจากที่คุณActivityหายไปในพื้นหลัง เพื่อหลีกเลี่ยงปัญหานี้คุณควรใช้commitAllowingStateLoss()

EDIT3:การแก้ปัญหาข้างต้นแก้ไขปัญหาในไลบรารี support.v4 ก่อนหน้านี้จากสิ่งที่ฉันจำได้ แต่ถ้าคุณยังมีปัญหาเกี่ยวกับสิ่งนี้คุณต้องอ่านบล็อกของ@AlexLockwoodด้วย: การแยกส่วนธุรกรรมและการสูญเสียสถานะกิจกรรม

สรุปจากโพสต์บล็อก (แต่ฉันขอแนะนำให้คุณอ่าน):

  • ไม่เคย commit()ทำธุรกรรมหลังจากonPause()ในรังผึ้งล่วงหน้าและonStop()หลังรังผึ้ง
  • โปรดใช้ความระมัดระวังเมื่อทำธุรกรรมภายในActivityวิธีวงจรชีวิต การใช้งาน onCreate() , onResumeFragments()และonPostResume()
  • หลีกเลี่ยงการทำธุรกรรมภายในวิธีการโทรกลับแบบอะซิงโครนัส
  • ใช้commitAllowingStateLoss()เป็นทางเลือกสุดท้ายเท่านั้น

97
คุณควรใช้ commitAllowingStateLoss () แทนการกระทำ ()
Meh

7
ดังนั้นการไม่เรียก super ใน onSaveInstanceState จะหยุด FragmentManager เพื่อให้สามารถบันทึกสถานะของแฟรกเมนต์ทั้งหมดและเรียกคืนได้ ที่อาจทำให้เกิดปัญหากับการหมุน นอกจากนี้ฉันเพิ่งลองทำสิ่งอื่น ๆ เกี่ยวกับการวางขยะลงในชุดรวมและนั่นก็ไม่ได้สร้างความแตกต่างให้ฉัน ไม่แน่ใจว่ามันจะเป็นอย่างไร - ข้อผิดพลาดที่คุณอ้างอิงในแพคเกจการสนับสนุนคือ NullPointerException และดูเหมือนจะไม่เป็นไปอย่างนี้ IllegalStateException ...
themightyjon

56
@meh commitAllowingStateLoss()หลีกเลี่ยงข้อยกเว้นเท่านั้น มันไม่ได้ปกป้องแอปพลิเคชันของคุณจากการสูญเสียสถานะโดยไม่ตั้งใจ ดูโพสต์บล็อกนี้
Alex Lockwood

2
@AlexLockwood จากบล็อกโพสต์นั้นเราสามารถเรียนรู้ว่าเราควรทำการโทรผ่านเครือข่ายของเราทั้งหมดภายในแฟรกเมนต์ (และแสดงความคืบหน้าชั่วคราวหากจำเป็น) และนั่นเป็นวิธีเดียวที่เราสามารถหลีกเลี่ยงข้อยกเว้นนี้ได้ กำลังถูกเรียกหลังจากการเรียกใช้วิธีการแบบอะซิงโครนัส
Meh

1
ฉันไม่ได้รับจุดแรก: "ไม่เคยทำธุรกรรม () หลังจาก ... onStop () บนโพสต์รังผึ้ง" ถ้าฉันต้องการปุ่มเพื่อทริกเกอร์ชิ้นส่วนให้ถูกแทนที่ด้วยอันอื่น ฉันควรใส่บูลีนที่ตรวจสอบว่ากิจกรรมเสร็จสิ้นใน Stop หรือไม่และถ้าเป็นเช่นนั้นให้เรียก commitAllowingStateLoss แทนหรือไม่ นอกจากนี้ถ้าฉันมีแฟรกเมนต์อยู่ในแฟรกเมนต์ฉันต้องแทนที่เมื่อคลิกปุ่ม?
นักพัฒนา android

76

การดูในซอร์สโค้ด Android เกี่ยวกับสิ่งที่ทำให้เกิดปัญหานี้จะทำให้ค่าสถานะ mStateSaved ในFragmentManagerImplคลาส (อินสแตนซ์ที่มีอยู่ในกิจกรรม) มีค่าเป็นจริง มันถูกตั้งค่าเป็นจริงเมื่อสแต็คกลับจะถูกบันทึกไว้ (saveAllState) Activity#onSaveInstanceStateบนโทรศัพท์จาก หลังจากนั้นโทรจาก ActivityThread ไม่ได้ตั้งค่าสถานะนี้โดยใช้วิธีการตั้งค่าที่มีอยู่จากและFragmentManagerImpl#noteStateNotSaved()dispatch()

วิธีที่ฉันเห็นมันมีการแก้ไขบางอย่างที่มีขึ้นอยู่กับสิ่งที่แอพของคุณกำลังทำและใช้:

วิธีที่ดี

ก่อนสิ่งอื่น: ฉันจะโฆษณาบทความอเล็กซ์ล็อควู้ด จากสิ่งที่ฉันทำไปแล้ว:

  1. สำหรับชิ้นส่วนและกิจกรรมที่ไม่จำเป็นต้องเก็บข้อมูลใด ๆ ของรัฐโทรcommitAllowStateLoss นำมาจากเอกสาร:

    อนุญาตให้คอมมิชชันที่จะดำเนินการหลังจากบันทึกกิจกรรมของรัฐ นี่เป็นอันตรายเพราะการกระทำอาจสูญหายได้หากกิจกรรมจำเป็นต้องกู้คืนจากสถานะในภายหลังดังนั้นจึงควรใช้เฉพาะกรณีที่โอเคสำหรับสถานะ UI ที่จะเปลี่ยนโดยไม่คาดคิดกับผู้ใช้ ฉันเดาว่ามันไม่เป็นไรถ้าใช้แฟรกเมนต์แสดงข้อมูลแบบอ่านอย่างเดียว หรือแม้ว่าพวกเขาจะแสดงข้อมูลที่แก้ไขได้ให้ใช้วิธีการโทรกลับเพื่อเก็บข้อมูลที่แก้ไข

  2. หลังจากทำรายการเสร็จแล้ว (คุณเพิ่งโทรมาcommit()) โทรไปFragmentManager.executePendingTransactions()หา

ไม่แนะนำวิธี:

  1. ในฐานะที่เป็น Ovidiu Latcu super.onSaveInstanceState()ดังกล่าวข้างต้นจะไม่เรียก แต่นั่นหมายความว่าคุณจะสูญเสียสถานะกิจกรรมทั้งหมดของคุณไปพร้อมกับการแยกส่วน

  2. แทนที่และมีโทรเท่านั้นonBackPressed finish()นี่ควรจะโอเคถ้าคุณไม่ได้ใช้ API ของ Fragments ในขณะที่มีการเรียกไปยังsuper.onBackPressedFragmentManager#popBackStackImmediate()

  3. หากคุณกำลังใช้ทั้ง Fragments API และสถานะของกิจกรรมของคุณมีความสำคัญ / สำคัญคุณสามารถลองใช้การสะท้อน API FragmentManagerImpl#noteStateNotSaved()ได้ แต่นี่คือการแฮ็กหรืออาจกล่าวได้ว่าเป็นการแก้ปัญหา ฉันไม่ชอบ แต่ในกรณีของฉันมันค่อนข้างยอมรับได้เพราะฉันมีรหัสจากแอปรุ่นเก่าที่ใช้รหัสที่เลิกใช้แล้ว ( TabActivityและโดยปริยายLocalActivityManager)

ด้านล่างเป็นรหัสที่ใช้การสะท้อน:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    invokeFragmentManagerNoteStateNotSaved();
}

@SuppressWarnings({ "rawtypes", "unchecked" })
private void invokeFragmentManagerNoteStateNotSaved() {
    /**
     * For post-Honeycomb devices
     */
    if (Build.VERSION.SDK_INT < 11) {
        return;
    }
    try {
        Class cls = getClass();
        do {
            cls = cls.getSuperclass();
        } while (!"Activity".equals(cls.getSimpleName()));
        Field fragmentMgrField = cls.getDeclaredField("mFragments");
        fragmentMgrField.setAccessible(true);

        Object fragmentMgr = fragmentMgrField.get(this);
        cls = fragmentMgr.getClass();

        Method noteStateNotSavedMethod = cls.getDeclaredMethod("noteStateNotSaved", new Class[] {});
        noteStateNotSavedMethod.invoke(fragmentMgr, new Object[] {});
        Log.d("DLOutState", "Successful call for noteStateNotSaved!!!");
    } catch (Exception ex) {
        Log.e("DLOutState", "Exception on worka FM.noteStateNotSaved", ex);
    }
}

ไชโย!


ดูเหมือนว่าจะเกิดขึ้นกับ ActionBarSherlock ด้วยเช่นกันภายใต้ Gingerbread ดังนั้นกรณีของการตรวจสอบ Build Id ก็ดูเหมือนว่าจะเป็นสิ่งที่สงสัย ... :(
t0mm13b

นอกจากนี้คุณควรชี้ให้เห็น - ที่ไม่เหมาะสำหรับการใช้งาน ABS อย่างใดอย่างหนึ่ง :)
t0mm13b

@ t0mm13b: โค้ดข้างต้นหมายถึงโปรเจ็กต์ของฉันเนื่องจากไม่ได้ใช้แฟรกเมนต์ใด ๆ และไม่สนับสนุนแฟรกเมนต์แอคทีฟ มันทำงานบน android.app.Activity และเนื่องจากความไม่สอดคล้องที่ทำให้เกิดข้อยกเว้นเกิดขึ้นจาก FragmentManager (ระดับ API 11 ขึ้นไป) นั่นเป็นเหตุผลที่ตรวจสอบ ... ถ้าคุณเชื่อว่าแหล่งที่มาของความชั่วร้ายเหมือนกันสำหรับคุณเช่นกัน เพื่อลบการตรวจสอบ ABS เป็นเรื่องที่แตกต่างกันขณะที่มันทำงานอยู่ด้านบนของแพคเกจความเข้ากันได้และการใช้งานของการสนับสนุนการจัดระเบียบกิจกรรมอาจใช้การดำเนินการเดียวกันของ FragmentManager และ voila: ปัญหาเดียวกัน
gunar

@ t0mm13b: เพื่อเพิ่มมากขึ้นเนื่องจาก 600 ตัวอักษรไม่เพียงพอคุณต้องตรวจสอบก่อนว่าอะไรเป็นสาเหตุของปัญหานี้สำหรับคุณ คุณต้องตระหนักว่าข้างต้นเป็นแฮ็คที่น่าเกลียดและฉันจะไม่รับผิดชอบใด ๆ ถ้ามันทำงานหรือไม่ (สำหรับฉันเป็นทางออกที่ดีที่สุดในการพิจารณาสถานการณ์) หากคุณจำเป็นต้องใช้ให้ตรวจสอบซ้ำในซอร์สโค้ดที่เข้ากันได้เพื่อหาค่าตัวแปรซึ่งอาจแตกต่างจากแพ็คเกจมาตรฐาน ฉันหวังว่าปัญหานี้จะได้รับการแก้ไขโดยแพ็คเกจความเข้ากันได้รุ่นต่อไป แต่จากประสบการณ์ Android มีโอกาสน้อยที่จะเกิดขึ้น ...
gunar

อืม ... มันเป็นปัญหาเดียวกันกับในรายงานบั๊ก นี้ซึ่งเป็นประเด็นของคำถามของ OP นี้ ฉันยืนตามความคิดเห็นของฉัน - คุณควรใส่คำปฏิเสธอย่างชัดเจนและบอกว่ามันไม่รับประกันและควรระบุด้วยว่าคุณไม่ได้ใช้ชิ้นส่วน - มิฉะนั้นทำไมต้องโพสต์คำตอบนั้นด้วย! :) เพียงแค่พูดว่า ...
t0mm13b

35

ข้อยกเว้นดังกล่าวจะเกิดขึ้นหากคุณพยายามเปลี่ยนชิ้นส่วนหลังจากกิจกรรมแฟรกเมนต์ของคุณonSaveInstanceState()ถูกเรียก

เหตุผลหนึ่งที่สามารถเกิดขึ้นได้คือถ้าคุณปล่อยให้AsyncTask(หรือThread) ทำงานเมื่อกิจกรรมหยุดทำงาน

การเปลี่ยนแปลงใด ๆ หลังจากonSaveInstanceState()ถูกเรียกอาจทำให้หลงทางได้หากระบบเรียกคืนกิจกรรมสำหรับทรัพยากรและสร้างใหม่ในภายหลัง


2
เฮ้กลัวฉันมีคำถามที่นี่ทำไม onBackPressed สามารถถูกเรียกในกิจกรรมหรือส่วนนั้นถ้ากิจกรรมหรือชิ้นส่วนนั้นถูกหยุด ดูเหมือนว่าข้อยกเว้นข้างต้นจะเกิดขึ้นจากเหตุการณ์ UI บางอย่าง (เช่นการกดปุ่ม BACK) ฉันไม่สามารถหาความสัมพันธ์ระหว่าง Async Task และปุ่ม Back ได้
dcool

เนื่องจากคุณสามารถบันทึกการเปลี่ยนส่วนเป็นสถานะย้อนกลับการกดปุ่มย้อนกลับอาจทำให้เกิดการย้อนกลับของการเปลี่ยนแปลงที่คุณบันทึก (เพื่อให้ชิ้นส่วนเก่ากลับมา) onSaveInstanceState ถูกเรียกใช้ก่อนที่กิจกรรมของคุณจะถูกทำลายเพื่อกู้คืนทรัพยากรไปยังระบบ แต่ไม่เสมอไปหลังจากเรียกใช้ onStop ขออภัยที่ไม่ชัดเจนในคำตอบของฉัน
FunkTheMonk

กลัว แต่ฉันไม่ได้ใช้แฟรกเมนต์ในแอปพลิเคชันของฉัน อาจเป็นชิ้นส่วนที่ใช้ในรหัสเนทีฟ ก่อนหน้านี้ฉันคิดว่าคุณกำลังพูดถึงเรื่องเดียวกัน
dcool

1
ฉันมี AsyncTask ที่มีการอ้างอิงถึงส่วน แก้ไขปัญหาหลังจากลบการเรียก super () จาก onSaveInstanceState และแทนที่การอ้างอิงจาก AsyncTask ของฉันด้วย WeakReference <Fragment>
บัฟฟาโล่

2
@Buffalo แน่นอนว่าไม่ใช่วิธีแก้ปัญหา super.onSaveInstanceState()คุณควรโทร
Alex Lockwood

27

เพียงเรียกsuper.onPostResume ()ก่อนที่จะแสดงแฟรกเมนต์ของคุณหรือย้ายโค้ดของคุณในเมธอด onPostResume () หลังจากเรียก super.onPostResume () นี่เป็นการแก้ปัญหา!


6
การเรียกใช้ onPostResume () ทำให้มั่นใจได้ว่า onResumeFragments () ได้รับการโทรหาและสำหรับฉันนี่เป็นทางออกที่ดี
j2emanue

20

สิ่งนี้สามารถเกิดขึ้นได้เมื่อเรียกdismiss()ใช้แฟรกเมนต์ส่วนโต้ตอบหลังจากหน้าจอถูกล็อค \ blanked และสถานะอินสแตนซ์ของไดอะล็อกของกิจกรรม + ได้รับการบันทึกแล้ว วิธีรับสาย:

dismissAllowingStateLoss()

แท้จริงทุกครั้งที่ฉันยกเลิกการโต้ตอบฉันไม่สนใจเกี่ยวกับสถานะของมันอีกต่อไปดังนั้นมันก็โอเคที่จะทำ - คุณไม่สูญเสียสถานะใด ๆ


2
นี่คือปัญหาที่แน่นอนของฉัน! คุณเป็นคนที่ยอดเยี่ยม!
Tash Pemhiwa

17

โซลูชันระยะสั้นและทำงาน:

ทำตามขั้นตอนง่าย ๆ :

ขั้นตอนที่ 1 : แทนที่สถานะ onSaveInstanceState ในส่วนที่เกี่ยวข้อง และลบ super method ออกจากมัน

@Override
public void onSaveInstanceState(Bundle outState) {
};

ขั้นตอนที่ 2 : ใช้ CommitAllowingStateLoss (); แทนที่จะกระทำ (); ในขณะที่การดำเนินงานส่วน

fragmentTransaction.commitAllowingStateLoss();

1
เคล็ดลับในการลบเมธอด super ออกคุณช่วยอธิบายได้ไหมว่าทำไม? การลบนั้นปลอดภัยไหม
Bruce

7
มันไม่ปลอดภัยที่จะลบ super () คุณจะสูญเสียข้อมูลอื่น ๆ หลังจากนั้น!
เดดฟิช

12

ฉันคิดว่าสถานะระยะเวลาการใช้งานสามารถช่วยป้องกันความผิดพลาดดังกล่าวที่เริ่มต้นจากการสนับสนุน Android lib v26.1.0 คุณสามารถตรวจสอบสิ่งต่อไปนี้ได้:

if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)){
  // Do fragment's transaction commit
}

หรือคุณสามารถลอง:

Fragment.isStateSaved()

ข้อมูลเพิ่มเติมที่นี่ https://developer.android.com/reference/android/support/v4/app/Fragment.html#isStateSaved ()


7

สิ่งนี้ใช้ได้กับฉัน ... พบสิ่งนี้ด้วยตัวเอง ... หวังว่ามันจะช่วยคุณได้!

1) ไม่มี FragmentManager "FragmentManager / FragmentTrans" ทั่วโลก

2) onCreate เริ่มต้น FragmentManager ใหม่เสมอ!

ตัวอย่างด้านล่าง: -

public abstract class FragmentController extends AnotherActivity{
protected FragmentManager fragmentManager;
protected FragmentTransaction fragmentTransaction;
protected Bundle mSavedInstanceState;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mSavedInstanceState = savedInstanceState;
    setDefaultFragments();
}

protected void setDefaultFragments() {
    fragmentManager = getSupportFragmentManager();
    //check if on orientation change.. do not re-add fragments!
    if(mSavedInstanceState == null) {
        //instantiate the fragment manager

        fragmentTransaction = fragmentManager.beginTransaction();

        //the navigation fragments
        NavigationFragment navFrag = new NavigationFragment();
        ToolbarFragment toolFrag = new ToolbarFragment();

        fragmentTransaction.add(R.id.NavLayout, navFrag, "NavFrag");
        fragmentTransaction.add(R.id.ToolbarLayout, toolFrag, "ToolFrag");
        fragmentTransaction.commitAllowingStateLoss();

        //add own fragment to the nav (abstract method)
        setOwnFragment();
    }
}

6

ฉันได้รับสิ่งนี้เสมอเมื่อฉันพยายามแสดงแฟรกเมนต์ในเมธอด onActivityForResult () ดังนั้นปัญหาจึงเกิดขึ้นต่อไป:

  1. กิจกรรมของฉันถูกหยุดชั่วคราวและหยุดซึ่งหมายความว่า onSaveInstanceState () ถูกเรียกใช้แล้ว (สำหรับอุปกรณ์ทั้งล่วงหน้าและรังผึ้งหลัง)
  2. ในกรณีที่มีผลลัพธ์ใด ๆ ฉันทำธุรกรรมเพื่อแสดง / ซ่อนส่วนซึ่งทำให้ IllegalStateException นี้

สิ่งที่ฉันทำคือถัดไป:

  1. เพิ่มมูลค่าสำหรับการพิจารณาว่าการกระทำที่ฉันต้องการได้ทำไปแล้วหรือไม่ (เช่นการถ่ายภาพจาก camere - isPhotoTaken) - สามารถเป็นค่าบูลีนหรือค่าจำนวนเต็มขึ้นอยู่กับธุรกรรมที่คุณต้องการ
  2. ใน overriden วิธี onResumeFragments () ฉันตรวจสอบค่าของฉันและหลังจากทำธุรกรรมส่วนฉันต้องการ ในกรณีนี้ commit () ไม่ได้ทำหลังจาก onSaveInstanceState เนื่องจากสถานะถูกส่งคืนในเมธอด onResumeFragments ()

5

ฉันแก้ไขปัญหาด้วยการกำหนดค่าการเปลี่ยนแปลง เคล็ดลับก็คือตามวงจรชีวิตของกิจกรรม Android เมื่อคุณเรียกความตั้งใจอย่างชัดเจน (เจตนาของกล้องหรือสิ่งอื่นใด) กิจกรรมถูกหยุดชั่วคราวและ onsavedInstance ถูกเรียกใช้ในกรณีนั้น เมื่อหมุนอุปกรณ์ไปยังตำแหน่งอื่นนอกเหนือจากที่อยู่ระหว่างการทำงานกิจกรรม การดำเนินการแยกส่วนเช่นการส่งชิ้นส่วนทำให้เกิดข้อยกเว้นสถานะที่ผิดกฎหมาย มีหลายคนบ่นเกี่ยวกับมัน มันเป็นบางสิ่งบางอย่างเกี่ยวกับการจัดการวงจรกิจกรรม Android และการเรียกใช้วิธีการที่เหมาะสม ในการแก้ปัญหาฉันได้ทำสิ่งนี้: 1-Override วิธี onsavedInstance ของกิจกรรมของคุณและกำหนดการวางแนวหน้าจอปัจจุบัน (แนวตั้งหรือแนวนอน) จากนั้นตั้งค่าการวางแนวหน้าจอของคุณก่อนที่กิจกรรมของคุณจะหยุดชั่วคราว วิธีนี้จะทำให้กิจกรรมที่คุณล็อคการหมุนหน้าจอสำหรับกิจกรรมของคุณในกรณีที่มันถูกหมุนโดยอีกกิจกรรมหนึ่ง 2-override onresume method ของกิจกรรมและตั้งค่าโหมดปฐมนิเทศของคุณเป็น sensor เพื่อที่ว่าหลังจากที่ onsaved method ถูกเรียกมันจะเรียกอีกครั้ง onconfiguration เพื่อจัดการกับการหมุนอย่างถูกต้อง

คุณสามารถคัดลอก / วางรหัสนี้ในกิจกรรมของคุณเพื่อจัดการกับมัน:

@Override
protected void onSaveInstanceState(Bundle outState) {       
    super.onSaveInstanceState(outState);

    Toast.makeText(this, "Activity OnResume(): Lock Screen Orientation ", Toast.LENGTH_LONG).show();
    int orientation =this.getDisplayOrientation();
    //Lock the screen orientation to the current display orientation : Landscape or Potrait
    this.setRequestedOrientation(orientation);
}

//A method found in stackOverflow, don't remember the author, to determine the right screen orientation independently of the phone or tablet device 
public int getDisplayOrientation() {
    Display getOrient = getWindowManager().getDefaultDisplay();

    int orientation = getOrient.getOrientation();

    // Sometimes you may get undefined orientation Value is 0
    // simple logic solves the problem compare the screen
    // X,Y Co-ordinates and determine the Orientation in such cases
    if (orientation == Configuration.ORIENTATION_UNDEFINED) {
        Configuration config = getResources().getConfiguration();
        orientation = config.orientation;

        if (orientation == Configuration.ORIENTATION_UNDEFINED) {
        // if height and widht of screen are equal then
        // it is square orientation
            if (getOrient.getWidth() == getOrient.getHeight()) {
                orientation = Configuration.ORIENTATION_SQUARE;
            } else { //if widht is less than height than it is portrait
                if (getOrient.getWidth() < getOrient.getHeight()) {
                    orientation = Configuration.ORIENTATION_PORTRAIT;
                } else { // if it is not any of the above it will defineitly be landscape
                    orientation = Configuration.ORIENTATION_LANDSCAPE;
                }
            }
        }
    }
    return orientation; // return value 1 is portrait and 2 is Landscape Mode
}

@Override
public void onResume() {
    super.onResume();
    Toast.makeText(this, "Activity OnResume(): Unlock Screen Orientation ", Toast.LENGTH_LONG).show();
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
} 

4

ฉันมีปัญหาเดียวกันการรับ IllegalStateException แต่การแทนที่การเรียกทั้งหมดของฉันเพื่อส่ง () ด้วย commitAllowingStateLoss () ไม่ได้ช่วย

ผู้ร้ายคือการเรียกไปยัง DialogFragment.show ()

ฉันล้อมรอบด้วย

try {
    dialog.show(transaction, "blah blah");
}
catch(IllegalStateException e) {
    return;
}

และนั่นก็ทำ ตกลงฉันไม่ได้แสดงข้อความโต้ตอบ แต่ในกรณีนี้ไม่เป็นไร

มันเป็นที่เดียวในแอปของฉันที่ฉันเรียก FragmentManager.beginTransaction () แต่ไม่เคยเรียกว่า commit () ดังนั้นฉันจึงไม่พบมันเมื่อฉันค้นหา "commit ()"

สิ่งที่ตลกคือผู้ใช้ไม่เคยออกจากแอป นักฆ่าคือโฆษณาคั่นระหว่างหน้าของ AdMob แทน


3
กันที่นี่ ฉันได้แก้ไขการแทนที่ 'show (FragmentManager manager, String tag)' method แทนที่ 'commit' ด้วย 'commitAllowingStateLoss'; สูญเสียบางอย่างเนื่องจากฉันไม่สามารถตั้งค่าคุณลักษณะส่วนตัวสองอย่างของ Dialog: mDismissed และ mShownByMe แต่ดูเหมือนว่าจะทำงานทุกครั้ง :)
ฟรานเชสโก Ditrani

ฉันได้แก้ปัญหาทางเลือกใน DialogFragment แล้วซึ่งสามารถหลีกเลี่ยงข้อยกเว้นนี้ได้: github.com/AndroidDeveloperLB/DialogShard
นักพัฒนา Android

4

โซลูชันของฉันสำหรับปัญหานั้นคือ

ในวิธีการเพิ่มส่วน:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    ...
    guideMapFragment = (SupportMapFragment)a.getSupportFragmentManager().findFragmentById(R.id.guideMap);
    guideMap = guideMapFragment.getMap();
    ...
}

@Override
public void onDestroyView() {
    SherlockFragmentActivity a = getSherlockActivity();
    if (a != null && guideMapFragment != null) {
        try {
            Log.i(LOGTAG, "Removing map fragment");
            a.getSupportFragmentManager().beginTransaction().remove(guideMapFragment).commit();
            guideMapFragment = null;
        } catch(IllegalStateException e) {
            Log.i(LOGTAG, "IllegalStateException on exit");
        }
    }
    super.onDestroyView();
}

อาจไม่ดี แต่ไม่พบสิ่งใดดีกว่า


ความจริง .. การจับข้อยกเว้นอาจหลีกเลี่ยงความผิดพลาดของแอปพลิเคชัน แต่ปัญหาพฤติกรรมชิ้นส่วนเช่นถูกทิ้งไว้บนหน้าจอหรือไม่ได้รับการเพิ่ม
Marcos Vasconcelos

4
เลื่อนต่อไป ความจริงมี
อยู่จริง

4

ฉันได้รับปัญหานี้ แต่ฉันคิดว่าปัญหานี้ไม่เกี่ยวข้องกับการคอมมิทและ commitAllowStateLoss

การติดตามสแต็กและข้อความข้อยกเว้นต่อไปนี้เกี่ยวกับการกระทำ ()

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)

แต่ข้อยกเว้นนี้เกิดจาก onBackPressed ()

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(Unknown Source)
at android.support.v4.app.FragmentManagerImpl.popBackStackImmediate(Unknown Source)
at android.support.v4.app.FragmentActivity.onBackPressed(Unknown Source)

พวกเขาทั้งหมดเกิดจาก checkStateLoss ()

private void checkStateLoss() {
    if (mStateSaved) {
        throw new IllegalStateException(
                "Can not perform this action after onSaveInstanceState");
    }
    if (mNoTransactionsBecause != null) {
        throw new IllegalStateException(
                "Can not perform this action inside of " + mNoTransactionsBecause);
    }

mStateSaved จะเป็นจริงหลังจาก onSaveInstanceState

ปัญหานี้เกิดขึ้นน้อยมากฉันไม่เคยพบปัญหานี้ฉันไม่สามารถกลับเป็นปัญหาได้อีก

ฉันพบปัญหา 25517

มันอาจเกิดขึ้นในสถานการณ์ต่อไปนี้

  1. ปุ่มย้อนกลับถูกเรียกหลังจาก onSaveInstanceState แต่ก่อนที่จะเริ่มกิจกรรมใหม่

  2. ใช้ onStop () ในรหัส

ฉันไม่แน่ใจว่ารากของปัญหาคืออะไร ดังนั้นฉันจึงใช้วิธีที่น่าเกลียด

@Override
public void onBackPressed() {

    try{
        super.onBackPressed();
    }catch (IllegalStateException e){
        // can output some information here
        finish();
    }
}

ฉันไม่ได้แก้ปัญหาจริงๆ แต่ปัญหานี้ไม่เกี่ยวข้องกับการยอมรับและ commitAllowStateLoss
oO_ox

4

ฉันมีปัญหาเดียวกันในแอพของฉัน ฉันได้รับการแก้ไขปัญหานี้เพียงแค่เรียกsuper.onBackPressed();ชั้นเรียนก่อนหน้าและเรียกcommitAllowingStateLoss()ชั้นเรียนปัจจุบันด้วยชิ้นส่วนนั้น


2
ขอบคุณ. การแก้ปัญหานี้แก้ปัญหา uisng commitAllowingStateLoss()แทนcommit()
Chintak Patel

หลีกเลี่ยงการใช้ commitAllowingStateLoss () medium.com/@elye.project/…
swooby

3

onSaveInstance จะถูกเรียกใช้หากผู้ใช้หมุนหน้าจอเพื่อให้สามารถโหลดทรัพยากรที่เกี่ยวข้องกับการวางแนวใหม่

อาจเป็นไปได้ว่าผู้ใช้รายนี้หมุนหน้าจอตามด้วยการกดปุ่มย้อนกลับ (เพราะอาจเป็นไปได้ว่าผู้ใช้รายนี้คลำโทรศัพท์ขณะใช้แอพของคุณ)


2
ในขณะที่การเปลี่ยนแปลงการกำหนดค่า (เช่นการเปลี่ยนแปลงการวางแนว) อาจส่งผลให้เกิดข้อยกเว้นนี้พวกเขาไม่ได้เป็นสาเหตุที่แท้จริง
Alex Lockwood


2

ปัญหาเดียวกันจากฉันและหลังจากการวิเคราะห์บทความบล็อกและสแต็คโอเวอร์โฟลว์ทุกวันฉันพบวิธีแก้ปัญหาง่ายๆ อย่าใช้ saveInstanceState เลยนี่เป็นเงื่อนไขที่มีรหัสหนึ่งบรรทัด ในรหัสส่วน:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(null);
    .....

2

สิ่งนี้จะเกิดขึ้นทุกครั้งที่คุณพยายามโหลดชิ้นส่วน แต่กิจกรรมได้เปลี่ยนสถานะเป็น onPause () สิ่งนี้เกิดขึ้นเช่นเมื่อคุณพยายามดึงข้อมูลและโหลดไปยังกิจกรรม แต่เมื่อผู้ใช้คลิกปุ่มและมี ย้ายไปที่กิจกรรมถัดไป

คุณสามารถแก้ปัญหานี้ได้สองวิธี

คุณสามารถใช้ transaction.commitAllowingStateLoss () แทน transaction.commit () เพื่อโหลดแฟรกเมนต์ แต่คุณอาจสิ้นสุดการสูญเสียการดำเนินการที่ทำไว้

หรือ

ตรวจสอบให้แน่ใจว่ากิจกรรมกำลังดำเนินการต่อและจะไม่หยุดชั่วคราวเมื่อโหลดชิ้นส่วน สร้างบูลีนและตรวจสอบว่ากิจกรรมไม่ได้อยู่ในสถานะ onPause ()

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

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

จากนั้นในขณะที่โหลดการตรวจสอบชิ้นส่วนถ้ามีกิจกรรมและโหลดเฉพาะเมื่อกิจกรรมอยู่เบื้องหน้า

if(mIsResumed){
 //load the fragment
}

1

ขอบคุณ @gunar แต่ฉันคิดว่ามีวิธีที่ดีกว่า

ตามเอกสาร:

 * If you are committing a single transaction that does not modify the
 * fragment back stack, strongly consider using
 * {@link FragmentTransaction#commitNow()} instead. This can help avoid
 * unwanted side effects when other code in your app has pending committed
 * transactions that expect different timing.
 *
 * @return Returns true if there were any pending transactions to be
 * executed.
 */
public abstract boolean executePendingTransactions();

ใช้commitNowเพื่อแทนที่:

fragmentTransaction.commit();
FragmentManager.executePendingTransactions()

0

หลังจากลองใช้วิธีแก้ปัญหาทั้งหมดข้างต้นโดยไม่ประสบความสำเร็จ (เพราะโดยทั่วไปฉันไม่มีธุรกรรม)

ในกรณีของฉันฉันใช้ AlertDialogs และ ProgressDialog เป็นแฟรกเมนต์ที่บางครั้งการหมุนเมื่อถาม FragmentManager ข้อผิดพลาดจะเพิ่มขึ้น

ฉันพบวิธีแก้ปัญหาในการผสมโพสต์ที่คล้ายกัน:

เป็นโซลูชัน 3 ขั้นตอนที่ทำใน FragmentActivity ของคุณ (ในกรณีนี้เรียกว่า GenericActivity):

private static WeakReference<GenericActivity> activity = null; //To avoid bug for fragments: Step 1 of 3

@Override
protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    //To avoid bug for fragments: Step 2 of 3
    activity = new WeakReference<GenericActivity>(this);
}

@Override
public FragmentManager getSupportFragmentManager(){
    //To avoid bug for fragments: Step 3 of 3
    if (this == activity.get()) {
        return super.getSupportFragmentManager();
    }
    return activity.get().getSupportFragmentManager();
}

0

เมื่อฉันใช้ startactivity ในส่วนเดียวฉันจะได้รับข้อยกเว้นนี้;

เมื่อฉันเปลี่ยนเป็น startactivityforresult ข้อยกเว้นจะหายไป :)

ดังนั้นวิธีง่ายๆในการแก้ไขคือใช้ startActivityForResult api :)


0

ฉันได้รับข้อยกเว้นนี้เมื่อฉันกดปุ่มย้อนกลับเพื่อยกเลิกตัวเลือกเจตนาในกิจกรรมชิ้นส่วนแผนที่ของฉัน ฉันแก้ไขปัญหานี้โดยแทนที่รหัสของ onResume () (ที่ฉันกำลังเริ่มต้นส่วนย่อยและการทำธุรกรรม) เป็น onStart () และแอปทำงานได้ดีในขณะนี้ หวังว่ามันจะช่วย


0

สิ่งนี้ได้รับการแก้ไขใน Android 4.2 และในแหล่งที่มาของไลบรารีการสนับสนุน [*]

สำหรับรายละเอียดของสาเหตุ (และวิธีแก้ไข) โปรดดูรายงานข้อผิดพลาดของ Google: http://code.google.com/p/android/issues/detail?id=19917

หากคุณใช้ไลบรารีการสนับสนุนคุณไม่ควรกังวลเกี่ยวกับข้อบกพร่องนี้ (นาน) [*] อย่างไรก็ตามหากคุณใช้ API โดยตรง (เช่นไม่ได้ใช้ FragmentManager ของห้องสมุดสนับสนุน) และกำหนดเป้าหมาย API ด้านล่าง Android 4.2 คุณจะต้องลองใช้วิธีแก้ไขปัญหาอย่างใดอย่างหนึ่ง

[*] ในขณะเขียน Android SDK Manager ยังคงเผยแพร่เวอร์ชั่นเก่าที่แสดงข้อผิดพลาดนี้

แก้ไขฉันจะเพิ่มความกระจ่างบางอย่างที่นี่เพราะเห็นได้ชัดว่าฉันสับสนใครก็ตามที่ลงคะแนนในคำตอบนี้

นอกจากนี้ที่แตกต่างกัน ( แต่ที่เกี่ยวข้อง) หลายสถานการณ์ที่อาจทำให้เกิดข้อยกเว้นนี้จะถูกโยน คำตอบของฉันข้างต้นหมายถึงตัวอย่างเฉพาะที่กล่าวถึงในคำถามเช่นข้อบกพร่องใน Android ซึ่งได้รับการแก้ไขในภายหลัง หากคุณได้รับข้อยกเว้นนี้ด้วยเหตุผลอื่นนั่นเป็นเพราะคุณกำลังเพิ่ม / ลบแฟรกเมนต์เมื่อคุณไม่ควร (หลังจากบันทึกสถานะของแฟรกเมนต์แล้ว) หากคุณอยู่ในสถานการณ์เช่นนั้นบางที " ชิ้นส่วนซ้อน - IllegalStateException" ไม่สามารถดำเนินการนี้หลังจาก onSaveInstanceState " " สามารถใช้งานได้กับคุณ



0

กรณีการใช้งานของฉัน: ฉันใช้ผู้ฟังเป็นส่วน ๆ เพื่อแจ้งกิจกรรมที่เกิดขึ้น ฉันทำแฟรกเมนต์ใหม่บนวิธีการโทรกลับ มันใช้งานได้ดีอย่างสมบูรณ์ในครั้งแรก แต่เมื่อเปลี่ยนการวางแนวกิจกรรมจะถูกสร้างขึ้นใหม่ด้วยสถานะอินสแตนซ์ที่บันทึกไว้ ในกรณีที่ไม่ได้สร้างชิ้นส่วนอีกครั้งก็หมายความว่าส่วนมีฟังซึ่งเป็นกิจกรรมที่ถูกทำลายเก่า วิธีการโทรกลับจะได้รับการเรียกใช้ในการดำเนินการ มันไปที่กิจกรรมที่ถูกทำลายซึ่งทำให้เกิดปัญหา การแก้ปัญหาคือการรีเซ็ตผู้ฟังในส่วนที่มีกิจกรรมสดในปัจจุบัน วิธีนี้แก้ปัญหาได้


0

สิ่งที่ฉันพบคือว่าถ้าแอพอื่นเป็นประเภทของกล่องโต้ตอบและอนุญาตให้ส่งการสัมผัสไปยังแอปพื้นหลังแอปพื้นหลังเกือบทุกแอปจะมีปัญหากับข้อผิดพลาดนี้ ฉันคิดว่าเราต้องตรวจสอบทุกครั้งที่มีการทำธุรกรรมหากมีการบันทึกหรือเรียกคืนอินสแตนซ์


0

ในกรณีของฉันด้วยข้อผิดพลาดเดียวกันฉันใส่ "onBackPressed ()" ใน runnable (คุณสามารถใช้มุมมองของคุณ):

myView.post(new Runnable() {
                    @Override
                    public void run() {
                        onBackPressed()
                    }
                });

ฉันไม่เข้าใจว่าทำไม แต่ใช้งานได้!


การโพสต์บนมุมมองจะเรียกใช้ Runnable หลังจากวางมุมมองและวาดลงบนหน้าจออย่างถูกต้องแล้ว สิ่งนี้มักจะหมายถึงกิจกรรมของตัวเองได้กลับมาทำงานต่ออย่างสมบูรณ์ดังนั้นจึงไม่มีปัญหา
Mercato

0

คุณอาจจะเรียก FragmentManager.popBackStackImmediate (); เมื่อกิจกรรมถูกหยุดชั่วคราว กิจกรรมยังไม่เสร็จสิ้น แต่ถูกหยุดชั่วคราวและไม่ได้ทำงานเบื้องหน้า คุณต้องตรวจสอบว่ากิจกรรมหยุดชั่วคราวหรือไม่ก่อน popBackStackImmediate ()


0

ฉันสังเกตเห็นสิ่งที่น่าสนใจมาก ฉันมีตัวเลือกในแอปเพื่อเปิดแกลเลอรีของโทรศัพท์และอุปกรณ์ถามว่าแอปใดที่จะใช้ฉันคลิกที่พื้นที่สีเทาห่างจากกล่องโต้ตอบและเห็นปัญหานี้ ฉันสังเกตเห็นว่ากิจกรรมของฉันไปจาก onPause, onSaveInstanceState กลับสู่ onResume มันจะไม่เกิดขึ้นกับการเยี่ยมชม onCreateView ฉันกำลังทำธุรกรรมที่ onResume ดังนั้นสิ่งที่ฉันทำลงไปก็คือการตั้งค่าสถานะที่ถูกทอดทิ้งบนหยุดชั่วคราว หากการตั้งค่าสถานะเป็นความจริงบนรีจีสแล้วทำ onCommit มิฉะนั้น commitAllowingStateLoss ฉันสามารถไปและเสียเวลามาก แต่ฉันต้องการตรวจสอบวงจรชีวิต ฉันมีอุปกรณ์ที่เป็น sdkversion 23 และฉันไม่ได้รับปัญหานี้ แต่ฉันมีอีกอันหนึ่งที่ 21 และฉันเห็นมัน


-1

คุณสามารถใช้ FragmentActivity.onStart ก่อน popBackStackImmediate

แบบนี้:

public void backStackFragment() {
    this.start();
    getFragmentManager().popBackStackImmediate();
}

public void start(){
    FragmentActivity a = getActivity();
    if(a instanceof DepositPlanPadActivity){
      ((DepositPlanPadActivity)a).onStart();
    }
    if(a instanceof SmallChangePlanPad){
            ((SmallChangePlanPad)a).onStart();
        }
        if(a instanceof UserCenterActivity){
            ((UserCenterActivity)a).onStart();
        }
    }

http://jorryliu.blogspot.com/2014/09/illegalstateexception-can-not-perform.html

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.