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中的数据和通过get方法获取的数据是一样的,如果只到这里是没有问题的,我们旋转一下屏幕模拟内存不足fragment重建的时候的情况,
发生了什么,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方法获取到了!
可以参考这篇文章:
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
}