android - IllegalStateException: ViewPager के साथ SaveInstanceState पर इस क्रिया को निष्पादित नहीं कर सकता




android-fragments android-viewpager (14)

CommitAllowingStateLoss () का उपयोग न करें, इसे केवल उन मामलों के लिए उपयोग किया जाना चाहिए जहां उपयोगकर्ता के लिए यूआई राज्य अप्रत्याशित रूप से बदलना ठीक है।

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

इसके बजाए, अगर आप इस IllegalStateException से मिले ऑपरेशन के बाहर (fragment.isResume ()) जांचें "SaveInstanceState पर इस क्रिया को निष्पादित नहीं कर सकता"

मुझे बाजार में अपने ऐप से उपयोगकर्ता रिपोर्ट मिल रही है, निम्नलिखित अपवाद प्रदान करना:

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)

जाहिर है, इसमें फ्रैगमेंट मैनेजर के साथ कुछ करना है, जिसका मैं उपयोग नहीं करता हूं। स्टैकट्रैक मेरे किसी भी वर्ग को नहीं दिखाता है, इसलिए मुझे नहीं पता कि यह अपवाद कहां होता है और इसे कैसे रोकें।

रिकॉर्ड के लिए: मेरे पास एक टैबहोस्ट है, और प्रत्येक टैब में गतिविधियां के बीच एक गतिविधि समूह स्विचिंग है।


अपवाद यहां फेंक दिया गया है (FragmentActivity में):

@Override
public void onBackPressed() {
    if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
        super.onBackPressed();
    }
}

FragmentManager.popBackStatckImmediate() , FragmentManager.checkStateLoss() को पहली बार कहा जाता है। यह IllegalStateException का कारण है। नीचे कार्यान्वयन देखें:

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

मैं गतिविधि की वर्तमान स्थिति को चिह्नित करने के लिए केवल ध्वज का उपयोग करके इस समस्या को हल करता हूं। मेरा समाधान यहाँ है:

public class MainActivity extends AppCompatActivity {
    /**
     * A flag that marks whether current Activity has saved its instance state
     */
    private boolean mHasSaveInstanceState;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

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

    @Override
    protected void onResume() {
        super.onResume();
        mHasSaveInstanceState = false;
    }

    @Override
    public void onBackPressed() {
        if (!mHasSaveInstanceState) {
            // avoid FragmentManager.checkStateLoss()'s throwing IllegalStateException
            super.onBackPressed();
        }
    }

}


इसे अपनी गतिविधि में जोड़ें

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

एक और संभावित कामकाज, जो मुझे यकीन नहीं है कि अगर सभी मामलों में मदद करता है ( here मूल):

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

कृपया मेरा जवाब here । असल में मुझे बस इतना करना पड़ा:

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

saveInstanceState विधि पर super() को कॉल न करें। यह चीजों को गड़बड़ कर रहा था ...

यह समर्थन पैकेज में एक ज्ञात bug है।

यदि आपको इंस्टेंस को सहेजने और अपने outState Bundle कुछ जोड़ने की आवश्यकता है तो आप निम्न का उपयोग कर सकते हैं:

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

अंत में उचित समाधान (टिप्पणियों में देखा गया) का उपयोग करने के लिए किया गया था:

transaction.commitAllowingStateLoss();

जब FragmentTransaction जोड़ या निष्पादन कर रहा था जो Exception पैदा कर रहा था।


जब भी आप अपनी गतिविधि में एक टुकड़ा लोड करने की कोशिश कर रहे हैं, सुनिश्चित करें कि गतिविधि फिर से शुरू हो रही है और राज्य को रोकने के लिए नहीं जा रहा है। विराम स्थिति में आप प्रतिबद्ध ऑपरेशन खोने को समाप्त कर सकते हैं।

आप fraction.commitAllowingStateLoss () के बजाय fraction.commit () को खंड लोड करने के लिए उपयोग कर सकते हैं

या

एक बूलियन बनाएं और जांचें कि गतिविधि चालू नहीं है या नहीं

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

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

फिर खंड जांच लोड करते समय

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

मुझे एक ही समस्या थी, परिदृश्य इस तरह था:

  • मेरी गतिविधि सूची टुकड़े जोड़ / बदल रही है।
  • जब सूची सूची पर क्लिक किया जाता है तो गतिविधि सूची को सूचित करने के लिए प्रत्येक सूची खंड में गतिविधि का संदर्भ होता है (पर्यवेक्षक पैटर्न)।
  • प्रत्येक सूची खंड सेटRetainInstance (सत्य) कहते हैं; इसकी ऑन्रेट विधि में।

गतिविधि की ऑनक्रेट विधि इस तरह थी:

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

अपवाद फेंक दिया गया था क्योंकि जब कॉन्फ़िगरेशन बदलता है (डिवाइस घुमाया जाता है), गतिविधि बनाई जाती है, मुख्य टुकड़ा खंड प्रबंधक के इतिहास से पुनर्प्राप्त किया जाता है और साथ ही खंड में पहले से ही नष्ट गतिविधि के लिए पुराना संदर्भ होता है

इस हल समस्या को कार्यान्वित करने में परिवर्तन:

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

जब आप टुकड़ों के गतिविधि के पुराने नष्ट उदाहरणों के संदर्भ में हैं, तो स्थिति से बचने के लिए आपको हर बार गतिविधि सुनने के लिए अपने श्रोताओं को सेट करने की आवश्यकता होती है।


मुझे यह अपवाद मिल रहा था जब मैं अपने मानचित्र खंड गतिविधि पर इरादा चयनकर्ता को रद्द करने के लिए वापस बटन दबा रहा था। मैंने onResume (जहां मैं टुकड़ा शुरू कर रहा था) के कोड को चालू करने के लिए इसे हल किया () और ऐप ठीक काम कर रहा है। उम्मीद है कि यह मदद करता है।


मैं आधार टुकड़ा बनाने के साथ समाप्त हुआ और मेरे ऐप में सभी टुकड़े इसे बढ़ाते हैं

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

फिर जब मैं एक टुकड़ा दिखाने की कोशिश करता हूं तो मैं शो के बजाए showAllowingStateLoss उपयोग करता showAllowingStateLoss

इस तरह:

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

मैं इस पीआर से इस समाधान तक आया: https://github.com/googlesamples/easypermissions/pull/170/files


मैंने इस मुद्दे का भी अनुभव किया है और समस्या तब होती है जब आपकी FragmentActivity का संदर्भ बदल जाता है (उदाहरण के लिए स्क्रीन अभिविन्यास बदल जाता है, आदि)। तो इसके लिए सबसे अच्छा फिक्स आपके FragmentActivity से संदर्भ अद्यतन करना है।


यह अक्टूबर 2017 है, और Google नई चीजों के साथ एंड्रॉइड सपोर्ट लाइब्रेरी बनाता है जिसे जीवन चक्र घटक कहते हैं। यह 'SaveInstanceState' समस्या के बाद इस क्रिया को निष्पादित नहीं कर सकता है इसके लिए कुछ नया विचार प्रदान करता है।

संक्षेप में:

  • यह निर्धारित करने के लिए कि क्या यह आपके टुकड़े को पॉप अप करने का सही समय है, जीवन चक्र घटक का प्रयोग करें।

व्याख्या के साथ लंबा संस्करण:

  • यह समस्या क्यों आती है?

    ऐसा इसलिए है क्योंकि आप अपने गतिविधि से FragmentManager का उपयोग करने की कोशिश कर रहे हैं (जो आपके टुकड़े को पकड़ने वाला है?) आपको टुकड़े के लिए लेनदेन करने के लिए। आम तौर पर ऐसा लगता है कि आप आने वाले टुकड़े के लिए कुछ लेन-देन करने की कोशिश कर रहे हैं, इस बीच मेजबान गतिविधि पहले से ही onStop() विधि (उपयोगकर्ता होम बटन को छूने के लिए हो सकता है ताकि गतिविधि onStop() , मेरे मामले में यह कारण है)

    आम तौर पर यह समस्या नहीं होनी चाहिए - हम हमेशा शुरुआत में गतिविधि में टुकड़े को लोड करने का प्रयास करते हैं, जैसे onCreate() विधि इसके लिए एक आदर्श स्थान है। लेकिन कभी-कभी ऐसा होता है , खासकर जब आप यह तय नहीं कर सकते कि आप उस गतिविधि में किस टुकड़े को लोड करेंगे, या आप AsyncTask ब्लॉक से खंड लोड करने की कोशिश कर रहे हैं (या कुछ भी समय लगेगा)। समय, खंड लेनदेन वास्तव में होता है, लेकिन गतिविधि के बाद onCreate() विधि के बाद, उपयोगकर्ता कुछ भी कर सकता है। यदि उपयोगकर्ता होम बटन दबाता है, जो गतिविधि की onSavedInstanceState() विधि को ट्रिगर करता है, तो can not perform this action क्रैश can not perform this action सकता है।

    अगर कोई इस मुद्दे में गहराई से देखना चाहता है, तो मैं सुझाव देता हूं कि वे इस ब्लॉग androiddesignpatterns.com/2013/08/… । यह स्रोत कोड परत के अंदर गहराई से दिखता है और इसके बारे में बहुत कुछ समझाता है। साथ ही, यह कारण बताता है कि आपको इस क्रैश को हल करने के लिए commitAllowingStateLoss() विधि का उपयोग नहीं करना चाहिए (मेरा विश्वास करें कि यह आपके कोड के लिए कुछ भी अच्छा नहीं है)

  • इसे कैसे ठीक करें?

    • क्या मुझे commitAllowingStateLoss() विधि का उपयोग टुकड़ा लोड करने के लिए करना चाहिए? नहीं, आपको नहीं करना चाहिए ;

    • क्या मुझे onSaveInstanceState विधि पर ओवरराइड करना चाहिए, इसके अंदर super विधि को अनदेखा करना चाहिए? नहीं, आपको नहीं करना चाहिए ;

    • क्या मुझे isFinishing गतिविधि का उपयोग करना चाहिए, यह जांचने के लिए कि मेजबान गतिविधि खंड के लेनदेन के लिए सही समय पर है या नहीं? हाँ यह करने के लिए सही तरीका की तरह दिखता है।

  • Lifecycle घटक क्या कर सकता है पर एक नज़र डालें।

    असल में, Google AppCompatActivity क्लास (और कई अन्य बेस क्लास जो आपको अपने प्रोजेक्ट में उपयोग करना चाहिए) के अंदर कुछ कार्यान्वयन करता है, जो वर्तमान जीवन चक्र स्थिति को निर्धारित करना आसान बनाता है। हमारी समस्या पर नज़र डालें: यह समस्या क्यों होगी? ऐसा इसलिए है क्योंकि हम गलत समय पर कुछ करते हैं। तो हम इसे करने की कोशिश नहीं करते हैं, और यह समस्या चली जाएगी।

    मैं अपने स्वयं के प्रोजेक्ट के लिए थोड़ा कोड करता हूं, LifeCycle का उपयोग करके मैं यही करता LifeCycle । मैं कोटलिन में कोड।

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

जैसा कि मैंने ऊपर दिखाया है। मैं मेजबान गतिविधि की जीवन चक्र स्थिति की जांच करूंगा। समर्थन पुस्तकालय के भीतर जीवन चक्र घटक के साथ, यह और अधिक विशिष्ट हो सकता है। कोड lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED) अर्थ है, यदि वर्तमान स्थिति कम से कम onResume , तो इसके बाद नहीं? जो सुनिश्चित करता है कि मेरी विधि किसी अन्य जीवन स्थिति (जैसे onStop ) के दौरान निष्पादित नहीं की जाएगी।

  • क्या यह सब किया गया है?

    बिलकूल नही। मैंने जो कोड दिखाया है उसे एप्लिकेशन को क्रैश होने से रोकने के लिए कुछ नया तरीका बताता है। लेकिन अगर यह onStop की स्थिति में onStop , तो कोड की वह पंक्ति चीजें नहीं onStop और इस प्रकार आपकी स्क्रीन पर कुछ भी नहीं दिखाएगी। जब उपयोगकर्ता एप्लिकेशन पर वापस आते हैं, तो उन्हें एक खाली स्क्रीन दिखाई देगी, यह खाली मेजबान गतिविधि है जिसमें कोई टुकड़ा नहीं दिखता है। यह बुरा अनुभव है (हाँ एक दुर्घटना से थोड़ा बेहतर)।

    तो यहां मैं चाहता हूं कि कुछ अच्छा हो सकता है: अगर onResume बाद जीवन की स्थिति आती है तो ऐप क्रैश नहीं होगा, लेनदेन विधि जीवन की स्थिति जागरूक है; इसके अलावा, उपयोगकर्ता हमारे ऐप पर वापस आने के बाद, उस खंड लेनदेन कार्रवाई को समाप्त करने का प्रयास करेगा।

    मैं इस विधि में कुछ और जोड़ता हूं:

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

मैं इस dispatcher वर्ग के अंदर एक सूची बनाए रखता हूं, उन टुकड़ों को स्टोर करने के लिए लेनदेन की कार्रवाई को पूरा करने का मौका नहीं है। और जब उपयोगकर्ता होम स्क्रीन से वापस आते हैं और पाया कि अभी भी खंडन लॉन्च होने की प्रतीक्षा है, तो यह @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) एनोटेशन के तहत resume() से resume() विधि पर @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) । अब मुझे लगता है कि यह काम करना चाहिए जैसा कि मैंने उम्मीद की थी।


यह जांचें कि क्या गतिविधि isFinishing() को टुकड़े दिखाने से पहले isFinishing() और commitAllowingStateLoss() करने पर ध्यान दें commitAllowingStateLoss()

उदाहरण:

if(!isFinishing()) {
FragmentManager fm = getSupportFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            DummyFragment dummyFragment = DummyFragment.newInstance();
            ft.add(R.id.dummy_fragment_layout, dummyFragment);
            ft.commitAllowingStateLoss();
}

समर्थन लाइब्रेरी संस्करण 24.0.0 से शुरू करना आप FragmentTransaction.commitNow() विधि को कॉल कर सकते हैं जो commit() को executePendingTransactions() commit() बजाय सिंक्रनाइज़ रूप से इस लेनदेन को करता है जिसके बाद executePendingTransactions()documentation रूप में यह दृष्टिकोण भी बेहतर कहता है:

कॉलिंग प्रतिबद्ध अब प्रतिबद्ध () को कॉल करने के लिए बेहतर है () निष्पादित करने के बाद निष्पादन () के रूप में बाद वाले सभी मौजूदा लंबित लेनदेन करने का प्रयास करने का दुष्प्रभाव होगा चाहे वह वांछित व्यवहार हो या नहीं।


लघु और कामकाजी समाधान:

सरल चरणों का पालन करें

कदम

चरण 1: संबंधित खंड में onSaveInstanceState स्थिति को ओवरराइड करें। और इससे सुपर विधि हटा दें।

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

चरण 2: fragmentTransaction.commitAllowingStateLoss( ); उपयोग करें fragmentTransaction.commitAllowingStateLoss( );

fragmentTransaction.commit( ); बजाय fragmentTransaction.commit( ); जबकि खंड संचालन।





android-viewpager