Khi hiển thị hộp thoại, tôi nhận được "Không thể thực hiện hành động này sau onSaveInstanceState"


121

Một số người dùng đang báo cáo, nếu họ sử dụng thao tác nhanh trên thanh thông báo, họ đang bị đóng.

Tôi hiển thị một hành động nhanh trong thông báo người gọi lớp "TestDialog" . Trong lớp TestDialog sau khi nhấn nút "báo lại", tôi sẽ hiển thị 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");
}

Lỗi là *IllegalStateException: Can not perform this action after onSaveInstanceState*.

Dòng mã nơi IllegarStateException được kích hoạt là:

snoozeDialog.show(fm, "snooze_dialog");

Lớp đang mở rộng "FragmentActivity" và lớp "SnoozeDialog" đang mở rộng "DialogFragment".

Đây là toàn bộ dấu vết ngăn xếp của lỗi:

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)

Tôi không thể tạo lại lỗi này, nhưng tôi nhận được nhiều báo cáo lỗi.

Ai có thể giúp tôi làm cách nào để khắc phục lỗi này?


2
Bạn đã tìm thấy một giải pháp? Tôi có cùng một vấn đề như bạn. Tôi đã hỏi ở đây: stackoverflow.com/questions/15730878/… Vui lòng kiểm tra câu hỏi của tôi và xem giải pháp khả thi không hoạt động cho trường hợp của tôi. Có lẽ nó sẽ làm việc cho bạn.
rootpanthera

Không có giải pháp chưa :-( Và gợi ý của bạn đã được thêm vào lớp học của tôi.
chrisonline

Kiểm tra câu trả lời được chấp nhận từ đây. Điều này giải quyết vấn đề của tôi: stackoverflow.com/questions/14177781/...
Bogdan

4
Hoạt động của bạn có hiển thị khi hộp thoại này được kích hoạt không? Có vẻ như điều này có thể do ứng dụng của bạn cố gắng hiển thị hộp thoại được đính kèm với Hoạt động đã bị tạm dừng / dừng.
Kai

stackoverflow.com/questions/7575921/… bạn đã thử điều này Tôi chắc chắn.
Orion

Câu trả lời:


66

Đây là vấn đề phổ biến . Chúng tôi đã giải quyết vấn đề này bằng cách ghi đè show () và xử lý ngoại lệ trong lớp mở rộng 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);
        }
    }
}

Lưu ý rằng việc áp dụng phương pháp này sẽ không làm thay đổi các trường bên trong của DialogFragment.class:

boolean mDismissed;
boolean mShownByMe;

Điều này có thể dẫn đến kết quả không mong muốn trong một số trường hợp. Sử dụng tốt hơn commitAllowingStateLoss () thay vì commit ()


3
Nhưng tại sao vấn đề này xảy ra? Bỏ qua lỗi có ổn không? Điều gì xảy ra khi bạn làm? Rốt cuộc, khi nhấp vào, điều đó có nghĩa là hoạt động đang hoạt động tốt ... Dù sao, tôi đã báo cáo về điều này ở đây vì tôi coi đây là lỗi: code.google.com/p/android/issues/detail?id= 207269
nhà phát triển Android

1
Sau đó, bạn có thể vui lòng gắn dấu sao và / hoặc bình luận ở đó không?
nhà phát triển Android

2
tốt hơn nên gọi super.show (manager, tag) bên trong mệnh đề try-catch. Cờ thuộc sở hữu của DialogFragment có thể giữ an toàn theo cách này
Shayan_Aryan

20
Tại thời điểm này, bạn có thể gọi commitAllowingStateLoss () thay vì commit (). Ngoại lệ sẽ không được nêu ra.
ARLabs

1
@ARLabs Tôi tưởng tượng trong trường hợp này sẽ tốt hơn cho người trả lời vì nếu bạn chỉ bắt được ngoại lệ như được hiển thị ở đây, hộp thoại chắc chắn sẽ không được hiển thị. Tốt hơn là hiển thị hộp thoại nếu bạn có thể và nó có thể biến mất nếu trạng thái phải được khôi phục. Đồng thời, giữ cho bộ nhớ ứng dụng của bạn sử dụng trong nền ở mức thấp để nó không có khả năng bị phá hủy.
androidguy

27

Điều đó có nghĩa là bạn commit()( show()trong trường hợp DialogFragment) phân đoạn sau onSaveInstanceState().

Android sẽ lưu trạng thái phân mảnh của bạn tại onSaveInstanceState(). Vì vậy, nếu bạn commit()phân mảnh sau khi onSaveInstanceState()trạng thái phân mảnh sẽ bị mất.

Do đó, nếu Activity bị giết và tạo lại sau đó, phân đoạn sẽ không thêm vào hoạt động, điều này gây ra trải nghiệm người dùng không tốt. Đó là lý do tại sao Android không cho phép mất trạng thái bằng mọi giá.

Giải pháp dễ dàng là kiểm tra xem trạng thái đã được lưu hay chưa.

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

Lưu ý: onResumeFragment () sẽ gọi khi các phân đoạn được tiếp tục lại.


1
Điều gì sẽ xảy ra nếu tôi muốn hiển thị DialogFragment trong một phân đoạn khác?
nhà phát triển android

Giải pháp của chúng tôi là tạo lớp cơ sở hoạt động và phân mảnh và ủy quyền các phân đoạn onResume đến phân mảnh (chúng tôi tạo các phân đoạn onResume trong lớp cơ sở phân mảnh). Nó không phải là giải pháp tốt nhưng nó hoạt động. Nếu bạn có giải pháp nào tốt hơn, vui lòng cho tôi biết :)
Pongpat

Tôi nghĩ rằng hiển thị hộp thoại trong "onStart" sẽ hoạt động tốt, vì chắc chắn phân đoạn đang được hiển thị, nhưng tôi vẫn thấy một số báo cáo sự cố về nó. Thay vào đó, tôi đã được hướng dẫn để thử đặt nó vào "onResume". Về các lựa chọn thay thế, tôi thấy điều này: twigstechtips.blogspot.co.il/2014/01/… , nhưng nó khá kỳ lạ.
nhà phát triển android

Tôi nghĩ lý do mà twigstechtips.blogspot.co.il/2014/01/… hoạt động vì nó bắt đầu luồng mới và do đó tất cả mã vòng đời tức là onStart, onResume, v.v. được gọi trước khi mã runOnUiThread từng chạy. Điều đó có nghĩa là trạng thái đã được khôi phục trước khi runOnUiThread được gọi.
Pongpat

2
Tôi sử dụng một cuộc gọi để đăng (có thể chạy được). Về getFragmentManager, nó phụ thuộc. Nếu bạn muốn chia sẻ hộp thoại đó với một hoạt động khác, bạn nên sử dụng getFragmentManager, tuy nhiên, nếu hộp thoại đó chỉ tồn tại với phân mảnh getChildFragmentManager có vẻ là lựa chọn tốt hơn.
Pongpat

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

ref: liên kết


11

Sau vài ngày tôi muốn chia sẻ giải pháp của tôi như thế nào Tôi đã khắc phục nó, để hiển thị DialogFragment bạn nên để ghi đè show()phương pháp của nó và gọi commitAllowingStateLoss()trên Transactionđối tượng. Đây là ví dụ trong Kotlin:

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

        }

    }

1
Vì vậy, các nhà phát triển không cần phải kế thừa từ DialogFragmentbạn có thể thay đổi điều này trở thành một chức năng mở rộng Kotlin với chữ ký sau đây: fun DialogFragment.showAllowingStateLoss(fragmentManager: FragmentManager, tag: String). Ngoài ra, việc thử bắt là không cần thiết vì bạn đang gọi commitAllowingStateLoss()phương thức chứ không phải commit()phương thức.
Adil Hussain

10

Nếu hộp thoại không thực sự quan trọng (có thể không hiển thị nó khi ứng dụng đóng / không còn ở chế độ xem), hãy sử dụng:

boolean running = false;

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

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

Và chỉ mở hộp thoại của bạn (phân đoạn) khi chúng tôi đang chạy:

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

CHỈNH SỬA, GIẢI PHÁP TỐT HƠN CHUYÊN NGHIỆP:

Trường hợp onSaveInstanceState được gọi trong vòng đời là không thể đoán trước, tôi nghĩ giải pháp tốt hơn là kiểm tra isSavedInstanceStateDone () như thế này:

/**
 * 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;
}

Điều này dường như không hoạt động, vì tôi nhận được ngoại lệ này trong cuộc gọi phương thức "onStart" (cố gắng hiển thị DialogFragment ở đó).
nhà phát triển android

Bạn đã cứu ngày của tôi. Cảm ơn Frank.
Cüneyt

7

Tôi đã gặp phải vấn đề này trong nhiều năm.
Các Internets rải rác với số điểm (hàng trăm? Hàng nghìn?) Của các cuộc thảo luận về điều này, và sự nhầm lẫn và thông tin sai lệch trong đó dường như rất nhiều.
Để làm cho tình hình tồi tệ hơn, và theo tinh thần của truyện tranh xkcd "14 tiêu chuẩn", tôi đang ném câu trả lời của mình vào vòng đấu.
xkcd 14 tiêu chuẩn

Các cancelPendingInputEvents(),commitAllowingStateLoss() , catch (IllegalStateException e), và các giải pháp tương tự tất cả dường như tàn bạo.

Hy vọng rằng phần sau dễ dàng chỉ ra cách tái tạo và khắc phục sự cố:

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
Tôi yêu những người bỏ phiếu không có lời giải thích. Thay vì chỉ bỏ phiếu xuống, có lẽ sẽ tốt hơn nếu họ giải thích giải pháp của tôi có sai sót như thế nào? Tôi có thể bỏ phiếu bỏ phiếu phản đối của cử tri không?
swooby

1
Vâng, đó là một vấn đề của SO, tôi viết vấn đề này lần nào trong các đề xuất, nhưng họ không muốn giải quyết.
CoolMind

2
Tôi nghĩ phiếu phản đối có thể là kết quả của XKCD được nhúng vào, câu trả lời thực sự không phải là nơi dành cho các bình luận xã hội, (bất kể hài hước và / hoặc đúng đến mức nào).
RestingRobot

6

hãy thử sử dụng FragmentTransaction thay vì FragmentManager. Tôi nghĩ đoạn mã dưới đây sẽ giải quyết được vấn đề của bạn. Nếu không, vui lòng cho tôi biết.

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

BIÊN TẬP:

Giao dịch phân mảnh

Vui lòng kiểm tra liên kết này. Tôi nghĩ rằng nó sẽ giải quyết các thắc mắc của bạn.


4
Mọi lời giải thích về lý do tại sao sử dụng FragmentTransaction lại khắc phục được sự cố sẽ rất tuyệt.
Hemanshu

3
Dialog # show (FragmentManager, tag) cũng làm điều tương tự. Đây không phải là một giải pháp.
William

3
Câu trả lời này không phải là giải pháp. DialogFragment # show (ft) và show (fm) thực hiện cùng một việc.
danijoo

@danijoo Bạn nói đúng rằng cả hai đều làm cùng một công việc. Nhưng trong một số điện thoại, có một số vấn đề tương tự như điều này nếu bạn đang sử dụng phân mảnh thay vì phân mảnh. Vì vậy, trong trường hợp của tôi, điều này đã giải quyết được vấn đề của tôi.
RIJO RV

6

Sử dụng phạm vi vòng đời mới của Activity-KTX đơn giản như mẫu mã sau:

lifecycleScope.launchWhenResumed {
   showErrorDialog(...)
}

Phương thức này có thể được gọi trực tiếp sau onStop () và sẽ hiển thị thành công hộp thoại khi onResume () đã được gọi khi quay lại.


3

Nhiều chế độ xem đăng các sự kiện cấp cao chẳng hạn như trình xử lý nhấp chuột vào hàng đợi sự kiện để chạy trì hoãn. Vì vậy, vấn đề là "onSaveInstanceState" đã được gọi cho Hoạt động nhưng hàng đợi sự kiện chứa "sự kiện nhấp chuột" bị trì hoãn. Do đó, khi sự kiện này được gửi đến trình xử lý của bạn

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

và mã của bạn không showném IllegalStateException.

Giải pháp đơn giản nhất là làm sạch hàng đợi sự kiện, trong 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();
        }
}

Bạn đã thực sự xác nhận rằng điều này giải quyết được vấn đề chưa?
mhsmith

Google đã thêm tính năng này vào bản phát hành tiếp theo của các thư viện androidx, hiện đang ở phiên bản beta ( activityfragment).
mhsmith

1
@mhsmith Tôi nhớ rằng giải pháp này đã giải quyết vấn đề trong mã của tôi bằng IllegalStateException
sim

2

Đặt đối tượng phân đoạn hộp thoại của bạn là toàn cầu và gọi lệnh removeAllowingStateLoss () trong phương thức onPause ()

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

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

Đừng quên gán giá trị trong phân đoạn và gọi show () khi nhấp vào nút hoặc bất cứ nơi nào.


1

Mặc dù nó không được đề cập chính thức ở bất cứ đâu nhưng tôi đã đối mặt với vấn đề này vài lần. Theo kinh nghiệm của tôi, có điều gì đó sai trong thư viện tương thích hỗ trợ các phân đoạn trên các nền tảng cũ hơn gây ra sự cố này. Bạn sử dụng kiểm tra điều này bằng cách sử dụng API trình quản lý phân mảnh bình thường. Nếu không có gì hoạt động thì bạn có thể sử dụng hộp thoại bình thường thay vì phân đoạn hộp thoại.


1
  1. Thêm lớp này vào dự án của bạn: (phải nằm trong gói android.support.v4.app )
gói android.support.v4.app;


/ **
 * Được tạo bởi Gil vào ngày 16/8/2017.
 * /

public class StatelessDialogFragment mở rộng DialogFragment {
    / **
     * Hiển thị hộp thoại, thêm phân đoạn bằng cách sử dụng một giao dịch hiện có và sau đó cam kết
     * giao dịch trong khi cho phép mất trạng thái.
* * Tôi khuyên bạn nên sử dụng {@link #show (FragmentTransaction, String)} hầu hết thời gian nhưng * điều này dành cho các hộp thoại mà bạn thực sự không quan tâm. (Gỡ lỗi / Theo dõi / Quảng cáo, v.v.) * * Giao dịch @param * Một giao dịch hiện có để thêm phân đoạn. * Thẻ @param * Thẻ cho đoạn này, theo * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add}. * @return Trả về định danh của giao dịch đã cam kết, theo * {@link FragmentTransaction # commit () FragmentTransaction.commit ()}. * @see StatelessDialogFragment # showAllowingStateLoss (FragmentManager, String) * / public int showAllowingStateLoss (giao dịch FragmentTransaction, thẻ chuỗi) { mDismissed = false; mShownByMe = true; transaction.add (cái này, thẻ); mViewDestroyed = false; mBackStackId = transaction.commitAllowingStateLoss (); trả về mBackStackId; } / ** * Hiển thị hộp thoại, thêm đoạn vào FragmentManager đã cho. Đây là một sự tiện lợi * để tạo giao dịch một cách rõ ràng, thêm phân đoạn vào giao dịch đó với thẻ đã cho và * cam kết nó mà không cần quan tâm đến trạng thái. Điều này không thêm giao dịch vào * ngăn xếp trở lại. Khi phân đoạn bị loại bỏ, một giao dịch mới sẽ được thực hiện để loại bỏ nó * từ hoạt động.
* * Tôi khuyên bạn nên sử dụng {@link #show (FragmentManager, String)} hầu hết thời gian nhưng đây là * đối với các hộp thoại mà bạn thực sự không quan tâm. (Gỡ lỗi / Theo dõi / Quảng cáo, v.v.) * * * Người quản lý @param * FragmentManager phân đoạn này sẽ được thêm vào. * Thẻ @param * Thẻ cho đoạn này, theo * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add}. * @see StatelessDialogFragment # showAllowingStateLoss (FragmentTransaction, String) * / public void showAllowingStateLoss (Trình quản lý FragmentManager, thẻ String) { mDismissed = false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction (); ft.add (cái này, thẻ); ft.commitAllowingStateLoss (); } }
  1. Mở rộng StatelessDialogFragment thay vì DialogFragment
  2. Sử dụng phương thức showAllowingStateLoss thay vì show

  3. Thưởng thức ;)


Tất cả các trường boolean này dùng để làm gì? Tại sao chúng không được khai báo là thành viên lớp?
không xác định

1
Các trường boolean là thành viên được bảo vệ của DialogFragment, tên của chúng rõ ràng gợi ý chúng dùng để làm gì và chúng ta cần cập nhật chúng để không can thiệp vào logic của DialogFragment. Lưu ý rằng trong lớp DialogFragment ban đầu, các chức năng này tồn tại nhưng không có quyền truy cập công khai
Gil SH

Mặc dù các thành viên này không được bảo vệ, nhưng chúng là nội bộ. Tôi đã gặp lỗi biên dịch khi tôi đặt StatelessDialogFragmentbên trong một trong các gói của mình.
không xác định

1

sử dụng mã này

FragmentTransaction ft = fm.beginTransaction();
        ft.add(yourFragment, "fragment_tag");
        ft.commitAllowingStateLoss();

thay vì

yourFragment.show(fm, "fragment_tag");

1

Tôi đã tìm thấy một giải pháp thanh lịch cho vấn đề này bằng cách sử dụng sự phản chiếu. Vấn đề của tất cả các giải pháp trên là các trường mDismissedmShownByMe không thay đổi trạng thái của chúng.

Chỉ cần ghi đè phương thức "hiển thị" trong phân đoạn hộp thoại trang tính dưới cùng tùy chỉnh của riêng bạn như mẫu bên dưới (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
"Tôi đã tìm thấy một giải pháp thanh lịch cho vấn đề này bằng cách sử dụng sự phản chiếu." nó thanh lịch như thế nào?
Mark Buikema

thanh lịch, phong cách, sang trọng, thông minh, tốt đẹp, duyên dáng
Рома Богдан

1
nó là giải pháp duy nhất làm việc cho tôi. Tôi nghĩ nó là thanh lịch
MBH

0

Việc triển khai sau đây có thể được sử dụng để giải quyết vấn đề thực hiện các thay đổi trạng thái an toàn trong Activityvòng đời, đặc biệt để hiển thị các hộp thoại: nếu trạng thái phiên bản đã được lưu (ví dụ: do thay đổi cấu hình), nó sẽ hoãn chúng cho đến khi trạng thái tiếp tục lại đã được thực hiện.

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

Sau đó, sử dụng một lớp như thế này:

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

Bạn có thể hiển thị các hộp thoại một cách an toàn mà không cần lo lắng về trạng thái ứng dụng:

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

và sau đó gọi TestDialog.show(this)từ bên trong của bạn XAppCompatActivity.

Nếu bạn muốn tạo một lớp hộp thoại chung chung hơn với các tham số, bạn có thể lưu chúng trong a Bundlevới các đối số trong show()phương thức và truy xuất chúng với getArguments()trong onCreateDialog().

Toàn bộ cách tiếp cận có vẻ hơi phức tạp, nhưng một khi bạn đã tạo hai lớp cơ sở cho các hoạt động và hộp thoại, nó khá dễ sử dụng và hoạt động hoàn hảo. Nó có thể được sử dụng cho các Fragmenthoạt động dựa trên khác có thể bị ảnh hưởng bởi cùng một vấn đề.


0

Lỗi này dường như đang xảy ra vì các sự kiện đầu vào (chẳng hạn như sự kiện key down hoặc onclick) đang được phân phối sau khi onSaveInstanceStateđược gọi.

Giải pháp là ghi đè onSaveInstanceStatetrong Hoạt động của bạn và hủy mọi sự kiện đang chờ xử lý.

@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();
        }
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.