3
votes

I am trying to figure out why my android app crashes sometimes, with the above exception. I can't find the source of the problem from what the logcat says.

It happens rarely.

Logcat output:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1360) at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1378) 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.FragmentTabHost.onAttachedToWindow(FragmentTabHost.java:282) at android.view.View.dispatchAttachedToWindow(View.java:12752) at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:2577) at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:2584) at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:2584) at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:2584) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1427) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1192) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6231) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:788) at android.view.Choreographer.doCallbacks(Choreographer.java:591) at android.view.Choreographer.doFrame(Choreographer.java:560) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:774) at android.os.Handler.handleCallback(Handler.java:808) at android.os.Handler.dispatchMessage(Handler.java:103) at android.os.Looper.loop(Looper.java:193) at android.app.ActivityThread.main(ActivityThread.java:5292) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:824) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:640) at dalvik.system.NativeStart.main(Native Method)

Main Activity:

public class MainActivity extends FragmentActivity {

    private FragmentTabHost mTabHost;    
    private UiLifecycleHelper uiHelper;

    LocationClient mClient;
    LocationRequest mRequest;
    private String user;
    private String userId;
    private ImageButton addPlaceBtn;
    private SQLiteDataSource datasource;
    private TextView notifTextView;
    private boolean appIsOn = false; 

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActionBar actionBar = getActionBar();
        actionBar.setDisplayShowHomeEnabled(true);
        actionBar.setDisplayShowTitleEnabled(false);
        setContentView(R.layout.activity_main);

        datasource = new SQLiteDataSource(this);
        datasource.open();

        uiHelper = new UiLifecycleHelper(this, null);
        uiHelper.onCreate(savedInstanceState);

        if (isFacebookLoggedIn()) {
            System.out.println("We are logged in!");
        } else {
            showLogin();
        }

        if (datasource.getAllImageItems().isEmpty()) {
            Intent splash = new Intent(this, SplashActivity.class);
            startActivity(splash);
        }

        addPlaceBtn = (ImageButton) findViewById(R.id.addPlace1);

        uiHelper = new UiLifecycleHelper(this, null);
        uiHelper.onCreate(savedInstanceState);

        mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
        mTabHost.setup(this, getSupportFragmentManager(),
                android.R.id.tabcontent);

        mTabHost.addTab(
                mTabHost.newTabSpec("tab1").setIndicator("PREPARATE", null),
                OffersFragmentActivity.class, null);
        mTabHost.addTab(
                mTabHost.newTabSpec("tab2").setIndicator("CATEGORII", null),
                CategoriesActivity.class, null);
        mTabHost.addTab(mTabHost.newTabSpec("tab3")
                .setIndicator("CAIETE", null), BooksFragmentActivity.class,
                null);

        for (int i = 0; i < mTabHost.getTabWidget().getChildCount(); i++) {
            mTabHost.getTabWidget().getChildAt(i)
                    .setBackgroundResource(R.drawable.tab_text_selector);
            mTabHost.getTabWidget().setDividerDrawable(R.drawable.divider);
            final TextView tv = (TextView) mTabHost.getTabWidget()
                    .getChildAt(i).findViewById(android.R.id.title);
            if (tv == null)
                continue;
            else
                tv.setTextColor(0xFFFFFFFF);
        }

        mTabHost.setOnTabChangedListener(new OnTabChangeListener() {

            public void onTabChanged(String tabId) {

                switch (mTabHost.getCurrentTab()) {
                case 0:
                    addPlaceBtn.setVisibility(View.VISIBLE);
                    break;
                case 1:
                    addPlaceBtn.setVisibility(View.GONE);
                    break;
                case 2:
                    addPlaceBtn.setVisibility(View.GONE);
                    break;

                default:

                    break;
                }
            }
        });

        addPlaceBtn.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (user != null && isOnline()) {
                    Intent i = new Intent(getApplication(),
                            CameraActivity.class);
                    startActivity(i);
                } else if (user == null) {
                    // If user is not logged in, start login activity
                    Intent i = new Intent(getApplication(), LoginActivity.class);
                    startActivity(i);
                } else if (!isOnline()) {
                    AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(
                            MainActivity.this);
                    alertDialogBuilder.setTitle("Internetul este oprit");
                    alertDialogBuilder
                            .setMessage("Pentru actualizarea continutului aveti nevoie de internet. Porniti acum?");
                    alertDialogBuilder.setPositiveButton("Da",
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog,
                                        int id) {
                                    // go to a new activity of the app
                                    Intent settingsIntent = new Intent(
                                            Settings.ACTION_DATA_ROAMING_SETTINGS);
                                    settingsIntent
                                            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                    startActivity(settingsIntent);
                                    finish();
                                }
                            });
                    // set negative button: No message
                    alertDialogBuilder.setNegativeButton("Nu",
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog,
                                        int id) {
                                    // cancel the alert box and put a Toast to
                                    // the user
                                    finish();
                                }
                            });

                    AlertDialog alertDialog = alertDialogBuilder.create();
                    alertDialog.show();
                }
            }
        });

        if(!(Thread.getDefaultUncaughtExceptionHandler() instanceof CustomExceptionHandler)) {
            Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler(this));
        }

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);        
        // Associate searchable configuration with the SearchView
        SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
        SearchView searchView = (SearchView) menu.findItem(R.id.search)
                .getActionView();
        searchView.setSearchableInfo(searchManager
                .getSearchableInfo(getComponentName()));

        final Menu m = menu;
        final MenuItem item = menu.findItem(R.id.action_notifications);
        View actionView = item.getActionView();
        notifTextView = (TextView) actionView.findViewById(R.id.notifTextView);

        String unreadNotifs = datasource.countUnreadNotif();
        if (!unreadNotifs.equals("0")) {
            notifTextView.setText(unreadNotifs);
        } else {
            notifTextView.setVisibility(View.GONE);
        }

        item.getActionView().setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                m.performIdentifierAction(item.getItemId(), 0);

            }
        });

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_user) {
            Intent i = new Intent(this, UserProfile.class);
            i.putExtra("user", user);
            i.putExtra("userId", userId);
            startActivity(i);

            return true;
        }

        if (id == R.id.action_notifications) {
            Intent i = new Intent(this, Notifications.class);
            startActivity(i);

            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    private boolean isFacebookLoggedIn() {
        Session session = Session.getActiveSession();

        if (session != null) {
            // Session can be open, check for valid token
            if (!session.isClosed()) {
                if (!session.getAccessToken().equalsIgnoreCase("")) {
                    return true;
                }
            }
        }
        return false;
    }

    // FB Login Session and user info
    public void showLogin() {
        Intent i = new Intent(this, LoginActivity.class);
        startActivity(i);

    }

    @Override
    protected void onResume() {
        super.onResume();
        uiHelper.onResume();
        appIsOn = true;
        invalidateOptionsMenu();

        if (!isOnline()) {
            showGpsButton();
        }

        SharedPreferences preferences = getSharedPreferences("USERINFO",
                Context.MODE_PRIVATE);
        user = preferences.getString("name", null);
        userId = preferences.getString("userId", null);
        userId = preferences.getString("userId", null);

        GCMActivity gcm = new GCMActivity(this);
        gcm.registerIfNeeded();

    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        uiHelper.onSaveInstanceState(outState);
    }

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

    @Override
    public void onDestroy() {
        super.onDestroy();
        uiHelper.onDestroy();
        datasource.deleteAllRows();

    }

    public void showGpsButton() {
        LinearLayout buttonLayout = (LinearLayout) findViewById(R.id.gpsWarning);
        buttonLayout.setVisibility(View.VISIBLE);
        buttonLayout.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                Intent settingsIntent = new Intent(
                        Settings.ACTION_DATA_ROAMING_SETTINGS);
                settingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(settingsIntent);
                finish();
            }
        });
    }

    public boolean isOnline() {
        ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo netInfo = cm.getActiveNetworkInfo();
        if (netInfo != null && netInfo.isConnected()) {
            return true;
        }
        return false;
    }
}
1
Use .commitAllowingStateLoss() instead of Commit() while using fragments.Ganesh AB
I did some research, read other posts and the blogpost of @Alex Lockwood on this topic. But I have only a Fragment TabHost in my activity and I never call "commit()". Usually I receive this error right after another crash if there is any in my app.Edmond Tamas
@EdmondTamas If you aren't calling commit(), the framework certainly is. The proof is in the stack trace: android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574) (BackStackRecord is the framework's internal implementation of the abstract FragmentTransaction class).Alex Lockwood
@AlexLockwood thanks for your support. Do you think there is a way to get around this? Could I somehow override this behaviour?Edmond Tamas

1 Answers

1
votes

I was experiencing a similar issue on KitKat. But on Lollipop it seems to work fine. Regardless, we have to support many OS versions, right? :)

The primary cause seems to be that the starting of a new activity is forcing the MainActivity to pause before it's even completely finished creating. When it pauses, it saves the instance state right away. But since Fragment instances get committed in a weird end-of-main-thread cycle, that start-pause-save cycle becomes out of order throwing this crash. And in some cases, it could be a race condition and happen sporadically.

Here's some code that seems to fix it after repeated testing:

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

    Handler hanlder = new Handler();
    hanlder.post(new Runnable() {
        @Override
        public void run() {
            if (isFacebookLoggedIn()) {
                System.out.println("We are logged in!");
            } else {
                showLogin();
            }

            if (datasource.getAllImageItems().isEmpty()) {
                Intent splash = new Intent(MainActivity.this, SplashActivity.class);
                startActivity(splash);
            }
        }
    });
}

The trick is to spawn a Handler and post to the end of the main thread to guarantee it will happen after all Fragment instances have been committed. Doing it in onPostCreate instead of immediately in onCreate should help the situation as well.