ในการแสดงกล่องโต้ตอบฉันได้รับ“ ไม่สามารถดำเนินการนี้ได้หลังจาก onSaveInstanceState”


121

ผู้ใช้บางรายกำลังรายงานหากใช้การดำเนินการด่วนในแถบการแจ้งเตือนพวกเขากำลังถูกบังคับให้ปิด

ฉันแสดงการดำเนินการอย่างรวดเร็วในการแจ้งเตือนว่าใครเรียกคลาส"TestDialog" ในคลาส TestDialog หลังจากกดปุ่ม "snooze" ฉันจะแสดง SnoozeDialog

private View.OnClickListener btnSnoozeOnClick() {
    return new View.OnClickListener() {

        public void onClick(View v) {
            showSnoozeDialog();
        }
    };
}

private void showSnoozeDialog() {
    FragmentManager fm = getSupportFragmentManager();
    SnoozeDialog snoozeDialog = new SnoozeDialog();
    snoozeDialog.show(fm, "snooze_dialog");
}

ข้อผิดพลาดคือ *IllegalStateException: Can not perform this action after onSaveInstanceState*.

บรรทัดรหัสที่ IllegarStateException เริ่มทำงานคือ:

snoozeDialog.show(fm, "snooze_dialog");

คลาสกำลังขยาย "FragmentActivity" และคลาส "SnoozeDialog" กำลังขยาย "DialogFragment"

นี่คือการติดตามสแต็กทั้งหมดของข้อผิดพลาด:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1327)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1338)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
at android.support.v4.app.DialogFragment.show(DialogFragment.java:127)
at com.test.testing.TestDialog.f(TestDialog.java:538)
at com.test.testing.TestDialog.e(TestDialog.java:524)
at com.test.testing.TestDialog.d(TestDialog.java:519)
at com.test.testing.g.onClick(TestDialog.java:648)
at android.view.View.performClick(View.java:3620)
at android.view.View$PerformClick.run(View.java:14292)
at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4507)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:790)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:557)
at dalvik.system.NativeStart.main(Native Method)

ฉันไม่สามารถสร้างข้อผิดพลาดนี้ซ้ำได้ แต่ฉันได้รับรายงานข้อผิดพลาดจำนวนมาก

ใครช่วยได้บ้างว่าฉันจะแก้ไขข้อผิดพลาดนี้ได้อย่างไร


2
คุณพบวิธีแก้ปัญหาหรือไม่? ฉันมีปัญหาเดียวกับคุณ ฉันถามที่นี่: stackoverflow.com/questions/15730878/… โปรดตรวจสอบคำถามของฉันและดูวิธีแก้ปัญหาที่เป็นไปได้ซึ่งใช้ไม่ได้กับกรณีของฉัน บางทีมันอาจจะทำงานสำหรับคุณ
rootpanthera

ยังไม่มีวิธีแก้ไข :-( และข้อเสนอแนะของคุณถูกเพิ่มในชั้นเรียนของฉันแล้ว
chrisonline

ตรวจสอบคำตอบที่ยอมรับได้จากที่นี่ สิ่งนี้ช่วยแก้ปัญหาของฉันได้: stackoverflow.com/questions/14177781/…
bogdan

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

stackoverflow.com/questions/7575921/…คุณได้ลองแล้วฉันแน่ใจ
Orion

คำตอบ:


66

นี้เป็นเรื่องธรรมดาปัญหา เราแก้ไขปัญหานี้โดยการแทนที่ show () และจัดการข้อยกเว้นในคลาสเสริมของ DialogFragment

public class CustomDialogFragment extends DialogFragment {

    @Override
    public void show(FragmentManager manager, String tag) {
        try {
            FragmentTransaction ft = manager.beginTransaction();
            ft.add(this, tag);
            ft.commit();
        } catch (IllegalStateException e) {
            Log.d("ABSDIALOGFRAG", "Exception", e);
        }
    }
}

โปรดทราบว่าการใช้วิธีนี้จะไม่เปลี่ยนแปลงฟิลด์ภายในของ DialogFragment.class:

boolean mDismissed;
boolean mShownByMe;

ซึ่งอาจนำไปสู่ผลลัพธ์ที่ไม่คาดคิดในบางกรณี ดีกว่าใช้ commitsAllowingStateLoss () แทนการกระทำ ()


3
แต่ทำไมปัญหานี้ถึงเกิดขึ้น? สามารถเพิกเฉยต่อข้อผิดพลาดได้หรือไม่? เกิดอะไรขึ้นเมื่อคุณทำ? ท้ายที่สุดเมื่อคลิกหมายความว่ากิจกรรมนั้นใช้งานได้จริง ... อย่างไรก็ตามฉันได้รายงานเกี่ยวกับเรื่องนี้ที่นี่เพราะฉันคิดว่านี่เป็นข้อบกพร่อง: code.google.com/p/android/issues/detail?id= 207269
นักพัฒนา Android

1
ช่วยติดดาวและ / หรือแสดงความคิดเห็นที่นั่นได้ไหม
นักพัฒนา Android

2
จะดีกว่าที่จะเรียก super.show (manager, tag) ใน try-catch clause ธงที่เป็นของ DialogFragment สามารถรักษาความปลอดภัยได้ด้วยวิธีนี้
Shayan_Aryan

20
ณ จุดนี้คุณสามารถเรียกได้ว่า commandAllowingStateLoss () แทนการกระทำ () ข้อยกเว้นจะไม่ถูกยกขึ้น
ARLabs

1
@ARLabs ฉันคิดว่าในกรณีนี้จะดีกว่าสำหรับผู้ตอบเช่นกันเนื่องจากถ้าคุณจับข้อยกเว้นดังที่แสดงไว้ที่นี่กล่องโต้ตอบจะไม่ปรากฏขึ้นอย่างแน่นอน ดีกว่าที่จะแสดงกล่องโต้ตอบหากคุณทำได้และอาจหายไปหากต้องกู้คืนสถานะ นอกจากนี้ให้หน่วยความจำแอปของคุณใช้งานอยู่เบื้องหลังในระดับต่ำดังนั้นจึงไม่น่าจะถูกทำลาย
androidguy

27

นั่นหมายความว่าคุณcommit()( show()ในกรณีของ DialogFragment) ส่วนหลังonSaveInstanceState()ส่วนหลัง

Android onSaveInstanceState()จะบันทึกสถานะส่วนของคุณได้ที่ ดังนั้นหากคุณcommit()แยกส่วนหลังจากนั้นonSaveInstanceState()สถานะส่วนย่อยจะหายไป

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

วิธีแก้ปัญหาง่ายๆคือตรวจสอบว่าบันทึกสถานะแล้วหรือไม่

boolean mIsStateAlreadySaved = false;
boolean mPendingShowDialog = false;

@Override
public void onResumeFragments(){
    super.onResumeFragments();
    mIsStateAlreadySaved = false;
    if(mPendingShowDialog){
        mPendingShowDialog = false;
        showSnoozeDialog();
    }
}

@Override
public void onPause() {
    super.onPause();
    mIsStateAlreadySaved = true;
}

private void showSnoozeDialog() {
    if(mIsStateAlreadySaved){
        mPendingShowDialog = true;
    }else{
        FragmentManager fm = getSupportFragmentManager();
        SnoozeDialog snoozeDialog = new SnoozeDialog();
        snoozeDialog.show(fm, "snooze_dialog");
    }
}

หมายเหตุ: onResumeFragments () จะเรียกเมื่อแฟรกเมนต์กลับมาทำงานต่อ


1
จะเกิดอะไรขึ้นถ้าฉันต้องการแสดง DialogFragment ภายในส่วนอื่น
นักพัฒนา Android

โซลูชันของเราคือสร้างคลาสฐานกิจกรรมและแฟรกเมนต์และมอบหมาย onResumeFragments ไปยังแฟรกเมนต์ (เราสร้าง onResumeFragments ในแฟรกเมนต์คลาส) ไม่ใช่ทางออกที่ดี แต่ใช้ได้ผล หากคุณมีทางออกที่ดีกว่านี้โปรดแจ้งให้เราทราบ :)
Pongpat

ฉันคิดว่าการแสดงกล่องโต้ตอบใน "onStart" น่าจะทำงานได้ดีเนื่องจากมีการแสดงส่วนย่อย แต่ฉันยังเห็นรายงานข้อขัดข้องบางอย่างเกี่ยวกับเรื่องนี้ ฉันได้รับคำสั่งให้ลองใส่ไว้ใน "onResume" แทน เกี่ยวกับทางเลือกอื่นฉันเห็นสิ่งนี้: twigstechtips.blogspot.co.il/2014/01/…แต่มันค่อนข้างแปลก
นักพัฒนา Android

ฉันคิดว่าเหตุผลที่twigstechtips.blogspot.co.il/2014/01/…ทำงานได้เพราะมันเริ่มต้นเธรดใหม่และด้วยเหตุนี้รหัสอายุการใช้งานทั้งหมดเช่น onStart, onResume ฯลฯ ที่เรียกก่อนโค้ด runOnUiThread เคยรัน นั่นหมายความว่าสถานะได้กู้คืนแล้วก่อนที่จะเรียกใช้ runOnUiThread
พงษ์พัฒน์

2
ฉันใช้การโทรเพียงครั้งเดียวเพื่อโพสต์ (รันได้) เกี่ยวกับ getFragmentManager นั้นขึ้นอยู่กับ หากคุณต้องการแบ่งปันกล่องโต้ตอบนั้นกับกิจกรรมอื่นคุณควรใช้ getFragmentManager อย่างไรก็ตามหากกล่องโต้ตอบนั้นมีอยู่เฉพาะกับส่วน getChildFragmentManager ดูเหมือนจะเป็นทางเลือกที่ดีกว่า
พงษ์พัฒน์

16
private void showSnoozeDialog() {
    FragmentManager fm = getSupportFragmentManager();
    SnoozeDialog snoozeDialog = new SnoozeDialog();
    // snoozeDialog.show(fm, "snooze_dialog");
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.add(snoozeDialog, "snooze_dialog");
    ft.commitAllowingStateLoss();
}

อ้างอิง: ลิงค์


11

หลังจากไม่กี่วันฉันต้องการแบ่งปันวิธีแก้ปัญหาของฉันว่าฉันแก้ไขอย่างไรเพื่อแสดง DialogFragment คุณควรแทนที่show()วิธีการของมันและเรียกcommitAllowingStateLoss()ใช้Transactionวัตถุ นี่คือตัวอย่างใน Kotlin:

override fun show(manager: FragmentManager?, tag: String?) {
        try {
            val ft = manager?.beginTransaction()
            ft?.add(this, tag)
            ft?.commitAllowingStateLoss()
        } catch (ignored: IllegalStateException) {

        }

    }

1
เพื่อให้นักพัฒนาไม่ได้มีการสืบทอดมาจากDialogFragmentคุณอาจมีการเปลี่ยนแปลงนี้จะมีฟังก์ชั่นการขยาย Kotlin fun DialogFragment.showAllowingStateLoss(fragmentManager: FragmentManager, tag: String)ที่มีลายเซ็นต่อไปนี้: นอกจากนี้การลองจับก็ไม่จำเป็นเนื่องจากคุณกำลังเรียกใช้commitAllowingStateLoss()เมธอดไม่ใช่commit()เมธอด
Adil Hussain

10

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

boolean running = false;

@Override
public void onStart() {
    running = true;
    super.onStart();
}

@Override
public void onStop() {
    running = false;
    super.onStop();
}

และเปิดกล่องโต้ตอบของคุณ (ส่วนย่อย) เฉพาะเมื่อเรากำลังทำงาน:

if (running) {
    yourDialog.show(...);
}

แก้ไขโซลูชันที่ดีกว่าที่เป็นไปได้:

ในกรณีที่เรียก onSaveInstanceState ในวงจรชีวิตไม่สามารถคาดเดาได้ฉันคิดว่าทางออกที่ดีกว่าคือตรวจสอบ isSavedInstanceStateDone () ดังนี้:

/**
 * True if SavedInstanceState was done, and activity was not restarted or resumed yet.
 */
private boolean savedInstanceStateDone;

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

    savedInstanceStateDone = false;
}

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

    savedInstanceStateDone = false;
}

protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    savedInstanceStateDone = true;
}


/**
 * Returns true if SavedInstanceState was done, and activity was not restarted or resumed yet.
 */
public boolean isSavedInstanceStateDone() {
    return savedInstanceStateDone;
}

ดูเหมือนจะไม่ได้ผลเนื่องจากฉันได้รับข้อยกเว้นนี้ในการเรียกเมธอด "onStart" (พยายามแสดง DialogFragment ที่นั่น)
นักพัฒนา Android

คุณช่วยวันของฉัน ขอบคุณแฟรงค์
Cüneyt

7

ฉันพบปัญหานี้มาหลายปีแล้ว
Internets ถูกทิ้งเกลื่อนไปด้วยคะแนน (หลายแสน?) ของการอภิปรายเกี่ยวกับเรื่องนี้และความสับสนและการบิดเบือนข้อมูลในนั้นดูเหมือนจะมากมาย
เพื่อทำให้สถานการณ์แย่ลงและด้วยจิตวิญญาณของการ์ตูน xkcd "14 มาตรฐาน" ฉันกำลังตอบรับคำตอบของฉันไปที่แหวน
xkcd 14 มาตรฐาน

cancelPendingInputEvents(), commitAllowingStateLoss(),catch (IllegalStateException e)และการแก้ปัญหาที่คล้ายกันทั้งหมดดูเหมือนจะเลวร้าย

หวังว่าต่อไปนี้จะแสดงวิธีการสร้างซ้ำและแก้ไขปัญหาได้อย่างง่ายดาย:

private static final Handler sHandler = new Handler();
private boolean mIsAfterOnSaveInstanceState = true;

@Override
protected void onSaveInstanceState(Bundle outState)
{
    super.onSaveInstanceState(outState);
    mIsAfterOnSaveInstanceState = true; // <- To repro, comment out this line
}

@Override
protected void onPostResume()
{
    super.onPostResume();
    mIsAfterOnSaveInstanceState = false;
}

@Override
protected void onResume()
{
    super.onResume();
    sHandler.removeCallbacks(test);
}

@Override
protected void onPause()
{
    super.onPause();
    sHandler.postDelayed(test, 5000);
}

Runnable test = new Runnable()
{
    @Override
    public void run()
    {
        if (mIsAfterOnSaveInstanceState)
        {
            // TODO: Consider saving state so that during or after onPostResume a dialog can be shown with the latest text
            return;
        }

        FragmentManager fm = getSupportFragmentManager();
        DialogFragment dialogFragment = (DialogFragment) fm.findFragmentByTag("foo");
        if (dialogFragment != null)
        {
            dialogFragment.dismiss();
        }

        dialogFragment = GenericPromptSingleButtonDialogFragment.newInstance("title", "message", "button");
        dialogFragment.show(fm, "foo");

        sHandler.postDelayed(test, 5000);
    }
};

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

1
ใช่มันเป็นปัญหาของ SO ฉันเขียนปัญหานี้ทุกครั้งในข้อเสนอแนะ แต่พวกเขาไม่ต้องการแก้ไข
CoolMind

2
ฉันคิดว่าการโหวตลดลงอาจเป็นผลมาจาก XKCD ที่ฝังไว้คำตอบไม่ใช่สถานที่สำหรับความคิดเห็นทางสังคม (ไม่ว่าจะตลกและ / หรือจริงแค่ไหน)
RestingRobot

6

โปรดลองใช้ FragmentTransaction แทน FragmentManager ฉันคิดว่าโค้ดด้านล่างจะช่วยแก้ปัญหาของคุณได้ ถ้าไม่โปรดแจ้งให้เราทราบ

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
SnoozeDialog snoozeDialog = new SnoozeDialog();
snoozeDialog.show(ft, "snooze_dialog");

แก้ไข:

ธุรกรรมส่วนย่อย

โปรดตรวจสอบลิงค์นี้ ฉันคิดว่ามันจะช่วยแก้ปัญหาให้คุณได้


4
คำอธิบายใด ๆ ว่าเหตุใดการใช้ FragmentTransaction จึงแก้ไขปัญหาได้ดี
Hemanshu

3
Dialog # show (FragmentManager, tag) ทำสิ่งเดียวกัน นี่ไม่ใช่วิธีแก้ปัญหา
William

3
คำตอบนี้ไม่ใช่ทางออก DialogFragment # show (ft) และ show (fm) ทำสิ่งเดียวกัน
danijoo

@danijoo คุณคิดถูกที่ทั้งสองอย่างทำงานเหมือนกัน แต่ในโทรศัพท์บางรุ่นมีปัญหาบางอย่างเช่นนี้หากคุณใช้แฟรกเมนต์แมนเอเจอร์แทนแฟรกเมนต์ทรานแซคชัน ดังนั้นในกรณีของฉันสิ่งนี้ช่วยแก้ปัญหาของฉันได้
RIJO RV

6

การใช้ขอบเขตอายุการใช้งานใหม่ของ Activity-KTX นั้นง่ายเหมือนตัวอย่างโค้ดต่อไปนี้:

lifecycleScope.launchWhenResumed {
   showErrorDialog(...)
}

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


3

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

at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)

และรหัสของคุณทำให้showIllegalStateException ถูกโยนทิ้ง

วิธีแก้ปัญหาที่ง่ายที่สุดคือล้างคิวเหตุการณ์ใน onSaveInstanceState

protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // ..... do some work
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            findViewById(android.R.id.content).cancelPendingInputEvents();
        }
}

คุณได้ยืนยันจริงหรือไม่ว่าวิธีนี้ช่วยแก้ปัญหาได้
mhsmith

Google ได้เพิ่มสิ่งนี้ลงในไลบรารี androidx รุ่นถัดไปซึ่งปัจจุบันเป็นรุ่นเบต้า ( activityและfragment)
mhsmith

1
@mhsmith ฉันจำได้ว่าวิธีนี้แก้ปัญหาในรหัสของฉันด้วย IllegalStateException
ซิม

2

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

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

    if (dialogFragment != null) {
        dialogFragment.dismissAllowingStateLoss();
    }
}

อย่าลืมกำหนดค่าในส่วนย่อยและการแสดงการโทร () เมื่อคลิกปุ่มหรือที่ใดก็ตาม


1

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


1
  1. เพิ่มคลาสนี้ในโปรเจ็กต์ของคุณ: (ต้องอยู่ในแพ็คเกจandroid.support.v4.app )
แพ็คเกจ android.support.v4.app;


/ **
 * สร้างโดย Gil เมื่อ 16/8/2017
 * /

คลาสสาธารณะ StatelessDialogFragment ขยาย DialogFragment {
    / **
     * แสดงกล่องโต้ตอบเพิ่มส่วนโดยใช้ธุรกรรมที่มีอยู่แล้วกระทำ
     * การทำธุรกรรมในขณะที่ยอมให้รัฐสูญเสีย
* * * * * ฉันขอแนะนำให้คุณใช้ {@link #show (FragmentTransaction, String)} เกือบตลอดเวลา แต่ * นี่คือกล่องโต้ตอบที่คุณไม่เคยสนใจ (แก้ไขข้อบกพร่อง / ติดตาม / โฆษณา ฯลฯ ) * * * * * @param รายการ * ธุรกรรมที่มีอยู่ซึ่งจะเพิ่มส่วน * @param แท็ก * แท็กสำหรับส่วนนี้ตาม * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add} * @return ส่งคืนตัวระบุของธุรกรรมที่ตกลงกันตาม * {@link FragmentTransaction # คอมมิต () FragmentTransaction.commit ()} * @see StatelessDialogFragment # showAllowingStateLoss (FragmentManager, สตริง) * / แสดง int สาธารณะAllowingStateLoss (ธุรกรรม FragmentTransaction แท็กสตริง) { mDismissed = เท็จ; mShownByMe = จริง; transaction.add (นี่คือแท็ก); mViewDestroyed = เท็จ; mBackStackId = transaction.commitAllowingStateLoss (); กลับ mBackStackId; } / ** * แสดงกล่องโต้ตอบเพิ่มส่วนให้กับ FragmentManager ที่กำหนด นี่คือความสะดวก * สำหรับการสร้างธุรกรรมอย่างชัดเจนให้เพิ่มส่วนที่มีแท็กที่กำหนดและ * กระทำโดยไม่สนใจสถานะ สิ่งนี้ไม่ได้เพิ่มธุรกรรมลงในไฟล์ * กองหลัง เมื่อส่วนย่อยถูกยกเลิกธุรกรรมใหม่จะถูกดำเนินการเพื่อลบออก * จากกิจกรรม
* * * * * ฉันอยากแนะนำให้คุณใช้ {@link #show (FragmentManager, String)} เกือบตลอดเวลา แต่นี่คือ * สำหรับกล่องโต้ตอบที่คุณไม่สนใจ (แก้ไขข้อบกพร่อง / ติดตาม / โฆษณา ฯลฯ ) * * * * * * * * * @param manager * FragmentManager ส่วนนี้จะถูกเพิ่มเข้าไป * @param แท็ก * แท็กสำหรับส่วนนี้ตาม * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add} * @see StatelessDialogFragment # showAllowingStateLoss (FragmentTransaction, สตริง) * / โมฆะสาธารณะ showAllowingStateLoss (ตัวจัดการ FragmentManager แท็กสตริง) { mDismissed = เท็จ; mShownByMe = จริง; FragmentTransaction ฟุต = manager.beginTransaction (); ft.add (นี่คือแท็ก); ft.commitAllowingStateLoss (); } }
  1. ขยายStatelessDialogFragmentแทน DialogFragment
  2. ใช้เมธอดshowAllowingStateLossแทน show

  3. สนุก ;)


ช่องบูลีนทั้งหมดนี้มีไว้เพื่ออะไรเหตุใดจึงไม่ประกาศเป็นสมาชิกชั้นเรียน
ไม่ระบุ

1
ฟิลด์บูลีนเป็นสมาชิกที่ได้รับการป้องกันของ DialogFragment ชื่อของพวกเขาบ่งบอกอย่างชัดเจนว่ามีไว้เพื่ออะไรและเราจำเป็นต้องอัปเดตเพื่อที่จะไม่รบกวนตรรกะของ DialogFragment โปรดทราบว่าในคลาส DialogFragment ดั้งเดิมฟังก์ชันนี้มีอยู่ แต่ไม่มีการเข้าถึงแบบสาธารณะ
Gil SH

Ough สมาชิกเหล่านี้ไม่ได้รับการปกป้องพวกเขาอยู่ภายในฉันได้รับข้อผิดพลาดในการคอมไพล์เมื่อฉันใส่StatelessDialogFragmentในแพ็คเกจของฉันขอบคุณเพื่อนฉันจะทดสอบในการผลิตเร็ว ๆ นี้
undefined


1

ฉันพบวิธีแก้ปัญหาที่ยอดเยี่ยมสำหรับปัญหานี้โดยใช้การสะท้อน ปัญหาของวิธีแก้ปัญหาข้างต้นคือฟิลด์mDismissedและmShownByMeไม่เปลี่ยนสถานะ

เพียงแค่แทนที่วิธีการ "แสดง" ในส่วนของกล่องโต้ตอบแผ่นงานด้านล่างที่กำหนดเองเช่นตัวอย่างด้านล่าง (Kotlin)

override fun show(manager: FragmentManager, tag: String?) {
        val mDismissedField = DialogFragment::class.java.getDeclaredField("mDismissed")
        mDismissedField.isAccessible = true
        mDismissedField.setBoolean(this, false)

        val mShownByMeField = DialogFragment::class.java.getDeclaredField("mShownByMe")
        mShownByMeField.isAccessible = true
        mShownByMeField.setBoolean(this, true)

        manager.beginTransaction()
                .add(this, tag)
                .commitAllowingStateLoss()
    }

4
"ฉันพบวิธีแก้ปัญหาที่สวยงามโดยใช้การไตร่ตรอง" สวยหรูยังไง?
Mark Buikema

สง่า, มีสไตล์, เก๋ไก๋, ฉลาด, ดี, สง่างาม
РомаРогдан

1
มันเป็นทางออกเดียวที่เหมาะกับฉัน ฉันคิดว่ามันสง่างาม
MBH

0

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

public abstract class XAppCompatActivity extends AppCompatActivity {

    private String TAG = this.getClass().getSimpleName();

    /** The retained fragment for this activity */
    private ActivityRetainFragment retainFragment;

    /** If true the instance state has been saved and we are going to die... */
    private boolean instanceStateSaved;

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        // get hold of retain Fragment we'll be using
        retainFragment = ActivityRetainFragment.get(this, "Fragment-" + this.getClass().getName());
    }

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

        // reset instance saved state
        instanceStateSaved = false;

        // execute all the posted tasks
        for (ActivityTask task : retainFragment.tasks) task.exec(this);
        retainFragment.tasks.clear();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        instanceStateSaved = true;
    }

    /**
     * Checks if the activity state has been already saved.
     * After that event we are no longer allowed to commit fragment transactions.
     * @return true if the instance state has been saved
     */
    public boolean isInstanceStateSaved() {
        return instanceStateSaved;
    }

    /**
     * Posts a task to be executed when the activity state has not yet been saved
     * @param task The task to be executed
     * @return true if the task executed immediately, false if it has been queued
     */
    public final boolean post(ActivityTask task)
    {
        // execute it immediately if we have not been saved
        if (!isInstanceStateSaved()) {
            task.exec(this);
            return true;
        }

        // save it for better times
        retainFragment.tasks.add(task);
        return false;
    }

    /** Fragment used to retain activity data among re-instantiations */
    public static class ActivityRetainFragment extends Fragment {

        /**
         * Returns the single instance of this fragment, creating it if necessary
         * @param activity The Activity performing the request
         * @param name The name to be given to the Fragment
         * @return The Fragment
         */
        public static ActivityRetainFragment get(XAppCompatActivity activity, String name) {

            // find the retained fragment on activity restarts
            FragmentManager fm = activity.getSupportFragmentManager();
            ActivityRetainFragment fragment = (ActivityRetainFragment) fm.findFragmentByTag(name);

            // create the fragment and data the first time
            if (fragment == null) {
                // add the fragment
                fragment = new ActivityRetainFragment();
                fm.beginTransaction().add(fragment, name).commit();
            }

            return fragment;
        }

        /** The queued tasks */
        private LinkedList<ActivityTask> tasks = new LinkedList<>();

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

            // retain this fragment
            setRetainInstance(true);
        }

    }

    /** A task which needs to be performed by the activity when it is "fully operational" */
    public interface ActivityTask {

        /**
         * Executed this task on the specified activity
         * @param activity The activity
         */
        void exec(XAppCompatActivity activity);
    }
}

จากนั้นใช้คลาสดังนี้:

/** AppCompatDialogFragment implementing additional compatibility checks */
public abstract class XAppCompatDialogFragment extends AppCompatDialogFragment {

    /**
     * Shows this dialog as soon as possible
     * @param activity The activity to which this dialog belongs to
     * @param tag The dialog fragment tag
     * @return true if the dialog has been shown immediately, false if the activity state has been saved
     *         and it is not possible to show it immediately
     */
    public boolean showRequest(XAppCompatActivity activity, final String tag) {
        return showRequest(activity, tag, null);
    }

    /**
     * Shows this dialog as soon as possible
     * @param activity The activity to which this dialog belongs to
     * @param tag The dialog fragment tag
     * @param args The dialog arguments
     * @return true if the dialog has been shown immediately, false if the activity state has been saved
     *         and it is not possible to show it immediately
     */
    public boolean showRequest(XAppCompatActivity activity, final String tag, final Bundle args)
    {
        return activity.post(new XAppCompatActivity.ActivityTask() {
            @Override
            public void exec(XAppCompatActivity activity) {
                if (args!= null) setArguments(args);
                show(activity.getSupportFragmentManager(), tag);
            }
        });
    }

    /**
     * Dismiss this dialog as soon as possible
     * @return true if the dialog has been dismissed immediately, false if the activity state has been saved
     *         and it is not possible to dismissed it immediately
     */
    public boolean dismissRequest()
    {
        return dismissRequest(null);
    }

    /**
     * Dismiss this dialog as soon as possible
     * @param runnable Actions to be performed before dialog dismissal
     * @return true if the dialog has been dismissed immediately, false if the activity state has been saved
     *         and it is not possible to dismissed it immediately
     */
    public boolean dismissRequest(final Runnable runnable)
    {
        // workaround as in rare cases the activity could be null
        XAppCompatActivity activity = (XAppCompatActivity)getActivity();
        if (activity == null) return false;

        // post the dialog dismissal
        return activity.post(new XAppCompatActivity.ActivityTask() {
            @Override
            public void exec(XAppCompatActivity activity) {
                if (runnable != null) runnable.run();
                dismiss();
            }
        });
    }
}

คุณสามารถแสดงกล่องโต้ตอบได้อย่างปลอดภัยโดยไม่ต้องกังวลเกี่ยวกับสถานะของแอป:

public class TestDialog extends XAppCompatDialogFragment {

    private final static String TEST_DIALOG = "TEST_DIALOG";

    public static void show(XAppCompatActivity activity) {
        new TestDialog().showRequest(activity, TEST_DIALOG);
    }

    public TestDialog() {}

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState)
    {
        return new AlertDialog.Builder(getActivity(), R.style.DialogFragmentTheme /* or null as you prefer */)
                .setTitle(R.string.title)
                // set all the other parameters you need, e.g. Message, Icon, etc.
                ).create();
    }
}

แล้วโทรTestDialog.show(this)จากภายในXAppCompatActivityไฟล์.

หากคุณต้องการสร้างคลาสโต้ตอบทั่วไปที่มีพารามิเตอร์มากขึ้นคุณสามารถบันทึกBundleด้วยอาร์กิวเมนต์ในshow()เมธอดและดึงข้อมูลด้วยgetArguments()ในonCreateDialog().

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


0

ข้อผิดพลาดนี้ดูเหมือนจะเกิดขึ้นเนื่องจากเหตุการณ์อินพุต (เช่นคีย์ลงหรือเหตุการณ์ onclick) ได้รับการส่งมอบหลังจากonSaveInstanceStateถูกเรียก

วิธีแก้ปัญหาคือการลบล้างonSaveInstanceStateกิจกรรมของคุณและยกเลิกกิจกรรมที่รอดำเนินการ

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