IllegalStateException: Không thể thực hiện hành động này sau khi onSaveInstanceState với ViewPager


496

Tôi đang nhận được báo cáo người dùng từ ứng dụng của mình trên thị trường, cung cấp ngoại lệ sau:

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)

Rõ ràng nó có liên quan đến FragmentManager mà tôi không sử dụng. Stacktrace không hiển thị bất kỳ lớp nào của riêng tôi, vì vậy tôi không biết ngoại lệ này xảy ra ở đâu và làm thế nào để ngăn chặn nó.

Đối với bản ghi: Tôi có một tabhost và trong mỗi tab có một Activitygroup chuyển đổi giữa các Hoạt động.


2
Tôi thấy câu hỏi này thảo luận về cùng một vấn đề, nhưng cũng không có giải pháp nào cả .. stackoverflow.com/questions/7469082/ ích
nhaarman

3
Trong khi bạn không sử dụng FragmentManager, Honeycomb chắc chắn là có. Điều này có xảy ra trên máy tính bảng Honeycomb thật không? Hoặc có thể là ai đó đang chạy Honeycomb bị hack trên điện thoại hoặc thứ gì đó và đó là phiên bản bị hack đang gặp khó khăn?
CommonsWare

1
Tôi không có ý kiến. Đây là thông tin duy nhất tôi nhận được trong Bảng điều khiển dành cho nhà phát triển thị trường, tin nhắn người dùng không chứa thông tin hữu ích nào cả ..
nhaarman

Tôi đang sử dụng Flurry, trong đó hiển thị cho tôi 11 phiên với Android 3.0.1 và tôi có 11 báo cáo về ngoại lệ này. Có thể là trùng hợp mặc dù. Android 3.1 và 3.2 có lần lượt 56 và 38 phiên.
nhaarman

Báo cáo lỗi thị trường có phần 'Nền tảng', đôi khi nó có phiên bản Android của thiết bị trong đó.
Nikolay Elenkov

Câu trả lời:


720

Vui lòng kiểm tra câu trả lời của tôi ở đây . Về cơ bản tôi chỉ phải:

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

Đừng thực hiện cuộc gọi đến super()trên saveInstanceStatephương pháp. Điều này đã làm mọi thứ rối tung lên ...

Đây là một lỗi đã biết trong gói hỗ trợ.

Nếu bạn cần lưu ví dụ và thêm một cái gì đó vào, outState Bundlebạn có thể sử dụng như sau:

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

Cuối cùng, giải pháp thích hợp là (như đã thấy trong các bình luận) để sử dụng:

transaction.commitAllowingStateLoss();

khi thêm hoặc thực hiện FragmentTransactionđiều đó đã gây ra Exception.


370
Bạn nên sử dụng commit ALLowingStateLoss () thay vì commit ()
meh

18
Nhận xét này về commit ALLowingStateLoss () là một câu trả lời theo đúng nghĩa của nó - bạn nên đăng nó như thế.
Risadinha

20
Về 'commit ALLowingStateLoss' - /> "Điều này rất nguy hiểm vì cam kết có thể bị mất nếu sau đó hoạt động cần được khôi phục từ trạng thái của nó, vì vậy chỉ nên sử dụng trạng thái này để trạng thái UI thay đổi bất ngờ người dùng."
Đã sửa đổi

10
Nếu tôi nhìn vào nguồn v4 cho popBackStackImmediatenó ngay lập tức thất bại nếu trạng thái đã được lưu. Trước đây thêm đoạn commitAllowingStateLosskhông chơi bất kỳ phần nào. Thử nghiệm của tôi cho thấy điều này là đúng. Nó không có tác dụng đối với ngoại lệ cụ thể này. Những gì chúng ta cần là một popBackStackImmediateAllowingStateLossphương pháp.
Synesso

3
@DanieleB vâng, tôi đã đăng câu trả lời ở đây. Nhưng tôi thực sự đã tìm thấy một giải pháp thậm chí tốt hơn bằng cách sử dụng bus tin nhắn Otto: đăng ký đoạn này với tư cách là người đăng ký và lắng nghe kết quả không đồng bộ từ xe buýt. Hủy đăng ký tạm dừng và đăng ký lại trong sơ yếu lý lịch. Async cũng cần một phương thức Produce cho những lần nó hoàn thành và đoạn bị tạm dừng. Khi tôi có thời gian tôi sẽ cập nhật câu trả lời của mình với điều này chi tiết hơn.
Synesso

130

Có nhiều vấn đề liên quan với một thông báo lỗi tương tự. Kiểm tra dòng thứ hai của dấu vết ngăn xếp đặc biệt này. Ngoại lệ này đặc biệt liên quan đến cuộc gọi đến FragmentManagerImpl.popBackStackImmediate.

Cuộc gọi phương thức này, như popBackStack, sẽ luôn luôn thất bại IllegalStateExceptionnếu trạng thái phiên đã được lưu. Kiểm tra nguồn. Bạn không thể làm gì để ngăn chặn ngoại lệ này bị ném.

  • Xóa cuộc gọi super.onSaveInstanceStatesẽ không giúp đỡ.
  • Tạo ra mảnh vỡ commitAllowingStateLosssẽ không giúp đỡ.

Đây là cách tôi quan sát vấn đề:

  • Có một hình thức với một nút gửi.
  • Khi nhấn nút, một hộp thoại sẽ được tạo và quá trình không đồng bộ bắt đầu.
  • Người dùng nhấp vào phím home trước khi quá trình kết thúc - onSaveInstanceStateđược gọi.
  • Quá trình hoàn tất, một cuộc gọi lại được thực hiện và popBackStackImmediateđược thử.
  • IllegalStateException được ném.

Đây là những gì tôi đã làm để giải quyết nó:

Vì không thể tránh được IllegalStateExceptiontrong cuộc gọi lại, hãy bắt và bỏ qua nó.

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

Điều này là đủ để ngăn chặn ứng dụng bị sập. Nhưng bây giờ, người dùng sẽ khôi phục ứng dụng và thấy rằng nút mà họ nghĩ rằng họ đã nhấn chưa được nhấn (họ nghĩ). Các mảnh mẫu vẫn đang hiển thị!

Để khắc phục điều này, khi hộp thoại được tạo, hãy tạo một số trạng thái để cho biết quá trình đã bắt đầu.

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

Và lưu trạng thái này trong gói.

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

Đừng quên tải lại lần nữa trong onViewCreated

Sau đó, khi tiếp tục, khôi phục lại các đoạn nếu đã gửi trước đó. Điều này ngăn người dùng quay trở lại hình thức chưa được gửi.

@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
Thú vị khi đọc về điều đó tại đây: androiddesignpotypes.com/2013/08/ Kẻ
Pascal

Nếu bạn sử dụng DialogFragment, tôi đã thực hiện một giải pháp thay thế cho nó tại đây: github.com/AndroidDeveloperLB/DialogShard
nhà phát triển Android

Điều gì xảy ra nếu popBackStackImmediateđược gọi bởi chính Android?
Kimi Chiu

1
Hoàn toàn tuyệt vời. Điều này nên là câu trả lời được chấp nhận. Cảm ơn rât nhiều! Có lẽ tôi sẽ thêm submitPression = false; sau popBackStackIn Ngay.
Sơ sinh

Tôi đã không sử dụng phương thức void onSaveInstanceState (Bundle outState). Tôi có cần đặt phương thức trống cho khoảng trống công khai trênSaveInstanceState (Bundle outState) không?
Faxriddin Abdullayev

55

Kiểm tra nếu các hoạt động isFinishing()trước khi hiển thị các mảnh và chú ý đến commitAllowingStateLoss().

Thí dụ:

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 () không hoạt động với tôi.
Allen Vork

! isFinishing () &&! isDestroyed () hoạt động với tôi, nhưng nó yêu cầu API 17. Nhưng đơn giản là nó không hiển thị a DialogFragment. Xem stackoverflow.com/questions/15729138/ cho các giải pháp tốt khác, stackoverflow.com/a/41813953/2914140 đã giúp tôi.
CoolMind

29

Đó là tháng 10 năm 2017 và Google tạo Thư viện hỗ trợ Android với những thứ mới gọi là thành phần Vòng đời. Nó cung cấp một số ý tưởng mới cho điều này 'Không thể thực hiện hành động này sau sự cố onSaveInstanceState'.

Nói ngắn gọn:

  • Sử dụng thành phần vòng đời để xác định xem đó có phải là thời điểm chính xác để bật lên đoạn của bạn không.

Phiên bản dài hơn với giải thích:

  • Tại sao vấn đề này đi ra?

    Đó là bởi vì bạn đang cố gắng sử dụng FragmentManagertừ hoạt động của mình (điều này sẽ giữ mảnh vỡ của bạn mà tôi cho là?) Để thực hiện một giao dịch cho đoạn của bạn. Thông thường, điều này có vẻ như bạn đang cố thực hiện một số giao dịch cho một đoạn sắp tới, trong khi đó hoạt động máy chủ đã gọi savedInstanceStatephương thức (người dùng có thể chạm vào nút home để hoạt động gọi onStop(), trong trường hợp của tôi là lý do)

    Thông thường vấn đề này không nên xảy ra - chúng tôi luôn cố gắng tải đoạn vào hoạt động ngay từ đầu, giống như onCreate()phương pháp là một nơi hoàn hảo cho việc này. Nhưng đôi khi điều này xảy ra , đặc biệt là khi bạn không thể quyết định đoạn nào bạn sẽ tải vào hoạt động đó hoặc bạn đang cố tải đoạn từ một AsyncTaskkhối (hoặc bất cứ điều gì sẽ mất một ít thời gian). Thời gian, trước khi giao dịch mảnh thực sự xảy ra, nhưng sau onCreate()phương thức của hoạt động , người dùng có thể làm bất cứ điều gì. Nếu người dùng nhấn nút home, kích hoạt onSavedInstanceState()phương thức của hoạt động , sẽ có một sự can not perform this actioncố.

    Nếu bất cứ ai muốn nhìn sâu hơn về vấn đề này, tôi khuyên họ nên xem bài đăng trên blog này . Nó nhìn sâu bên trong lớp mã nguồn và giải thích rất nhiều về nó. Ngoài ra, nó đưa ra lý do rằng bạn không nên sử dụng commitAllowingStateLoss()phương pháp để khắc phục sự cố này (tin tôi đi, nó không cung cấp gì cho mã của bạn)

  • Làm thế nào để khắc phục điều này?

    • Tôi có nên sử dụng commitAllowingStateLoss()phương pháp để tải mảnh? Không, bạn không nên ;

    • Tôi có nên ghi đè onSaveInstanceStatephương thức, bỏ qua superphương thức bên trong nó? Không, bạn không nên ;

    • Tôi có nên sử dụng phép thuật isFinishingbên trong hoạt động, để kiểm tra xem hoạt động của máy chủ có đúng lúc cho giao dịch mảnh không? Vâng, điều này có vẻ như đúng cách để làm.

  • Hãy xem những gì thành phần Vòng đời có thể làm.

    Về cơ bản, Google thực hiện một số triển khai bên trong AppCompatActivitylớp (và một số lớp cơ sở khác mà bạn nên sử dụng trong dự án của mình), điều này giúp xác định trạng thái vòng đời hiện tại dễ dàng hơn . Hãy nhìn lại vấn đề của chúng tôi: tại sao vấn đề này sẽ xảy ra? Đó là bởi vì chúng tôi làm một cái gì đó sai thời điểm. Vì vậy, chúng tôi cố gắng không làm điều đó, và vấn đề này sẽ biến mất.

    Tôi viết mã một chút cho dự án của riêng tôi, đây là những gì tôi đang sử dụng LifeCycle. Tôi viết mã trong 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()
    }

Như tôi trình bày ở trên. Tôi sẽ kiểm tra trạng thái vòng đời của hoạt động máy chủ. Với thành phần Vòng đời trong thư viện hỗ trợ, điều này có thể cụ thể hơn. Mã lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)có nghĩa là, nếu trạng thái hiện tại ít nhất onResume, không muộn hơn nó? Điều này đảm bảo rằng phương thức của tôi sẽ không được thực thi trong một số trạng thái cuộc sống khác (như onStop).

  • Tất cả đã xong chưa?

    Dĩ nhiên là không. Mã tôi đã trình bày cho biết một số cách mới để ngăn chặn ứng dụng bị sập. Nhưng nếu nó đi đến trạng thái onStop, dòng mã đó sẽ không làm gì và do đó không hiển thị gì trên màn hình của bạn. Khi người dùng quay lại ứng dụng, họ sẽ thấy một màn hình trống, đó là hoạt động máy chủ trống hoàn toàn không có mảnh vỡ nào. Đó là trải nghiệm tồi tệ (vâng tốt hơn một chút so với một vụ tai nạn).

    Vì vậy, ở đây tôi ước có thể có một cái gì đó đẹp hơn: ứng dụng sẽ không gặp sự cố nếu nói đến trạng thái cuộc sống muộn hơn onResume, phương thức giao dịch là trạng thái cuộc sống nhận thức được; ngoài ra, hoạt động sẽ cố gắng tiếp tục kết thúc hành động giao dịch mảnh đó, sau khi người dùng quay lại ứng dụng của chúng tôi.

    Tôi thêm một cái gì đó nhiều hơn vào phương pháp này:

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

Tôi duy trì một danh sách bên trong dispatcherlớp này , để lưu trữ những đoạn đó không có cơ hội hoàn thành hành động giao dịch. Và khi người dùng quay lại từ màn hình chính và thấy vẫn còn một đoạn đang chờ được tung ra, nó sẽ chuyển sang resume()phương thức dưới @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)chú thích. Bây giờ tôi nghĩ nó nên hoạt động như tôi mong đợi.


8
Sẽ thật tuyệt khi sử dụng Java thay vì Kotlin
Shchvova

1
Tại sao việc triển khai FragmentDispatcherdanh sách của bạn sử dụng một danh sách để lưu trữ các đoạn đang chờ xử lý nếu sẽ chỉ có một đoạn được khôi phục?
fraherm

21

Đây là một giải pháp khác nhau cho vấn đề này.

Sử dụng biến thành viên riêng, bạn có thể đặt dữ liệu được trả về dưới dạng mục đích có thể được xử lý sau super.onResume ();

Thích như vậy:

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
Tùy thuộc vào hành động bạn đang làm mà không được phép, điều này có thể cần phải chuyển sang thời điểm muộn hơn so với onResume (). Đối với insatcne, nếu FragmentTransaction.commit () là vấn đề, thì điều này cần phải đi vào onPostResume ().
pjv

1
Đây là câu trả lời cho câu hỏi này cho tôi. Vì tôi cần chuyển tiếp thẻ NFC đã nhận cho hoạt động trước đó, đây là điều đã làm cho tôi.
Janis Peisenieks

7
Đối với tôi nó đã xảy ra bởi vì tôi đã không gọi super.onActivityResult().
Sufian

20

Giải pháp ngắn gọn và hiệu quả:

Thực hiện theo các bước đơn giản

Các bước

Bước 1: Ghi đè onSaveInstanceStatetrạng thái trong đoạn tương ứng. Và loại bỏ siêu phương pháp từ nó.

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

Bước 2: Sử dụng fragmentTransaction.commitAllowingStateLoss( );

thay vì fragmentTransaction.commit( ); trong khi hoạt động phân mảnh.


Câu trả lời không được sao chép hoặc phản hồi dưới dạng khác. Trong đó, chúng tôi đã được đăng để giúp mọi người bằng giải pháp làm việc của tôi, có một vài thử nghiệm và lỗi
Vinayak

12

THƯỞNG , sử dụng transaction.commitAllowingStateLoss()có thể dẫn đến một trải nghiệm xấu cho người dùng. Để biết thêm thông tin về lý do tại sao ngoại lệ này được ném, xem bài viết này .


6
Điều này không cung cấp câu trả lời cho câu hỏi, bạn phải cung cấp câu trả lời hợp lệ cho câu hỏi
Umar Ata

10

Tôi tìm thấy một giải pháp bẩn cho loại vấn đề này. Nếu bạn vẫn muốn giữ bạn ActivityGroupsvì bất kỳ lý do gì (tôi có lý do giới hạn thời gian), bạn chỉ cần thực hiện

public void onBackPressed() {}

trong của bạn Activityvà làm một số backmã trong đó. ngay cả khi không có Phương thức như vậy trên các Thiết bị cũ hơn, Phương thức này được gọi bởi các thiết bị mới hơn.


6

Không sử dụng commit ALLowingStateLoss (), nó chỉ nên được sử dụng cho các trường hợp trạng thái UI thay đổi bất ngờ đối với người dùng.

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

Nếu giao dịch xảy ra trong ChildFragmentManager của ParentFragment, hãy sử dụng ParentFragment.isResume () bên ngoài để kiểm tra thay thế.

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

5

Tôi đã có một vấn đề tương tự, kịch bản là như thế này:

  • Hoạt động của tôi là thêm / thay thế các đoạn danh sách.
  • Mỗi phân đoạn danh sách có một tham chiếu đến hoạt động, để thông báo cho hoạt động khi một mục danh sách được nhấp (mẫu quan sát).
  • Mỗi đoạn danh sách gọi setRetainInstance (true); trong phương pháp onCreate của nó .

Các onCreate phương pháp của hoạt động là như thế này:

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

Ngoại lệ được đưa ra vì khi cấu hình thay đổi (thiết bị được quay), hoạt động được tạo, đoạn chính được lấy từ lịch sử của trình quản lý đoạn và đồng thời đoạn đó đã có tham chiếu OLD đến hoạt động bị hủy

thay đổi việc thực hiện để giải quyết vấn đề này:

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

bạn cần đặt trình nghe của mình mỗi khi hoạt động được tạo để tránh tình huống các đoạn có tham chiếu đến các trường hợp bị hủy cũ của hoạt động.


5

Nếu bạn thừa kế từ FragmentActivity, bạn phải gọi siêu lớp trong onActivityResult():

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

Nếu bạn không làm điều này và cố gắng hiển thị hộp thoại phân đoạn trong phương thức đó, bạn có thể nhận được OP IllegalStateException. (Thành thật mà nói, tôi hoàn toàn không hiểu tại sao siêu cuộc gọi khắc phục sự cố. onActivityResult()Được gọi trước đó onResume(), vì vậy nó vẫn không được phép hiển thị hộp thoại phân đoạn.)


1
Rất muốn biết lý do tại sao điều này khắc phục vấn đề.
Big McLUNDHuge

3

Tôi đã nhận được ngoại lệ này khi tôi nhấn nút quay lại để hủy bỏ trình chọn ý định trên hoạt động phân đoạn bản đồ của mình. Tôi đã giải quyết điều này bằng cách thay thế mã của onResume (nơi tôi đang khởi tạo đoạn) thành onstart () và ứng dụng đang hoạt động tốt. Hy vọng nó giúp ích.


2

Tôi nghĩ rằng sử dụng transaction.commitAllowingStateLoss();không phải là giải pháp tốt nhất. Ngoại lệ này sẽ được đưa ra khi cấu hình của hoạt động thay đổi và đoạn onSavedInstanceState()được gọi và sau đó phương thức gọi lại async của bạn cố gắng thực hiện phân đoạn.

Giải pháp đơn giản có thể kiểm tra xem hoạt động có thay đổi cấu hình hay không

ví dụ kiểm tra isChangingConfigurations()

I E

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

Kiểm tra liên kết này là tốt


Bằng cách nào đó tôi đã có ngoại lệ này khi người dùng nhấp vào một cái gì đó (nhấp chuột là kích hoạt để thực hiện giao dịch cam kết). Làm sao chuyện này có thể? Giải pháp của bạn ở đây?
nhà phát triển Android

@androiddeveloper bạn đang làm gì khi nhấp vào người dùng. bằng cách nào đó mảnh vỡ đang lưu trạng thái của nó trước khi bạn thực hiện giao dịch
Amol Desai

Ngoại lệ đã được ném vào dòng cam kết giao dịch chính xác. Ngoài ra, tôi có một lỗi đánh máy kỳ lạ: thay vì "ở đây" tôi có nghĩa là "làm việc ở đây".
nhà phát triển Android

@androiddeveloper bạn nói đúng! Nhưng trước khi thực hiện giao dịch, bạn có sinh ra bất kỳ chủ đề nền hay cái gì không?
Amol Desai

Tôi không nghĩ vậy (xin lỗi tôi ra khỏi văn phòng), nhưng tại sao nó lại quan trọng? Đó là tất cả các công cụ UI ở đây ... Nếu tôi từng làm một cái gì đó trên một chủ đề nền, tôi sẽ có ngoại lệ ở đó, cộng với việc tôi không đặt các công cụ liên quan đến UI trên các chủ đề nền, vì nó quá rủi ro.
nhà phát triển Android

2

Có thể là giải pháp đơn giản và trơn tru nhất mà tôi tìm thấy trong trường hợp của mình là tránh bật mảnh vỡ vi phạm ra khỏi ngăn xếp để đáp ứng với kết quả hoạt động. Vì vậy, thay đổi cuộc gọi này trong onActivityResult():

popMyFragmentAndMoveOn();

đến đây:

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

đã giúp trong trường hợp của tôi.


2

Nếu bạn đang thực hiện một số FragmentTransaction trong onActivityResult những gì bạn có thể làm, bạn có thể đặt một số giá trị boolean bên trong onActivityResult sau đó trong onResume bạn có thể thực hiện FragmentTransaction của mình trên cơ sở giá trị boolean. Vui lòng tham khảo mã dưới đây.

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

Vui lòng không đăng mã dưới dạng hình ảnh, thay vào đó hãy sử dụng định dạng mã.
Micer

1
Vâng, nó đã giúp tôi .., Đây là giải pháp chính xác và phù hợp cho thiết bị marshmallow tôi đã có ngoại lệ này và giải quyết bằng thủ thuật đơn giản này .. !! Do đó đã bỏ phiếu.
Sandhya sasane

2

Phép lịch sự: Giải pháp cho IllegalStateException

Vấn đề này đã làm tôi bực mình rất nhiều thời gian nhưng may mắn thay tôi đã đưa ra một giải pháp cụ thể cho nó. Một lời giải thích chi tiết về nó ở đây .

Việc sử dụng commit ALLowStateloss () có thể ngăn chặn ngoại lệ này nhưng sẽ dẫn đến sự bất thường của UI. Vì vậy, chúng tôi đã hiểu rằng IllegalStateException gặp phải khi chúng tôi cố gắng thực hiện một đoạn sau khi trạng thái Activity bị mất - vì vậy chúng tôi chỉ nên trì hoãn giao dịch cho đến khi trạng thái được khôi phục Nó có thể được thực hiện đơn giản như thế này

Khai báo hai biến boolean riêng

 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;

Bây giờ, trong onPostResume () và onPause, chúng tôi đã đặt và hủy đặt biến boolean của chúng tôi làTransactionSafe. Ý tưởng là chỉ đánh dấu sự vận chuyển an toàn khi hoạt động ở phía trước để không có cơ hội stateloss.

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

-Những gì chúng tôi đã làm cho đến nay sẽ tiết kiệm được từ IllegalStateException nhưng các giao dịch của chúng tôi sẽ bị mất nếu chúng được thực hiện sau khi hoạt động chuyển sang nền, giống như commit ALLowStateloss (). Để giúp với điều đó, chúng ta có biến boolean 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

Giao dịch mảnh không nên được thực hiện sau Activity.onStop()! Kiểm tra xem bạn không có bất kỳ cuộc gọi lại nào có thể thực hiện giao dịch sau đó onStop(). Tốt hơn là khắc phục lý do thay vì cố gắng giải quyết vấn đề bằng các cách tiếp cận như.commitAllowingStateLoss()


1

Bắt đầu từ thư viện hỗ trợ phiên bản 24.0.0, bạn có thể gọi FragmentTransaction.commitNow()phương thức cam kết giao dịch này một cách đồng bộ thay vì gọi commit()theo sau executePendingTransactions(). Như tài liệu nói cách tiếp cận này thậm chí còn tốt hơn:

Gọi commitNow tốt hơn là gọi commit () theo sau là execPendingTransilities () vì sau này sẽ có tác dụng phụ là cố gắng thực hiện tất cả các giao dịch hiện đang chờ xử lý cho dù đó có phải là hành vi mong muốn hay không.


1

Bất cứ khi nào bạn đang cố gắng tải một đoạn trong hoạt động của mình, hãy đảm bảo rằng hoạt động đó được tiếp tục và sẽ không tạm dừng trạng thái. Trong trạng thái tạm dừng, bạn có thể sẽ mất hoạt động cam kết được thực hiện.

Bạn có thể sử dụng giao dịch.commit ALLowingStateLoss () thay vì giao dịch.commit () để tải đoạn

hoặc là

Tạo một boolean và kiểm tra xem hoạt động sẽ không xảy ra

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

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

sau đó trong khi tải kiểm tra đoạn

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

1

Để bỏ qua vấn đề này, chúng tôi có thể sử dụng Thành phần Kiến trúc Điều hướng , được giới thiệu trong Google I / O 2018. Thành phần Kiến trúc Điều hướng đơn giản hóa việc triển khai điều hướng trong ứng dụng Android.


Nó không đủ tốt và lỗi (xử lý liên kết sâu kém, không thể lưu chương trình trạng thái / ẩn các đoạn và một số vấn đề quan trọng vẫn còn mở)
do01

1

Liên quan đến câu trả lời tuyệt vời của @Anthonyeef, đây là một mã mẫu trong 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

Nếu bạn gặp sự cố với phương thức popBackStack () hoặc popBackStackImmediate (), hãy thử sửa lỗi với:

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

Điều này cũng làm việc cho tôi.


Lưu ý rằng nó yêu cầu API 26 trở lên
Itay Feldman

1

Trong trường hợp của tôi, tôi đã gặp lỗi này trong một phương thức ghi đè được gọi là onActivityResult. Sau khi đào tôi chỉ cần tìm ra có lẽ tôi cần phải gọi ' siêu ' trước đó.
Tôi đã thêm nó và nó chỉ hoạt động

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

}

Có lẽ bạn chỉ cần thêm một 'siêu' vào bất kỳ ghi đè nào bạn đang thực hiện trước mã của mình.


1

Gia hạn Kotlin

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

Sử dụng:

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

Bạn có thể xóa () -> trước Fragment
Claire

@Claire, cảm ơn! Bạn có nghĩa là thay đổi để fragment: Fragment? Có, tôi đã thử biến thể này, nhưng trong trường hợp này, một mảnh sẽ được tạo ra trong mọi trường hợp (ngay cả khi FragmentManager == null, nhưng tôi đã không gặp phải tình huống này). Tôi đã cập nhật câu trả lời và thay đổi null thành thẻ addToBackStack().
CoolMind

1

Sự cố này là do FragmentTransaction được cam kết sau khi vòng đời của Activity sở hữu nó đã chạy trênSaveInstanceState. Điều này thường được gây ra bởi việc cam kết các phân đoạn FragmentTrans từ một cuộc gọi lại không đồng bộ. Kiểm tra tài nguyên được liên kết để biết thêm chi tiết.

Giao dịch mảnh & mất trạng thái hoạt động

http://www.androiddesignpotypes.com/2013/08/fragment-transaction-commit-state-loss.html


0

Thêm điều này trong hoạt động của bạn

@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

Tôi cũng đã gặp phải sự cố này và sự cố xảy ra mỗi khi bối cảnh của bạn FragmentActivitybị thay đổi (ví dụ: Hướng màn hình được thay đổi, v.v.). Vì vậy, sửa chữa tốt nhất cho nó là cập nhật bối cảnh từ của bạn FragmentActivity.


0

Tôi đã kết thúc với việc tạo một đoạn cơ sở và làm cho tất cả các đoạn trong ứng dụng của tôi mở rộng nó

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

Sau đó, khi tôi cố gắng hiển thị một đoạn tôi sử dụng showAllowingStateLoss thay vìshow

như thế này:

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

Tôi đã đưa ra giải pháp này từ PR này: https://github.com/googlesamples/easypermissions/pull/170/files


0

Một cách giải quyết khác có thể xảy ra, mà tôi không chắc là có giúp ích trong mọi trường hợp không (nguồn gốc ở đây ):

@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

Tôi biết có một câu trả lời được chấp nhận bởi @Ovidiu Latcu nhưng sau một thời gian, lỗi vẫn còn.

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

Crashlytics vẫn gửi cho tôi thông báo lỗi kỳ lạ này.

Tuy nhiên, hiện tại lỗi chỉ xảy ra trên phiên bản 7+ (Nougat) Khắc phục của tôi là sử dụng commit ALLowingStateLoss () thay vì commit () tại FragmentTransaction.

Bài đăng này rất hữu ích cho commit ALLowingStateLoss () và không bao giờ gặp sự cố phân đoạn nữa.

Tóm lại, câu trả lời được chấp nhận ở đây có thể hoạt động trên các phiên bản Android trước Nougat.

Điều này có thể tiết kiệm cho ai đó một vài giờ tìm kiếm. hạnh phúc mã hóa. <3 chúc mừng

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.