Activity 回收 Fragments 重置 / 刷新 解决方案;

       Fragment 虽然足够轻量化,但其先天就是一个问题骚年,在他年少时(旧版本的v4包里),他的问题更多,比如:需要自己重写 Fragment 类的 onDestory,不过这些小毛病,都在他成年之后(最新的v4包里)被其父亲(Google)逐渐磨灭(修复)了,即便如此,如今的 fragment 仍然问题众多,比如一个很常见的问题就是: 当Activity因各种原因被重置时,Fragment 会暴露自己的一些问题,像是 fragment 重叠,或者 某些 fragment 不显示等等之类的,这种情况分析其相关源码 ,得知原因如下:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getContentViewResId());
 
        if (getIntent().hasExtra(Intent.EXTRA_TITLE)) {
            setTitle(getIntent().getStringExtra(Intent.EXTRA_TITLE));
        }
 
        final String customTitle = getIntent().getStringExtra(Intent.EXTRA_TITLE);
        setTitle(customTitle != null ? customTitle : getTitle());
 
        if (savedInstanceState == null) {
            mFragment = onCreatePane();
            mFragment.setArguments(intentToFragmentArguments(getIntent()));
            getFragmentManager().beginTransaction()
                    .add(R.id.root_container, mFragment, "single_pane")
                    .commit();
        } else {
            mFragment = getFragmentManager().findFragmentByTag("single_pane");
        }
    }

     * 
     * Save all appropriate fragment state.
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Parcelable p = mFragments.saveAllState();
        if (p != null) {
            outState.putParcelable(FRAGMENTS_TAG, p);//这里保存了Fragment数据
        }
        if (mPendingFragmentActivityResults.size() > 0) {
            outState.putInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG, mNextCandidateRequestIndex);
 
            int[] requestCodes = new int[mPendingFragmentActivityResults.size()];
            String[] fragmentWhos = new String[mPendingFragmentActivityResults.size()];
            for (int i = 0; i < mPendingFragmentActivityResults.size(); i++) {
                requestCodes[i] = mPendingFragmentActivityResults.keyAt(i);
                fragmentWhos[i] = mPendingFragmentActivityResults.valueAt(i);
            }
            outState.putIntArray(ALLOCATED_REQUEST_INDICIES_TAG, requestCodes);
            outState.putStringArray(REQUEST_FRAGMENT_WHO_TAG, fragmentWhos);
        }

从源码中得知,当 Activity 调用onSaveInstanceState方法的时候,会保存当前Activity里面的所有Fragment,保存在了一个Bundle里,key 就是 FRAGMENTS_TAG。

这个Bundle会在Activity恢复的时候传给onCreate,这就是为什么onSaveInstanceState不是null了。

当系统设置 切换语言、切换主题、或系统内存不足,Activity 被系统回收时,依附于该 Activity 的 fragment 也会被系统 destroy 掉,;当切换完成,或重启应用时,系统会 自动重新 创建Activity (重新 走onCreated(...){...})和 fragment 的实例,并将所有fragments都 attach到新创建的Activity中,若此时,用户在 Activity中 再次手动创建 fragment,则会导致程序 fragment 界面布局显示错乱。

可以用如下方法创建fragment,切换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<Fragment> 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() 加上:setRetainInstance(true);

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

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

可见它并不会执行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:
     * <ul>
     * <li> {@link #onDestroy()} will not be called (but {@link #onDetach()} still
     * will be, because the fragment is being detached from its current activity).
     * <li> {@link #onCreate(Bundle)} will not be called since the fragment
     * is not being re-created.
     * <li> {@link #onAttach(Activity)} and {@link #onActivityCreated(Bundle)} <b>will</b>
     * still be called.
     * </ul>
     */

控制一个fragment实例在Activity重建的时候是否保留(比如说fragment里的东西变了的时候),这个仅仅只能用于fragment没有在回退栈中。如果设置为true,在activity重建的时候,这个fragment的生命周期将会发生变化。
activity重建的时候,fragment周期的变化:
onDestroy:不会执行,但是onDetach会始终执行。
onCreate():不会执行。因为fragment不会被重建。
onAttach()和onActivityCreat():始终会执行。

总结

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按钮状态。
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值