当 activity 从后台到前台时,重用片段会导致 IllegalArgumentException

when activity is background to foreground, reuse fragment cause IllegalArgumentException

我的activity使用预初始化的片段(初始化onCreate)

当片段 (A) 首先显示时,使用 FragmentTransaction.add(A)。 如果片段 B 请求显示(如果它是第一个),FragmentTransaction.detach(A)FragmentTransaction.add(B).

片段A再次请求显示,使用FragmentTransaction.detach(B)FragmentTransaction.attach(A).

此操作在 BottomNavagationView.OnNavigationItemSelectedListener.

在这种情况下,我使应用程序完成(使用后退按钮,但不使用 activity.finish),然后再次 运行 该应用程序,不显示任何片段(期望显示片段 B ).

并且,当我单击 BottomNavagationView 按钮(片段添加)时,

原因java.lang.IllegalStateException:在 onSaveInstanceState at commit() 后无法执行此操作。

如何解决这个问题?

commitallowingstateloss() 似乎无法正常工作...

附上我的代码:

public class TestActivity extends AppCompatActivity {

private TextView mTextMessage;
private BottomNavigationView navigation;

private Fragment homeFragment     = null;
private Fragment seatFragment     = null;
private Fragment settingFragment  = null;
private Fragment dialogFragment   = null;

private FragmentUtil fUtil = null;

private boolean finishFlag = false;

private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
        = new BottomNavigationView.OnNavigationItemSelectedListener() {

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case R.id.navigation_home:

                    fUtil.addOnMain(R.id.content, homeFragment, "home", FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                return true;
            case R.id.navigation_seat:

                    fUtil.addOnMain(R.id.content, seatFragment, "seat", FragmentTransaction.TRANSIT_FRAGMENT_FADE);

                return true;
            case R.id.navigation_shelf:

                return true;
            case R.id.navigation_settings:

                    fUtil.addOnMain(R.id.content, settingFragment, "setting", FragmentTransaction.TRANSIT_FRAGMENT_FADE);

                return true;
        }
        return false;
    }

};

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

   // mTextMessage = (TextView) findViewById(R.id.message);

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
    navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
    BottomNavigationViewHelper.disableShiftMode(navigation);

    fUtil = FragmentUtil.getInstance(this);

    homeFragment = new HomeFragment();
    seatFragment = new SeatMenuFragment();
    settingFragment = new SettingFragment();
    dialogFragment = new DialogFragment();

    findViewById(R.id.login_popup).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if(fUtil.isContained(dialogFragment)) {
                return;
            } else {
                fUtil.add(R.id.content, dialogFragment, "", FragmentTransaction.TRANSIT_FRAGMENT_FADE);
            }
        }
    });
}

和 FragmentUtil:

public class FragmentUtil {
private Context context = null;
private FragmentManager manager = null;
private static FragmentUtil sInstance = null;
private Fragment currentFragment = null;

public static FragmentUtil getInstance(FragmentActivity activity) {
    if (sInstance == null) {
        sInstance = new FragmentUtil(activity);
    }

    return sInstance;
}
private FragmentUtil(FragmentActivity activity) {
    this.context = activity.getApplicationContext();
    this.manager = activity.getSupportFragmentManager();
}

public FragmentManager getManager() {
    return manager;
}

private void setTransition(FragmentTransaction ft, int transition) {
    ft.setTransition(transition);
}

public void attach(Fragment fragment) {
    if(fragment.isDetached()) {
        FragmentTransaction ft = manager.beginTransaction();
        ft.attach(fragment);
        ft.commit();
        currentFragment = fragment;
    } else {
        return;
    }
}

public void attach(Fragment fragment, int transition) {
    if(fragment.isDetached()) {
        FragmentTransaction ft = manager.beginTransaction();
        ft.attach(fragment);
        if (transition != FragmentTransaction.TRANSIT_UNSET) {
            setTransition(ft, transition);
        }
        ft.commit();
        currentFragment = fragment;
    } else {
        return;
    }
}

public void detach(Fragment fragment, int transition) {
    if(fragment.isDetached()) {
        return;
    } else {
        FragmentTransaction ft = manager.beginTransaction();
        ft.detach(fragment);
        if(transition != FragmentTransaction.TRANSIT_UNSET) {
            setTransition(ft, transition);
        }
        currentFragment = null;
        ft.commit();
    }

}

public void detach(Fragment fragment) {
    if(fragment.isDetached()) {
        return;
    } else {
        FragmentTransaction ft = manager.beginTransaction();
        ft.detach(fragment);
        ft.commit();
        currentFragment = null;
    }
}

public void add(int resId, Fragment fragment, String tag) {
    //manager.executePendingTransactions();

    if(fragment.isAdded()) {
        return;
    } else {
        FragmentTransaction ft = manager.beginTransaction();
        ft.addToBackStack(null);
        ft.add(resId, fragment, tag);

        ft.commit();
        currentFragment = fragment;
    }
}

public void addOnMain(int resId, Fragment fragment, String tag) {
    //manager.executePendingTransactions();

    if(fragment.isAdded()) {
        return;
    } else {
        manager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
        FragmentTransaction ft = manager.beginTransaction();

        ft.add(resId, fragment, tag);

        ft.commit();
        currentFragment = fragment;
    }
}

public void remove(Fragment fragment) {
    //manager.executePendingTransactions();
    manager.popBackStack();

    FragmentTransaction ft = manager.beginTransaction();
    ft.remove(fragment);
    ft.commit();
}

public void add(int resId, Fragment fragment, String tag, int transition) {
    //manager.executePendingTransactions();

    if(fragment.isAdded()) {
        return;
    } else {
        FragmentTransaction ft = manager.beginTransaction();
        ft.addToBackStack(null);
        ft.add(resId, fragment, tag);
        setTransition(ft, transition);
        ft.commit();
        currentFragment = fragment;
    }
}

public void addOnMain(int resId, Fragment fragment, String tag, int transition) {
    //manager.executePendingTransactions();

    if(fragment.isAdded()) {
        return;
    } else {
        manager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
        FragmentTransaction ft = manager.beginTransaction();
        ft.add(resId, fragment, tag);
        setTransition(ft, transition);
        ft.commit();
        currentFragment = fragment;
    }
}

public void remove(Fragment fragment, int transition) {
    //manager.executePendingTransactions();

    FragmentTransaction ft = manager.beginTransaction();
    ft.remove(fragment);
    setTransition(ft, transition);
    ft.commit();
}

HomeFragment 和其他什么都不做。只是膨胀 xml.

我目前正在处理一个带有 BottomNavigationView 和 Fragments 的项目。基本上这就是我做事的方式(为简单起见,省略了一些代码)。

MainActivity.java:

public class MainActivity extends AppCompatActivity
        implements OnFragmentSelectedListener {

    // the MainFragment in Fragment container with id R.id.fragment_container_main and state "started"
    private MainFragment selectedFragment;

    // MainFragments
    private HomeFragment homeFragment;
    private SeatFragment seatFragment;
    private SettingFragment settingFragment;
    private DialogFragment dialogFragment;

    private BottomNavigationView navBar;

    private BottomNavigationView.OnNavigationItemSelectedListener onNavigationItemSelectedListener
            = new BottomNavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            /*
             * Navigation strategy:
             * - if the Fragment being selected on the NavBar is already visible, ignore the click
             * - else navigate to the requested Fragment, creating it if necessary.
             */
            switch (item.getItemId()) {
                case R.id.navigation_home:
                    if (!(selectedFragment instanceof HomeFragment)) {
                        if (homeFragment == null) {
                            homeFragment = new HomeFragment();
                        }
                        displayFragment(homeFragment, R.id.fragment_container_main);
                    }
                    return true;
                case R.id.navigation_seat:
                    if (!(selectedFragment instanceof SeatFragment)) {
                        if (seatFragment == null) {
                            seatFragment = new SeatFragment();
                        }
                        displayFragment(seatFragment, R.id.fragment_container_main);
                    }
                    return true;
                case R.id.navigation_setting:
                    if (!(selectedFragment instanceof SettingFragment)) {
                        if (settingFragment == null) {
                            settingFragment = new SettingFragment();
                        }
                        displayFragment(settingFragment, R.id.fragment_container_main);
                    }
                    return true;
                case R.id.navigation_dialog:
                    if (!(selectedFragment instanceof DialogFragment)) {
                        if (dialogFragment == null) {
                            dialogFragment = new DialogFragment();
                        }
                        displayFragment(dialogFragment, R.id.fragment_container_main);
                    }
                    return true;
            }
            return false;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        navBar = findViewById(R.id.navigation);
        navBar.setOnNavigationItemSelectedListener(onNavigationItemSelectedListener);

        if (savedInstanceState == null) {
            homeFragment = new HomeFragment();
            displayFragment(homeFragment, R.id.fragment_container_main);
        }
    }

    /**
     * Replaces the fragment (MainFragment) in main fragment container with fragmentToDisplay, adding
     * the transaction to the back stack.
     *
     * @param fragmentToDisplay the fragment to display
     */
    @Override
    public void displayFragment(MainFragment fragmentToDisplay, int fragmentContainerId) {
        FragmentManager fm = getSupportFragmentManager();
        ft = fm.beginTransaction();
        ft.replace(fragmentContainerId, fragmentToDisplay, fragmentToDisplay.getClass().getName());
        ft.addToBackStack(null);
        ft.commit();
    }

    @Override
    public void onBackPressed() {
        if (selectedFragment == null || !selectedFragment.onBackPressed()) {
            if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
                getSupportFragmentManager().popBackStackImmediate();
            } else {
                super.onBackPressed();
            }
        }
    }

    @Override
    public void setSelectedFragment(MainFragment fragment) {
        selectedFragment = fragment;
    }
}

MainFragment.java:

/**
 * Abstract Fragment class for MainActivity Fragments
 * <p>
 * Created by kazume on 01.02.2018.
 */

public abstract class MainFragment extends Fragment {
    protected OnFragmentSelectedListener onFragmentSelectedListener;

    /**
     * Called when a back-press occurs in MainActivity popping the back stack (or exiting the app if
     * the back stack is empty ) by default. The return boolean offers the possibility to let the
     * MainFragment consume the back-press and stay on the MainFragment by returning true.
     *
     * @return Returns true if MainActivity's back-press is consumed by MainFragment and false if
     * back-press is handled by MainActivity
     */
    @SuppressWarnings("SameReturnValue")
    public boolean onBackPressed() {
        return false;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        try {
            onFragmentSelectedListener = (OnFragmentSelectedListener) context;
        } catch (ClassCastException e) {
            throw new ClassCastException(context.toString()
                    + " must implement OnFragmentSelectedListener");
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        if (this.getId() == R.id.fragment_container_main) {
            onFragmentSelectedListener.setSelectedFragment(this);
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        onFragmentSelectedListener = null;
    }
}

OnFragmentSelectedListener.java

/**
 * Interface to be implemented by an Activity hosting MainFragments. Via this interface, the hosting
 * activity always has a reference to the Fragment currently being displayed (state "started") in the
 * main fragment container.
 *
 * Created by kazume on 05.02.2017.
 */
public interface OnFragmentSelectedListener {
    void setSelectedFragment(MainFragment fragment);
}

在执行片段的 commit() 之前,您应该检查如下条件。

if(!getSupportFragmentManager().isStateSaved()) {  //this to avoid IllegalStateException
   //Here do your fragment.commit(); or fragmentDialog.show() etc.
}

refer to this post for more detail