栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Fragment(四)常见问题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Fragment(四)常见问题

  • 简书

    • 《Fragment(一)从源码角度看add和replace过程》
    • 《Fragment(二)状态改变与管理》
    • 《Fragment(三)ViewPager中使用Fragment》
    • 《Fragment(四)常见问题》
  • CSDN

    • 《Fragment(一)从源码角度看add和replace过程》
    • 《Fragment(二)状态改变与管理》
    • 《Fragment(三)ViewPager中使用Fragment》
    • 《Fragment(四)常见问题》
Fragment(四)常见问题

博客对应的Demo地址:GitHub、Gitee

通过这篇博客,我们能知道以下问题:

  • Fragment 在不同情况下实现网络延迟
  • Fragment 为什么一定要有无参构造?
  • Fragment 与 Activity 传递数据方式
  • 嵌套 Fragment 时父 Fragment 生命周期传递到子 Fragment 中的方式
1. Fragment 在不同情况下实现网络延迟

其实使用延迟加载主要目的是在页面对用户可见时在加载网络,避免资源浪费,那么这个问题就转换成了 Fragment 在不同情况下怎样判断对用户的可见性,这个问题在前面的几篇博客中都或多或少的提到了,这里直接做一个总结:

  1. add()+show()/hide():生命周期方法不对,多个添加的 Fragment 一开始就会会同时执行到 onResume(),退出时又会同时执行其他生命周期方法(onPause() 到 onDetach()),所以不能直接通过生命周期方法处理,而是需要通过 onHiddenChanged(boolean hidden) 方法判断。

  2. replace():“替换”,这种方式会销毁布局容器内的已有 Fragment,然后重新创建一个新的 Fragment,销毁的Fragment执行onPause() 到 onDetach()回调方法,新的 Fragment 会执行 onAttach() 到 onResume() 回调,所以直接在 onStart()或onResume() 回调中处理就行了。

  3. 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() 方法。

  4. ViewPager2:生命周期方法也是正常的,只有正在显示的 Fragment 执行到 onResume() 方法,其他 Fragment 只会执行到 onStart() 方法,并且当 Fragment 切换到显示时执行 onResume() 方法,切换到不显示状态时触发 onPause() 方法。

2. Fragment 为什么一定要有无参构造?

正常情况下,我们如果使用有参构造,自己在创建 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 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 传递数据

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 与 Fragment 之间传递数据

如果两个 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() 事件也是一样的,那么肯定是我们不希望看到的,那么有什么办法能够避免这种问题了。一般有两种方式解决这个问题:

  1. 宿主 Fragment 提供可见性的回调,子 Fragment 监听改回调,有点类似于观察者模式
  2. 宿主 Fragment 可见性变化的时候,主动去遍历所有的 子 Fragment,调用 子 Fragment 的相应方法
1.宿主 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机械键盘等精美礼品!
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1016438.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号