Fragment的正确用法

fargment和activity通信一直是Android开发里面经久不衰的问题,我们可以使用广播、eventbus或者接口回调完成。但是很少有人提到为什么不能在fragment里面直接定义一个get方法去获取呢?我查了一些文章,大部分都是说的fragment重建后会从arguments取值,其实这样说的也没错,但是为什么用set方法就不可以给fragment传值呢,好多人并没有说到点上。
我之前遇到过这种情况,也会偷懒直接用fragment的get方法给activity传值,但是在遇到内存不足,fragment获取的值是null,这就很奇怪了,因为按我当时的想法,即便是重建,它也会按照我的代码从头到尾执行一遍,从网络获取的数据然后解析成对象,为什么会变成null呢?而且重建之后显示还是正常的。
抱着这点好奇,我写下如下代码做了一个实验:

class MyFragment : Fragment {
    private lateinit var mView: View
    var i: Int? = 0;
    var j = 0;

    constructor() : super() {

    }

    constructor(j: Int) : super() {
        this.j = j
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        mView = inflater.inflate(R.layout.layout_myfragment, container, false)
        i = arguments?.getInt("value");
        return mView
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        mView.tv.text = "我是从arguments传递过来的:${i?.toString()}" + "\n" + "我是从构造函数传递过来的:${j.toString()}"
    }

}
btn.setOnClickListener {
            toast("我是从arguments传递过去的:${fragment.i.toString()}" + "\n" + "我是从构造函数传递过去的:${fragment.j.toString()}")
        }

我在fragment中定义了两个变量,一个从构造函数传递过来,一个从arguments传递过来,将两个变量显示在textview上。然后在activity中通过get方法直接将传递过去的变量toast出来。我们看一下结果:
Fragment的正确用法_第1张图片
竖屏未销毁

可以看到fragment中的数据和通过get方法获取的数据是一样的,如果只到这里是没有问题的,我们旋转一下屏幕模拟内存不足fragment重建的时候的情况,


Fragment的正确用法_第2张图片
横屏重建

发生了什么,fragment中从argument获取的值没变,但是从构造函数获取的值为什么变成0了呢,原因其实和文章开头说的一样,就是因为fragment重建后会从arguments中取值,可能会有小伙伴问了,我从构造函数传值怎么了,刚学java的人都知道这样也不会有问题的。这里先把结论说出来:
重建之后显示在手机上的fragment和在activity中new出来的fragment不是同一个!!!
重建之后显示在手机上的fragment和在activity中new出来的fragment不是同一个!!!
重建之后显示在手机上的fragment和在activity中new出来的fragment不是同一个!!!

既然不是同i一个,那从构造函数传递过去的值当然就拿不到了。
其实从activity的toast也可以说明这个问题,这也可以说明activity中的fragment实例在显示到手机时已经被换掉了。我来解释下为什么toast的结果和fragment显示的截然相反,首先是从arguments传递过去的,因为这个fragment实例并没有真正加载到activity上,onCreateView这个方法也是不曾执行,当然也不会为i赋值了,所以销毁后这个值是0,而从构造函数传递的值其实和一个普通类的没什么区别,所以当然可以用get方法获取到了!

Fragment的正确用法_第3张图片
微信截图_20191128152041.png

可以参考这篇文章:
android ViewPager子页面为Fragment,app被杀死后重建引发的bug
我们接下来说一说Fragment的重建,在FragmentManagerImp里面我们可以看到如下代码:

Fragment fragment = id != View.NO_ID ? findFragmentById(id) : null;
        if (fragment == null && tag != null) {
            fragment = findFragmentByTag(tag);
        }
        if (fragment == null && containerId != View.NO_ID) {
            fragment = findFragmentById(containerId);
        }

        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x"
                + Integer.toHexString(id) + " fname=" + fname
                + " existing=" + fragment);
        if (fragment == null) {
            fragment = mContainer.instantiate(context, fname, null);
            fragment.mFromLayout = true;
            fragment.mFragmentId = id != 0 ? id : containerId;
            fragment.mContainerId = containerId;
            fragment.mTag = tag;
            fragment.mInLayout = true;
            fragment.mFragmentManager = this;
            fragment.mHost = mHost;
            fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState);
            addFragment(fragment, true);

        } 

最后会调用Fragment的instantiate方法

public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
        try {
            Class clazz = sClassMap.get(fname);
            if (clazz == null) {
                // Class not found in the cache, see if it's real, and try to add it
                clazz = context.getClassLoader().loadClass(fname);
                sClassMap.put(fname, clazz);
            }
            Fragment f = (Fragment) clazz.getConstructor().newInstance();
            if (args != null) {
                args.setClassLoader(f.getClass().getClassLoader());
                f.setArguments(args);
            }
            return f;
        } catch (ClassNotFoundException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (java.lang.InstantiationException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (IllegalAccessException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (NoSuchMethodException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": could not find Fragment constructor", e);
        } catch (InvocationTargetException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": calling Fragment constructor caused an exception", e);
        }
    }

我们可以看到Fragment调用了无参的构造函数重新创建了一个实例,最后总结一下viewpager中重建的过程,activity重新执行onCreate,然后我们又初始化了新的fragment实例,但是在添加到ViewPager适配器时,系统去重建了fragment并且将arguements赋值,然后FragmentPagerAdapter检查缓存将重建后的fargment添加到viewpager,这也就导致了我们List中的fragment与实际显示的不是同一个fargment,正确做法应该是我们在创建fragment时应该用findFragmentByTag去检测是否存在,如果存在则直接添加到List中,反之再去重新实例化。

 if (savedInstanceState == null) {
      fragment = MyFragment()
      supportFragmentManager.beginTransaction().add(R.id.content, fragment, "MyFragment1").commit()
  } else {
      fragment = supportFragmentManager.findFragmentByTag("MyFragment1") as MyFragment
  }

你可能感兴趣的:(Fragment的正确用法)