IllegalStateException: Tidak dapat melakukan tindakan ini setelah onSaveInstanceState dengan ViewPager

496

Saya mendapatkan laporan pengguna dari aplikasi saya di pasar, memberikan pengecualian berikut:

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)

Rupanya itu ada hubungannya dengan FragmentManager, yang tidak saya gunakan. Stacktrace tidak menunjukkan kelas saya sendiri, jadi saya tidak tahu di mana pengecualian ini terjadi dan bagaimana cara mencegahnya.

Sebagai catatan: Saya memiliki tabhost, dan di setiap tab ada ActivityGroup yang beralih di antara Aktivitas.

nhaarman
sumber
2
Saya menemukan pertanyaan ini membahas masalah yang sama, tapi tidak ada solusi ada baik .. stackoverflow.com/questions/7469082/...
nhaarman
3
Meskipun Anda tidak menggunakan FragmentManager, Honeycomb tentu saja. Apakah ini terjadi pada tablet Honeycomb asli? Atau mungkinkah seseorang menjalankan Honeycomb yang diretas di telepon atau sesuatu dan edisi yang diretas itu mengalami kesulitan?
CommonsWare
1
Saya tidak punya ide. Ini adalah satu-satunya informasi yang saya dapatkan di Konsol Pengembang Pasar, pesan pengguna juga tidak mengandung informasi yang berguna ..
nhaarman
Saya menggunakan Flurry, yang menunjukkan kepada saya 11 sesi dengan Android 3.0.1, dan saya memiliki 11 laporan pengecualian ini. Bisa jadi kebetulan juga. Android 3.1 dan 3.2 masing-masing memiliki 56 dan 38 sesi.
nhaarman
Laporan kesalahan Pasar memiliki bagian 'Platform', terkadang ada versi Android perangkat di dalamnya.
Nikolay Elenkov

Jawaban:

720

Silakan periksa jawaban saya di sini . Pada dasarnya saya hanya harus:

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

Jangan membuat panggilan ke super()pada saveInstanceStatemetode. Ini mengacaukan segalanya ...

Ini yang diketahui bug yang dalam paket dukungan.

Jika Anda perlu menyimpan instance dan menambahkan sesuatu ke outState Bundle Anda dapat menggunakan yang berikut ini:

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

Pada akhirnya solusi yang tepat adalah (seperti yang terlihat di komentar) untuk menggunakan:

transaction.commitAllowingStateLoss();

saat menambahkan atau melakukan FragmentTransactionyang menyebabkan Exception.

Ovidiu Latcu
sumber
370
Anda harus menggunakan commitAllowingStateLoss () alih-alih komit ()
meh
18
Komentar tentang commitAllowingStateLoss () ini adalah jawabannya sendiri - Anda harus mempostingnya seperti itu.
Risadinha
20
Mengenai 'commitAllowingStateLoss' - /> "Ini berbahaya karena komit dapat hilang jika aktivitas nanti perlu dipulihkan dari kondisinya, jadi ini hanya boleh digunakan untuk kasus-kasus di mana tidak apa-apa bagi keadaan UI untuk berubah secara tak terduga pada pengguna. "
Codeversed
10
Jika saya melihat sumber v4 untuk popBackStackImmediatesegera gagal jika negara telah disimpan. Sebelumnya menambahkan fragmen dengan commitAllowingStateLosstidak memainkan peran apa pun. Pengujian saya menunjukkan ini benar. Ini tidak berpengaruh pada pengecualian khusus ini. Yang kita butuhkan adalah sebuah popBackStackImmediateAllowingStateLossmetode.
Synesso
3
@AnieleB ya, saya sudah mengirim jawaban di sini. Tapi saya benar-benar menemukan solusi yang lebih baik dengan menggunakan bus pesan Otto: daftarkan fragmen sebagai pelanggan dan dengarkan hasil async dari bus. Batalkan registrasi saat jeda dan daftar ulang pada resume. Async juga membutuhkan metode Produce untuk saat-saat ketika selesai dan fragmen dijeda. Ketika saya mendapatkan waktu, saya akan memperbarui jawaban saya dengan ini secara lebih rinci.
Synesso
130

Ada banyak masalah terkait dengan pesan kesalahan yang serupa. Periksa baris kedua dari jejak tumpukan khusus ini. Pengecualian ini secara khusus terkait dengan panggilan ke FragmentManagerImpl.popBackStackImmediate.

Panggilan metode ini, seperti popBackStack, akan selalu gagal IllegalStateExceptionjika status sesi sudah disimpan. Periksa sumbernya. Tidak ada yang bisa Anda lakukan untuk menghentikan pengecualian ini.

  • Menghapus panggilan ke super.onSaveInstanceStatetidak akan membantu.
  • Menciptakan Fragmen dengan commitAllowingStateLosstidak akan membantu.

Inilah cara saya mengamati masalahnya:

  • Ada formulir dengan tombol kirim.
  • Ketika tombol diklik, dialog dibuat dan proses async dimulai.
  • Pengguna mengklik tombol beranda sebelum proses selesai - onSaveInstanceStatedipanggil.
  • Proses selesai, panggilan balik dilakukan dan popBackStackImmediatedicoba.
  • IllegalStateException terlempar.

Inilah yang saya lakukan untuk menyelesaikannya:

Karena tidak mungkin untuk menghindari IllegalStateExceptionpanggilan balik, tangkap & abaikan saja.

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

Ini cukup untuk menghentikan aplikasi agar tidak mogok. Tetapi sekarang pengguna akan memulihkan aplikasi dan melihat bahwa tombol yang mereka pikir telah mereka tekan belum ditekan sama sekali (menurut mereka). Fragmen formulir masih menunjukkan!

Untuk memperbaiki ini, ketika dialog dibuat, buat beberapa negara untuk menunjukkan proses telah dimulai.

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

Dan simpan status ini dalam bundel.

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

Jangan lupa untuk memuatnya kembali onViewCreated

Kemudian, ketika melanjutkan, kembalikan fragmen-fragmen jika kirim sebelumnya dicoba. Ini mencegah pengguna untuk kembali ke formulir yang tampaknya tidak terkirim.

@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;
    }
}
Synesso
sumber
5
Bacaan menarik tentang itu di sini: androiddesignpatterns.com/2013/08/...
Pascal
Jika Anda menggunakan DialogFragment, saya telah membuat alternatif untuknya di sini: github.com/AndroidDeveloperLB/DialogShard
pengembang android
Bagaimana jika popBackStackImmediateitu disebut oleh Android itu sendiri?
Kimi Chiu
1
Benar-benar hebat Yang ini harus menjadi jawaban yang diterima. Terima kasih banyak! Mungkin saya akan menambahkan submPressed = false; setelah popBackStackInmediate.
Neonigma
Saya tidak menggunakan public void onSaveInstanceState (Bundle outState) metode. Apakah saya perlu mengatur metode kosong untuk public void onSaveInstanceState (Bundle outState)?
Faxriddin Abdullayev
55

Periksa apakah aktivitas isFinishing()sebelum menunjukkan fragmen dan perhatikan commitAllowingStateLoss().

Contoh:

if(!isFinishing()) {
FragmentManager fm = getSupportFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            DummyFragment dummyFragment = DummyFragment.newInstance();
            ft.add(R.id.dummy_fragment_layout, dummyFragment);
            ft.commitAllowingStateLoss();
}
Naskov
sumber
1
! isFinishing () &&! isDestroyed () tidak berfungsi untuk saya.
Allen Vork
! isFinishing () &&! isDestroyed () bekerja untuk saya, tetapi membutuhkan API 17. Tapi itu tidak menunjukkan a DialogFragment. Lihat stackoverflow.com/questions/15729138/… untuk solusi bagus lainnya, stackoverflow.com/a/41813953/2914140 membantu saya.
CoolMind
29

Ini Oktober 2017, dan Google membuat Perpustakaan Dukungan Android dengan hal-hal baru yang disebut komponen Siklus Hidup. Ini memberikan beberapa ide baru untuk masalah 'Tidak dapat melakukan tindakan ini setelah onSaveInstanceState'.

Pendeknya:

  • Gunakan komponen siklus hidup untuk menentukan apakah ini waktu yang tepat untuk memunculkan fragmen Anda.

Versi lebih panjang dengan jelaskan:

  • mengapa masalah ini muncul?

    Itu karena Anda mencoba menggunakan FragmentManagerdari aktivitas Anda (yang akan menyimpan fragmen Anda, saya kira?) Untuk melakukan transaksi untuk fragmen Anda. Biasanya ini akan terlihat seperti Anda mencoba melakukan transaksi untuk fragmen yang akan datang, sementara aktivitas host sudah memanggil savedInstanceStatemetode (pengguna mungkin kebetulan menyentuh tombol beranda sehingga aktivitas memanggil onStop(), dalam kasus saya itu alasannya)

    Biasanya masalah ini seharusnya tidak terjadi - kami selalu mencoba memuat fragmen ke dalam aktivitas di awal, seperti onCreate()metode adalah tempat yang sempurna untuk ini. Tetapi kadang-kadang ini bisa terjadi , terutama ketika Anda tidak dapat memutuskan fragmen apa yang akan Anda muat ke aktivitas itu, atau Anda mencoba memuat fragmen dari AsyncTaskblok (atau apa pun akan memakan waktu sedikit). Waktu, sebelum transaksi fragmen benar-benar terjadi, tetapi setelah metode aktivitas onCreate(), pengguna dapat melakukan apa saja. Jika pengguna menekan tombol beranda, yang memicu metode aktivitas onSavedInstanceState(), akan ada can not perform this actioncrash.

    Jika ada yang ingin melihat lebih dalam tentang masalah ini, saya sarankan mereka untuk melihat posting blog ini . Itu terlihat jauh di dalam lapisan kode sumber dan menjelaskan banyak tentang hal itu. Selain itu, ini memberikan alasan bahwa Anda tidak boleh menggunakan commitAllowingStateLoss()metode ini untuk menyelesaikan masalah ini (percayalah itu tidak menawarkan apa pun yang baik untuk kode Anda)

  • Bagaimana cara memperbaikinya?

    • Haruskah saya menggunakan commitAllowingStateLoss()metode untuk memuat fragmen? Tidak seharusnya Anda tidak ;

    • Haruskah saya mengganti onSaveInstanceStatemetode, abaikan supermetode di dalamnya? Tidak seharusnya Anda tidak ;

    • Haruskah saya menggunakan isFinishingaktivitas dalam magis , untuk memeriksa apakah aktivitas host pada saat yang tepat untuk transaksi fragmen? Ya ini sepertinya cara yang tepat untuk dilakukan.

  • Lihatlah apa yang dapat dilakukan komponen Siklus Hidup .

    Pada dasarnya, Google membuat beberapa implementasi di dalam AppCompatActivitykelas (dan beberapa kelas dasar lainnya yang harus Anda gunakan dalam proyek Anda), yang membuatnya lebih mudah untuk menentukan status siklus hidup saat ini . Lihatlah kembali masalah kita: mengapa masalah ini terjadi? Itu karena kita melakukan sesuatu pada waktu yang salah. Jadi kami berusaha untuk tidak melakukannya, dan masalah ini akan hilang.

    Saya sedikit memberi kode untuk proyek saya sendiri, ini yang saya lakukan LifeCycle. Saya kode di 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()
    }

Seperti yang saya tunjukkan di atas. Saya akan memeriksa status siklus aktivitas host. Dengan komponen Siklus Hidup dalam pustaka dukungan, ini bisa lebih spesifik. Kode tersebut lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)berarti, jika keadaan saat ini setidaknya onResume, tidak lebih lama dari itu? Yang memastikan metode saya tidak akan dieksekusi selama beberapa kondisi kehidupan lainnya (seperti onStop).

  • Apakah semuanya sudah selesai?

    Tentu saja tidak. Kode yang saya tunjukkan memberi tahu beberapa cara baru untuk mencegah aplikasi mogok. Tetapi jika itu pergi ke keadaan onStop, baris kode itu tidak akan melakukan hal-hal dan dengan demikian tidak menunjukkan apa pun di layar Anda. Ketika pengguna kembali ke aplikasi, mereka akan melihat layar kosong, itulah aktivitas host kosong yang tidak menunjukkan fragmen sama sekali. Ini pengalaman buruk (yeah sedikit lebih baik daripada crash).

    Jadi di sini saya berharap mungkin ada sesuatu yang lebih baik: aplikasi tidak akan crash jika datang ke keadaan hidup lebih lambat daripada onResume, metode transaksi sadar keadaan kehidupan; selain itu, aktivitas akan mencoba melanjutkan untuk menyelesaikan tindakan transaksi fragmen, setelah pengguna kembali ke aplikasi kami.

    Saya menambahkan sesuatu ke metode ini:

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

Saya menyimpan daftar di dalam dispatcherkelas ini , untuk menyimpan fragmen itu tidak memiliki kesempatan untuk menyelesaikan tindakan transaksi. Dan ketika pengguna kembali dari layar beranda dan menemukan masih ada fragmen yang menunggu untuk diluncurkan, itu akan pergi ke resume()metode di bawah @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)anotasi. Sekarang saya pikir itu harus berfungsi seperti yang saya harapkan.

Anthonyeef
sumber
8
Akan lebih baik menggunakan Java daripada Kotlin
Shchvova
1
Mengapa implementasi Anda FragmentDispatchermenggunakan daftar untuk menyimpan fragmen yang tertunda jika hanya akan ada satu fragmen yang dipulihkan?
fraherm
21

Berikut adalah solusi berbeda untuk masalah ini.

Menggunakan variabel anggota pribadi Anda dapat mengatur data yang dikembalikan sebagai maksud yang kemudian dapat diproses setelah super.onResume ();

Seperti itu:

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;
    }
}
Jed
sumber
7
Bergantung pada tindakan apa yang Anda lakukan yang tidak diizinkan, ini mungkin perlu pindah ke saat yang lebih lambat daripada diResume (). Untuk insatcne, jika FragmentTransaction.commit () adalah masalahnya, ini perlu masuk ke onPostResume () sebagai gantinya.
pjv
1
Ini adalah jawaban untuk pertanyaan ini bagi saya. Karena saya perlu meneruskan tag NFC yang diterima ke aktivitas sebelumnya, inilah yang melakukannya untuk saya.
Janis Peisenieks
7
Bagi saya itu terjadi karena saya tidak menelepon super.onActivityResult().
Sufian
20

Solusi singkat dan bekerja:

Ikuti Langkah Sederhana

Langkah

Langkah 1: Override onSaveInstanceStatestate di masing-masing fragmen. Dan hapus metode super dari itu.

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

Langkah 2: Gunakan fragmentTransaction.commitAllowingStateLoss( );

bukannya fragmentTransaction.commit( ); operasi fragmen sementara.

Vinayak
sumber
Jawaban tidak disalin atau direferensikan dari bentuk lain di mana. Saya diposting untuk membantu orang-orang dengan solusi kerja saya yang didapat dari beberapa percobaan dan kesalahan
Vinayak
12

WASPADALAH , menggunakan transaction.commitAllowingStateLoss()bisa berakibat pengalaman buruk bagi pengguna. Untuk informasi lebih lanjut tentang mengapa pengecualian ini dilemparkan, lihat posting ini .

Eric Brandwein
sumber
6
Ini tidak memberikan jawaban untuk pertanyaan, Anda harus memberikan jawaban yang valid untuk pertanyaan
Umar Ata
10

Saya menemukan solusi kotor untuk masalah seperti ini. Jika Anda masih ingin menyimpannya ActivityGroupsuntuk alasan apa pun (saya punya alasan pembatasan waktu), Anda tinggal menerapkan

public void onBackPressed() {}

di Anda Activitydan lakukan beberapa backkode di sana. bahkan jika tidak ada Metode seperti itu di Perangkat lama, Metode ini dipanggil oleh yang lebih baru.

mandau
sumber
6

Jangan gunakan commitAllowingStateLoss (), seharusnya hanya digunakan untuk kasus-kasus di mana tidak apa-apa bagi negara UI untuk mengubah tiba-tiba pada pengguna.

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

Jika transaksi terjadi di ChildFragmentManager dari parentFragment, gunakan parentFragment.isResume () luar untuk memeriksa.

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

Saya punya masalah yang sama, skenarionya seperti ini:

  • Aktivitas saya menambah / mengganti fragmen daftar.
  • Setiap fragmen daftar memiliki referensi ke aktivitas, untuk memberi tahu aktivitas ketika item daftar diklik (pola pengamat).
  • Setiap fragmen panggilan daftar setRetainInstance (true); dalam metode onCreate -nya .

The onCreate metode dari kegiatan seperti ini:

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

Pengecualian dilemparkan karena ketika konfigurasi berubah (perangkat diputar), aktivitas dibuat, fragmen utama diambil dari riwayat manajer fragmen dan pada saat yang sama fragmen sudah memiliki referensi TUA ke aktivitas yang dihancurkan.

mengubah implementasi untuk memecahkan masalah ini:

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

Anda perlu mengatur pendengar Anda setiap kali aktivitas dibuat untuk menghindari situasi di mana fragmen-fragmen tersebut memiliki referensi untuk contoh-contoh aktivitas yang lama hancur.

Mina Samy
sumber
5

Jika Anda mewarisi dari FragmentActivity, Anda harus memanggil superclass di onActivityResult():

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

Jika Anda tidak melakukan ini dan mencoba menampilkan kotak dialog fragmen dalam metode itu, Anda bisa mendapatkan OP IllegalStateException. (Sejujurnya, saya tidak begitu mengerti mengapa panggilan super memperbaiki masalah. onActivityResult()Disebut sebelumnya onResume(), jadi seharusnya masih tidak diizinkan untuk menampilkan kotak dialog fragmen.)

Lawrence Kesteloot
sumber
1
Ingin tahu mengapa ini memperbaiki masalah.
Big McLargeHuge
3

Saya mendapatkan pengecualian ini ketika saya menekan tombol kembali untuk membatalkan pemilih pemilih pada aktivitas fragmen peta saya. Saya menyelesaikan ini dengan mengganti kode onResume (di mana saya menginisialisasi fragmen) untuk mulai () dan aplikasi bekerja dengan baik. Semoga membantu.

DCS
sumber
2

Saya pikir menggunakan transaction.commitAllowingStateLoss();bukanlah solusi terbaik. Pengecualian ini akan dibuang ketika konfigurasi aktivitas berubah dan terpecah-pecahonSavedInstanceState() dipanggil dan kemudian metode panggilan balik async Anda mencoba melakukan fragmen.

Solusi sederhana dapat memeriksa apakah aktivitas mengubah konfigurasi atau tidak

mis. periksa isChangingConfigurations()

yaitu

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

Lihat tautan ini juga

Amol Desai
sumber
Entah bagaimana saya mendapatkan pengecualian ini ketika pengguna mengklik sesuatu (klik adalah pemicu untuk melakukan komit transaksi). Bagaimana ini bisa terjadi? Apakah solusi Anda di sini di sini?
pengembang android
@androiddeveloper apa lagi yang Anda lakukan pada klik pengguna. entah bagaimana fragmen menyelamatkan negaranya sebelum Anda melakukan transaksi
Amol Desai
Pengecualian diberikan pada garis persis komit transaksi. Juga, saya memiliki kesalahan ketik yang aneh: alih-alih "di sini di sini" yang saya maksudkan "bekerja di sini".
pengembang android
@androiddeveloper Anda benar! tetapi sebelum melakukan transaksi, apakah Anda menelurkan utas latar belakang atau sesuatu?
Amol Desai
Saya kira tidak (maaf saya di luar kantor), tetapi mengapa itu penting? Ini semua barang UI di sini ... Jika saya pernah membuat sesuatu di utas latar belakang, saya akan memiliki pengecualian di sana, ditambah saya tidak menempatkan barang-barang terkait UI di utas latar belakang, karena terlalu berisiko.
pengembang android
2

Mungkin solusi paling halus dan paling sederhana yang saya temukan dalam kasus saya adalah untuk menghindari mengeluarkan fragmen yang menyinggung dari tumpukan sebagai respons terhadap hasil aktivitas. Jadi mengubah panggilan ini di onActivityResult():

popMyFragmentAndMoveOn();

untuk ini:

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

membantu dalam kasus saya.

Mojuba
sumber
2

Jika Anda melakukan beberapa FragmentTransaction di onActivityResult apa yang dapat Anda lakukan, Anda dapat menetapkan beberapa nilai boolean di dalam onActivityResult kemudian di onResume Anda dapat melakukan Transaksi FragmentTransaction Anda berdasarkan nilai boolean. Silakan merujuk kode di bawah ini.

@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;
    }
}
anoopbryan2
sumber
Tolong jangan memposting kode sebagai gambar, gunakan format kode sebagai gantinya
Micer
1
Ya itu membantu saya .., Ini solusi tepat dan tepat untuk perangkat marshmallow saya mendapatkan pengecualian ini dan diselesaikan dengan trik sederhana ini .. !! Oleh karena itu terpilih.
sandhya sasane
2

Courtesy: Solusi untuk IllegalStateException

Masalah ini telah mengganggu saya untuk banyak waktu tetapi untungnya saya datang dengan solusi konkret untuk itu. Penjelasan rinci tentang hal itu ada di sini .

Menggunakan commitAllowStateloss () dapat mencegah pengecualian ini tetapi akan menyebabkan penyimpangan UI. Sejauh ini kami telah memahami bahwa IllegalStateException ditemui ketika kami mencoba untuk melakukan fragmen setelah keadaan Aktivitas hilang - jadi kami hanya harus menunda transaksi sampai keadaan dipulihkan . Ini bisa dilakukan seperti ini

Deklarasikan dua variabel boolean pribadi

 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;

Sekarang di onPostResume () dan onPause kita mengatur dan menghapus variabel boolean kita adalahTransactionSafe. Ide adalah untuk menandai transaksi aman hanya ketika aktivitas berada di latar depan sehingga tidak ada kemungkinan 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();
    }
}

-Apa yang telah kami lakukan sejauh ini akan menghemat dari IllegalStateException tetapi transaksi kami akan hilang jika mereka dilakukan setelah aktivitas bergerak ke latar belakang, seperti commitAllowStateloss (). Untuk membantu dengan itu kita memiliki variabel 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;
 }
}
IrshadKumail
sumber
2

Transaksi fragmen tidak boleh dieksekusi setelah Activity.onStop()! Pastikan Anda tidak memiliki panggilan balik apa pun yang dapat melakukan transaksi setelahnya onStop(). Lebih baik untuk memperbaiki alasan daripada mencoba untuk mengatasi masalah dengan pendekatan seperti.commitAllowingStateLoss()

Ivo Stoyanov
sumber
1

Mulai dari pustaka dukungan versi 24.0.0 Anda dapat memanggil FragmentTransaction.commitNow()metode yang melakukan transaksi ini secara sinkron alih-alih panggilan commit()diikuti oleh executePendingTransactions(). Seperti dokumentasi mengatakan pendekatan ini lebih baik:

Memanggil commitNow lebih disukai daripada memanggil commit () diikuti oleh executePendingTransactions () karena yang terakhir akan memiliki efek samping dari upaya untuk melakukan semua transaksi yang tertunda saat ini apakah itu perilaku yang diinginkan atau tidak.

Volodymyr
sumber
1

Setiap kali Anda mencoba memuat fragmen dalam aktivitas Anda, pastikan bahwa aktivitas sedang dalam resume dan tidak akan berhenti sementara. Dalam keadaan jeda, Anda mungkin akhirnya kehilangan operasi komit yang dilakukan.

Anda dapat menggunakan transaction.commitAllowingStateLoss () alih-alih transaction.commit () untuk memuat fragmen

atau

Buat boolean dan periksa apakah aktivitas tidak akan berhenti

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

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

lalu saat memuat cek fragmen

if(mIsResumed){
//load the your fragment
}
Sharath kumar
sumber
1

Untuk menghindari masalah ini, kita dapat menggunakan Komponen Arsitektur Navigasi , yang diperkenalkan di Google I / O 2018. Komponen Arsitektur Navigasi menyederhanakan implementasi navigasi di aplikasi Android.

Levon Petrosyan
sumber
Itu tidak cukup baik, dan bugy (penanganan deeplink yang buruk, tidak dapat menyimpan fragmen show / hide negara dan beberapa masalah penting yang masih terbuka)
do01
1

Sehubungan dengan jawaban bagus @Anthonyeef, berikut adalah contoh kode di Jawa:

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

Jika Anda mengalami crash dengan metode popBackStack () atau popBackStackImmediate (), silakan coba perbaiki dengan:

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

Ini bekerja untuk saya juga.

Tapa Hemat
sumber
Perhatikan bahwa itu membutuhkan API 26 dan di atasnya
Itay Feldman
1

Dalam kasus saya, saya mendapatkan kesalahan ini dalam metode override bernama onActivityResult. Setelah menggali saya baru tahu mungkin saya perlu memanggil ' super ' sebelumnya.
Saya menambahkannya dan berhasil

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

}

Mungkin Anda hanya perlu menambahkan 'super' pada setiap override yang Anda lakukan sebelum kode Anda.

Gian Gomen
sumber
1

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

Pemakaian:

val fragment = { SomeFragment.newInstance(data) }
fragmentManager?.replaceAndAddToBackStack(R.id.container, fragment, SomeFragment.TAG)
CoolMind
sumber
Anda dapat menghapus () -> sebelum Fragment
Claire
@Claire, terima kasih! Apakah maksud Anda berubah menjadi fragment: Fragment? Ya, saya mencoba varian ini, tetapi dalam kasus ini sebuah fragmen akan dibuat dalam semua kasus (bahkan ketika fragmentManager == null, tetapi saya tidak menemukan situasi ini). Saya memperbarui jawabannya dan mengubah nol untuk memberi tag addToBackStack().
CoolMind
0

Tambahkan ini dalam aktivitas Anda

@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);
    }
}
yifan
sumber
0

Saya juga mengalami masalah ini dan masalah terjadi setiap kali ketika konteks Anda FragmentActivityberubah (mis. Orientasi layar diubah, dll.). Jadi perbaikan terbaik untuk itu adalah memperbarui konteks dari blog Anda FragmentActivity.

Adam
sumber
0

Saya berakhir dengan membuat fragmen basis dan membuat semua fragmen di aplikasi saya memperluasnya

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

Lalu ketika saya mencoba menunjukkan sebuah fragmen, saya menggunakan showAllowingStateLosssebagai gantinyashow

seperti ini:

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

Saya menemukan solusi ini dari PR ini: https://github.com/googlesamples/easypermissions/pull/170/files

Ahmad El-Melegy
sumber
0

Solusi lain yang mungkin, yang saya tidak yakin jika membantu dalam semua kasus (asal di sini ):

@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();
        }
    }
}
pengembang android
sumber
0

Saya tahu ada jawaban yang diterima oleh @Ovidiu Latcu tetapi setelah beberapa saat, kesalahan masih berlanjut.

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

Crashlytics masih mengirim saya pesan kesalahan aneh ini.

Namun kesalahan sekarang hanya terjadi pada versi 7+ (Nougat) Perbaikan saya adalah menggunakan commitAllowingStateLoss () alih-alih komit () di fragmentTransaction.

Posting ini ini bermanfaat untuk commitAllowingStateLoss () dan tidak pernah punya masalah fragmen lagi.

Singkatnya, jawaban yang diterima di sini mungkin bekerja pada versi Android pra Nougat.

Ini mungkin menghemat seseorang beberapa jam mencari. kode senang <3 sorakan

ralphgabb
sumber