切换多国语言导致Fragment被回收,出现切换错乱

如图所示,切换多国语言之后,由于fragment被回收,再次进入app的时候,切换fragment会错乱,甚至有可能切换完全无效果。

在百度google之后,发现只有这个和我现在遇到的问题比较像,但是代码却不是很完善。

在切换语言时,activity会被系统回收后重新创建,
此时原先依附于该activity的fragment也会被系统destroy掉,
但系统会自动创建新的fragment的实例attach到新的activity中,
若此时用户在activity中手动创建fragment,则会导致程序fragment管理混乱.
因此,建议用如下方法创建fragment

public class FragmentFactory {

  public static Fragment getFragmentByTag(Context pContext,String pTag){
       FragmentManager fm = pContext.getSupportFragmentManager();
    //查找是否已存在,已存在则不需要重发创建,切换语言时系统会自动重新创建并attch,无需手动创建
       Fragment fragment = fm.findFragmentByTag(pTag);
       if (fragment != null){
           return fragment;
        }
    else {
      if(MyFragment.TAG.equals(pTAG)){
        return new MyFragment(); 
      }
    }
  }
}

于是开始了自己的修改之路。
代码如下(我的切换fragment等操作放在BaseActivity里面,BaseActivity是继承的FragmentActivity):

 //利用反射获取fragment实例,如果该实例已经创建,则用不再创建。
    protected Fragment getFragmentInstance(Class fragmentClass) {
        FragmentManager fm = getSupportFragmentManager();

        //查找是否已存在,已存在则不需要重发创建,切换语言时系统会自动重新创建并attch,无需手动创建
        Fragment fragment = fm.findFragmentByTag(fragmentClass.getSimpleName());
        if (fragment != null) {
            return fragment;
        } else {
            try {
                fragment = (Fragment) fragmentClass.newInstance();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return fragment;
        }
    }

    /**
     * fragment切换
     *
     * @param fragment
     * @param layoutId
     * @return 最后显示的fragment
     */
    protected void switchFragment(Fragment fragment, int layoutId) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        if (fragmentManager == null || layoutId <= 0) {
            return;
        }
        //全局的fragment对象,用于记录最后一次切换的fragment(当前展示的fragment)
        mLastFragment = fragment;
        this.layoutId = layoutId;
        FragmentTransaction transaction = fragmentManager.beginTransaction();

        if (fragment != null) {
            List fragments = fragmentManager.getFragments();
            //先判断是否为空
            if (fragments != null) {
                for (Fragment mfragment : fragments) {
                    //再把现在显示的fragment 隐藏掉
                    if (!mfragment.isHidden()) {
                        transaction.hide(mfragment);
                    }
                }
            }
            //显示现在的fragment
            if (!fragment.isAdded()) {
                // transaction.addToBackStack(null);
                transaction.add(layoutId, fragment, fragment.getClass().getSimpleName());
            } else {
                transaction.show(fragment);
            }
        }
        transaction.commit();
    }


    @Override
    protected void onSaveInstanceState(Bundle outState) {
        //存储最后一个fragment对象
        if (mLastFragment != null) {
            outState.putString("fragment", mLastFragment.getClass().getName());
            outState.putInt("layoutId", layoutId);
        }
        super.onSaveInstanceState(outState);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        //取出该对象,由于此时所有的fragment是处于onAttach,hidden状态,所以只需要将它show出来就可以了。
        String fragmentSimpleName = savedInstanceState.getString("fragment");
        if (!TextUtils.isEmpty(fragmentSimpleName)) {
            try {
                Class mClass = Class.forName(fragmentSimpleName);
                //但是不能创建新fragment对象,因为之前的fragment并没有被回收掉,所以需要取出fragmentManager中的fragment,否则会造成错乱
                mLastFragment = getFragmentInstance(mClass);
                if (mLastFragment.isHidden()) {
                    switchFragment(mLastFragment, savedInstanceState.getInt("layoutId"));
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

效果如下:

但是发现了,虽然fragment没有错乱,但是fragment数据错乱了(订单)。
这是由于在activity被回收,重建的过程中,fragment虽然也被回收,但是并不是完全回收。然后show这个fragment,显示的不完全fragment。
当然,也有解决办法,而且非常简单。
在fragment中的onCreate()加上这句

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

setRetainInstance这个方法可以保存fragment实例,特别是在Activity重建的时候特别有用。
而设置这个属性为true的时候,fragment的生命周期发生了变化

切换多国语言导致Fragment被回收,出现切换错乱_第1张图片

可见它并不会执行onCreate()和onDestroy(),所以切忌在设置这个属性之后,还在onCreate()和onDestroy()里面进行操作。

官方说明

 /**
     * Control whether a fragment instance is retained across Activity
     * re-creation (such as from a configuration change).  This can only
     * be used with fragments not in the back stack.  If set, the fragment
     * lifecycle will be slightly different when an activity is recreated:
     * 
    *
  • {@link #onDestroy()} will not be called (but {@link #onDetach()} still * will be, because the fragment is being detached from its current activity). *
  • {@link #onCreate(Bundle)} will not be called since the fragment * is not being re-created. *
  • {@link #onAttach(Activity)} and {@link #onActivityCreated(Bundle)} will * still be called. *
*/ 控制一个fragment实例在Activity重建的时候是否保留(比如说fragment里的东西变了的时候),这个仅仅只能用于fragment没有在回退栈中。如果设置为true,在activity重建的时候,这个fragment的生命周期将会发生变化。 activity重建的时候,fragment周期的变化: onDestroy:不会执行,但是onDetach会始终执行。 onCreate():不会执行。因为fragment不会被重建。 onAttach()和onActivityCreat():始终会执行。

最终效果如下:

ps.
1、Fragment和FragmentManager是用的v4包的,android.support.v4.app.FragmentManager。否则没有getFragments()方法
2、由于我是用的RadioGroup作为TabHost,在Activity被回收之后重新进去还会保存上一个tab选中的状态,所以在onRestoreInstanceState只需要恢复Fragment就行了。如果是用LinearLayout或者TextView或者其他的,在activity重新进去之后没有保存tab选中的状态,那么需要在activity中重写onRestoreInstanceState,获取baseActivity的mLastFragment,判断它是属于哪个tab,再改变该tab按钮状态。

你可能感兴趣的:(Android,fragment)