-
简书
- 《Fragment(一)从源码角度看add和replace过程》
- 《Fragment(二)状态改变与管理》
- 《Fragment(三)ViewPager中使用Fragment》
- 《Fragment(四)常见问题》
-
CSDN
- 《Fragment(一)从源码角度看add和replace过程》
- 《Fragment(二)状态改变与管理》
- 《Fragment(三)ViewPager中使用Fragment》
- 《Fragment(四)常见问题》
博客对应的Demo地址:GitHub、Gitee
通过这篇博客,我们能知道以下问题:
- Fragment 在不同情况下实现网络延迟
- Fragment 为什么一定要有无参构造?
- Fragment 与 Activity 传递数据方式
- 嵌套 Fragment 时父 Fragment 生命周期传递到子 Fragment 中的方式
其实使用延迟加载主要目的是在页面对用户可见时在加载网络,避免资源浪费,那么这个问题就转换成了 Fragment 在不同情况下怎样判断对用户的可见性,这个问题在前面的几篇博客中都或多或少的提到了,这里直接做一个总结:
-
add()+show()/hide():生命周期方法不对,多个添加的 Fragment 一开始就会会同时执行到 onResume(),退出时又会同时执行其他生命周期方法(onPause() 到 onDetach()),所以不能直接通过生命周期方法处理,而是需要通过 onHiddenChanged(boolean hidden) 方法判断。
-
replace():“替换”,这种方式会销毁布局容器内的已有 Fragment,然后重新创建一个新的 Fragment,销毁的Fragment执行onPause() 到 onDetach()回调方法,新的 Fragment 会执行 onAttach() 到 onResume() 回调,所以直接在 onStart()或onResume() 回调中处理就行了。
-
ViewPager:在AndroidX之前只有一种情况,在AndroidX中有两种情况,在Adapter构造中增加了一个 behavior 参数(取值:BEHAVIOR_SET_USER_VISIBLE_HINT、BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT),非AndroidX就相当于取值 BEHAVIOR_SET_USER_VISIBLE_HINT,所以两种情况需要分别来看:
-
BEHAVIOR_SET_USER_VISIBLE_HINT:生命周期方法监听不准确,需要通过 setUserVisibleHint() 方法来监听,当方法传入值为true的时候,说明Fragment可见,为false的时候说明Fragment被切走了。但是需要注意的是,这个方法不属于生命周期方法,所以它可能在生命周期方法执行之前就执行了,也就是说,有可能执行这个方法的时候,Fragment 还没有被添加到容器中,所以需要进行判断一下。
-
BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT:生命周期方法是正常的,只有正在显示的 Fragment 执行到 onResume() 方法,其他 Fragment 只会执行到 onStart() 方法,并且当 Fragment 切换到显示时执行 onResume() 方法,切换到不显示状态时触发 onPause() 方法。
-
-
ViewPager2:生命周期方法也是正常的,只有正在显示的 Fragment 执行到 onResume() 方法,其他 Fragment 只会执行到 onStart() 方法,并且当 Fragment 切换到显示时执行 onResume() 方法,切换到不显示状态时触发 onPause() 方法。
正常情况下,我们如果使用有参构造,自己在创建 Fragment 对象使用时,也是没问题的,但是如果 FragmentActivity 销毁自动重建,恢复页面状态时,如果页面包含 Fragment,那么没有无参构造就会发送异常,我们从源码里来看一下。
-
FragmentActivity 初始化方法
public FragmentActivity() { super(); init(); } @ContentView public FragmentActivity(@LayoutRes int contentLayoutId) { super(contentLayoutId); init(); } private void init() { // 增加 Context 可用监听 addOnContextAvailableListener(new OnContextAvailableListener() { @Override public void onContextAvailable(@NonNull Context context) { mFragments.attachHost(null ); Bundle savedInstanceState = getSavedStateRegistry() .consumeRestoredStateForKey(FRAGMENTS_TAG); // 如果 savedInstanceState 不为null,恢复页面状态 if (savedInstanceState != null) { Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG); // 调用 FragmentController#restoreSaveState() 方法 mFragments.restoreSaveState(p); } } }); }当FragmentActivity恢复页面状态是,会调用 FragmentController#restoreSaveState() 方法
public void restoreSaveState(@Nullable Parcelable state) { mHost.mFragmentManager.restoreSaveState(state); }调用 FragmentManager#restoreSaveState() 方法
void restoreSaveState(@Nullable Parcelable state) { fragmentStateManager = new FragmentStateManager(mLifecycleCallbacksDispatcher, mFragmentStore, mHost.getContext().getClassLoader(), getFragmentFactory(), fs); } // 获取 FragmentFactory 对象 public FragmentFactory getFragmentFactory() { if (mFragmentFactory != null) { return mFragmentFactory; } if (mParent != null) { return mParent.mFragmentManager.getFragmentFactory(); } return mHostFragmentFactory; } // FragmentFactory 对象初始化过程 private FragmentFactory mHostFragmentFactory = new FragmentFactory() { @Override public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) { // 回调中调用 FragmentContainer#instantiate() 方法 return getHost().instantiate(getHost().getContext(), className, null); } };FragmentContainer#instantiate() 方法
public Fragment instantiate(@NonNull Context context, @NonNull String className, @Nullable Bundle arguments) { return Fragment.instantiate(context, className, arguments); }继续查看 Fragment#instantiate() 方法
public static Fragment instantiate(@NonNull Context context, @NonNull String fname, @Nullable Bundle args) { try { Class extends Fragment> clazz = FragmentFactory.loadFragmentClass( context.getClassLoader(), fname); Fragment f = clazz.getConstructor().newInstance(); if (args != null) { args.setClassLoader(f.getClass().getClassLoader()); f.setArguments(args); } return f; } catch (Exception e) { } }通过反射创建实例,并且调用的是无参构造,所以为了避免异常产生,我们需要给 Fragment 定义无参构造。
通过上面的分析,我们知道了,虽然不给 Fragment 提供无参构造,正常情况下也能使用,但是一旦 Fragment 容器异常重建,恢复状态时,那么就会抛出异常,导致崩溃,所以我们一定需要给 Fragment 提供无参构造,推荐用法,Fragment 不适用带参构造,参数是通过 Fragment#setArguments() 方法传递。
3. Fragment 与 Activity 传递数据方式在使用 Fragment 和 Activity 的过程当中,不免需要进行数据传递,那么他们有哪些方式可以传递了。当然这里所讨论的方式,不包括Android原生的广播、文件、内容提供者、数据库等形式,也不包括第三方的 EventBus、RxBus 等全局通知形式,而是仅内存中他们相互传递的方式。
Activity 向 Fragment 传递数据首先我们看一下Activity 向 Fragment 传递数据的方式,一般有四种形式:构造方法、Fragment#setArguments() 方法、自定义Fragment实例方法和接口方式,我们来分别看一下
-
1. 构造方法
这种方法不说了,一是比较简单,二是不推荐使用带参构造创建 Fragment,原因在上面已经说过了,就不再重复。 -
2. Fragment#setArguments()
这是 Fragment 自带的方法,通过 Fragment 实例调用 setArguments() 方法,可以传递一系列数据给 Fragment,Fragment 通过 getArguments() 方法获取。// 传递数据 fun newInstance(content: String, color: Int): Vp2Fragment { val arguments = Bundle() arguments.putString("content", content) arguments.putInt("color", color) arguments.putString("tag", content) val vpFragment = Vp2Fragment() vpFragment.arguments = arguments return vpFragment } // 获取数据 arguments?.apply { var content = getString("content") var tag = getString("tag") var color = getInt("color") } -
3. 自定义Fragment实例方法
这种方式和 getArguments() 方法类似,我们在 Fragment 自定义方法,然后再 Activity 中获取Fragment对象,然后调用其方法,传递数据给 Fragment。 -
4. 接口方式
定义一个接口,在需要传递数据的各个 Fragment 中实现接口,然后在注册到宿主 Activity 中,当 Activity 数据中发送改变时,调用接口方法,将数据传递到实现接口的 Fragment 中。接口定义:
// 定义接口 interface ActivityDataChangeListener { fun onDataChange(message: String) }Activity 中相关方法:
// Activity中保存接口和增加注册方法 private val listenerList = ArrayList() open fun registerListener(dataChangeListener: ActivityDataChangeListener) { listenerList.add(dataChangeListener) } open fun unRegisterListener(dataChangeListener: ActivityDataChangeListener) { listenerList.remove(dataChangeListener) } // Activity数据改变时,回调接口方法 titleView.titleContentView.setOnClickListener { for (dataChangeListener in listenerList) { dataChangeListener.onDataChange("currentItem: " + viewPager.currentItem) } }Fragment 相关方法
// 初始化监听 private var dataChangeListener = object : ActivityDataChangeListener { override fun onDataChange(message: String) { Logger.i("ActivityDataChangeListener: $message") } } // 页面创建时注册监听到 Activity 中 override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { (activity as VpFragmentActivity).registerListener(dataChangeListener) return super.onCreateView(inflater, container, savedInstanceState) } // 页面销毁时从 Activity 中移除监听 override fun onDestroyView() { super.onDestroyView() (activity as VpFragmentActivity).unRegisterListener(dataChangeListener) }以上就是通过接口的方式,由 Activity 向 Fragment 传递数据的主要代码。
Fragment 向 Activity 传递数据的方式,一般有两种形式:调用Activity中的方法和接口方式,我们来分别看一下:
-
1. 调用Activity中的方法
这种方式就是直接在 Activity 中定义方法,然后在 Fragment 中通过获取 getActivity() 然后强转,在调用方法。在 Activity 中定义方法
open fun updateActivityData(message: String) { Logger.i("Activity data update: $message") }Fragment 中获取 Activity 后强转在调用方法,传递数据到 Activity。
(activity as Vp2FragmentActivity).updateActivityData("新数据") -
2. 接口方式:
定义一个接口,宿主 Activity 实现接口,当 Fragment 数据中发送改变时,调用接口方法,将数据传递到实现接口的 Activity 中。接口定义:
// 定义接口 interface FragmentDataChangeListener { fun onDataChange(message: String) }Activity 中相关方法:
// Activity中实现接口 FragmentDataChangeListener class Vp2FragmentActivity : BaseActivity(), FragmentDataChangeListener { // 重写方法 override fun onDataChange(message: String) { Logger.i("FragmentDataChangeListener: $message") } }Fragment 相关方法
private lateinit var dataChangeListener: FragmentDataChangeListener override fun onAttach(context: Context) { super.onAttach(context) // 将宿主Activity强转成接口对象 dataChangeListener = activity as FragmentDataChangeListener } // 更新数据时,调用接口方法,Activity中就会收到新的数据 tvContent.setOnClickListener { dataChangeListener.onDataChange("Fragment 新数据") }以上就是通过接口的方式,由 Fragment 向 Activity 传递数据的主要代码。
如果两个 Fragment 是父子关系的话,那么与 Activity 和 Fragment 之间传递数据方式一样;如果两个 Fragment 是兄弟关系的话(都是在同一个 Activity 或 Fragment 中),那么他们之间需要相互传递数据的话,就需要通过宿主进行中转了,先将数据传递向上给宿主 Activity 或 Fragment 中,然后在向下传递给另一个 Fragment,传递方式还是和上面一样。
4. 嵌套 Fragment 时父 Fragment 生命周期传递到子 Fragment 中的方式正常情况下宿主 Fragment 在生命周期执行的时候会相应的分发到子 Fragment 中,但是 setUserVisibleHint() 和 onHiddenChanged() 却没有进行相应的回调。试想一下,一个 ViewPager 中有一个 FragmentA 的tab,而 FragmentA 中有一个子FragmentB,FragmentA 被滑走了,FragmentB 并不能接收到 setUserVisibleHint() 事件,onHiddenChange() 事件也是一样的,那么肯定是我们不希望看到的,那么有什么办法能够避免这种问题了。一般有两种方式解决这个问题:
- 宿主 Fragment 提供可见性的回调,子 Fragment 监听改回调,有点类似于观察者模式
- 宿主 Fragment 可见性变化的时候,主动去遍历所有的 子 Fragment,调用 子 Fragment 的相应方法
第一步,先定义一个接口
interface OnFragmentVisibilityChangedListener {
fun onFragmentVisibilityChanged(visible: Boolean)
}
第二步,在 ParentFragment 中提供 addOnVisibilityChangedListener() 和 removeOnVisibilityChangedListener() 方法,这里需要注意的是,我们需要用一个 ArrayList 来保存所有的 listener,因为一个宿主 Fragment 可能有多个子 Fragment。
当 Fragment 可见性变化的时候,会遍历 List 调用 OnFragmentVisibilityChangedListener 的 onFragmentVisibilityChanged() 方法
class ParentFragment : Fragment() {
private val listeners = ArrayList()
fun addOnVisibilityChangedListener(listener: OnFragmentVisibilityChangedListener?) {
listener?.apply {
listeners.add(this)
}
}
fun removeOnVisibilityChangedListener(listener: OnFragmentVisibilityChangedListener?) {
listener?.apply {
listeners.remove(this)
}
}
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
for (listener in listeners) {
listener.onFragmentVisibilityChanged(!hidden)
}
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
for (listener in listeners) {
listener.onFragmentVisibilityChanged(isVisibleToUser)
}
}
}
第三步,在 Fragment#attach() 的时候,我们通过 getParentFragment() 方法,拿到宿主 Fragment,进行监听。这样,当宿主 Fragment 可见性变化的时候,子 Fragment 能感应到。
class ChildFragment : Fragment(), OnFragmentVisibilityChangedListener {
override fun onAttach(context: Context) {
super.onAttach(context)
if (parentFragment is ParentFragment) {
(parentFragment as ParentFragment).addOnVisibilityChangedListener(this)
}
}
override fun onFragmentVisibilityChanged(visible: Boolean) {
// 可见性发送改变
}
override fun onDetach() {
super.onDetach()
if (parentFragment is ParentFragment) {
(parentFragment as ParentFragment).removeOnVisibilityChangedListener(this)
}
}
}
2. 宿主 Fragment 可见性变化的时候,主动去遍历所有的 子 Fragment
第一步,与第一种方式一样,先定义一个接口
interface OnFragmentVisibilityChangedListener {
fun onFragmentVisibilityChanged(visible: Boolean)
}
第二步,子 Fragment 实现接口
class ChildFragment : Fragment(), OnFragmentVisibilityChangedListener {
override fun onFragmentVisibilityChanged(visible: Boolean) {
// 可见性发送改变
}
}
第三步,宿主 Fragment 生命周期发生变化的时候,遍历子 Fragment,调用相应的方法,通知生命周期发生变化
class ParentFragment : Fragment() {
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
onFragmentVisibilityChanged(!hidden)
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
onFragmentVisibilityChanged(isVisibleToUser)
}
private fun onFragmentVisibilityChanged(visible: Boolean) {
var fragments = childFragmentManager.fragments
if (fragments.isEmpty()) return
for (fragment in fragments) {
if (fragment is OnFragmentVisibilityChangedListener) {
fragment.onFragmentVisibilityChanged(visible)
}
}
}
}
暑期编程PK赛
得CSDN机械键盘等精美礼品!


