做网站选关键词,wordpress给公司建站,北京云邦网站建设,网站建设陕icp场景介绍#xff1a;在Android业务功能开发的过程中#xff0c;需要使用到嵌套ViewPage2实现页面切换#xff0c;这种场景在我们的开发过程中并不少见#xff0c;大致结构为一个activity包含一个viewPage2#xff0c;这个viewPage2中存在一个fragment A#xff0c;fragme… 场景介绍在Android业务功能开发的过程中需要使用到嵌套ViewPage2实现页面切换这种场景在我们的开发过程中并不少见大致结构为一个activity包含一个viewPage2这个viewPage2中存在一个fragment Afragment A中也包含了一个viewPage2。所有viewPage2都使用FragmentStateAdapter 适配器实现界面数据联动。 上述实现过程并不复杂但是在我实际业务中需要实现activity调用fragmentA中viewPage2的一些方法当然这个需求可以使用viewModel进行实现但是由于初版使用了方法调用遇到了bug所以针对该功能的实现进行初步研究。 FragmentStateAdapter 介绍及简单使用 FragmentStateAdapter 是 Android Jetpack 中提供的用于管理 Fragment 的适配器它是 RecyclerView.Adapter 的子类。 FragmentStateAdapter 会在 ViewPager 中显示的每个 Fragment 的生命周期之间进行恰当的保存和恢复 Fragment 的状态以确保内存占用较小。 当 Fragment 不再可见时FragmentStateAdapter 会销毁该 Fragment 的视图但会保留其实例状态以便在需要时重新创建。 适用于大量 Fragment 的场景特别是数据动态变化或数据量较大的情况。该适配器的最简单使用方式如下 adapter new FragmentStateAdapter(getChildFragmentManager(), getLifecycle()) {NonNullOverridepublic Fragment createFragment(int position) {return fragments.get(position);}Overridepublic int getItemCount() {return fragments.size();}};fragment切换销毁 在默认情况下viewPage2提供的性能优化实现了临近一个fragment预加载机制及如果初始展示第0个fragmentviewPage2也会把第1个fragment进行创建视图但并不展示。也就是说viewPage2默认的缓存机制会缓存三个fragment一旦需要缓存的实例超过三个例如从第0个滑动到第2个则会缓存123位置的fragment响应的第0个fragment将被销毁一直执行到onDestroy()生命周期。 值得说明的是销毁仅仅代表了生命周期的结束默认情况下该fragment的实例、其内部成员变量以及其绑定的视图都不一定会消失。 基于这一原因为了防止内存溢出我们在onDestroy()生命周期一般会针对成员变量进行setNull操作。通过setNull可以将成员变量消除引用以便触发GC。接触过java都清楚即便没有引用的变量也未必里面会触发GC因此当我门将Binding设置成null后其关联的view也未必会里面消失在fragment在此展示时依旧有可能调用上次绘制过的view进行显示。而且在通过viewPage2切换导致fragment销毁的过程中其本质上是执行到了onDestroy()生命周期并不见得会销毁视图而且viewPage2还将保存一个该fragment的实例根据上述内容可以总结下面几点
进入onDestroy生命周期并不能一定是成员变量销毁。通过viewPage2切换导致fragment销毁本质上是让fragment执行到onDestroy()生命周期但是viewPage2还保存了该fragment的实例如果在onDestroy()生命周期还没有把该fragment成员变量setNull则viewPage2所持有的该fragment对象依旧保留着这些fragment成员变量在onDestroy()生命周期中将Binding设置成null后并不能将其view都进行清空
fragment展示 展示通常有三种一种是viewPage2内缓存的fragment复现一种是新的未展示过的fragment展示还有一种是被销毁了的fragment的展示他们对应一下过程
缓存内fragment展示执行onResume()后直接进行展示未展示过的fragment展示调用构造方法初始化实例 – 调用onCreate一直执行到onResume生命周期销毁的fragment重新展示调用onCreateView一直执行到onResume进行展示。 需要注意的是销毁的fragment重新展示的过程中并没有进行fragment实例创建因此本质上viewPage2已经拥有该实例了知识当时调用了onDestroy方法而已。
我的问题 在我的业务场景中需要使用到viewPage2下的fragment实例然后调用该实例的方法如果只是单层viewPage2的使用则相对比较简单但是如果是嵌套viewPage2则会出现以下问题 一旦持有viewPage2的fragment在其所属的viewPage2切换过程中销毁了然后又由销毁状态到复现此时通过上述FragmentStateAdapter设置的fragment回调会导致异常。 在适配器的实现过程中我们通过fragments【list】进行fragment对象持有如果fragmentA【持有viewPage2的那个fragment】被复现时如果我们在oncreateView生命周期对fragments进行初始化调用add(fragment)方法那么此时复现导致fragments持有对象和上次展示时其所持有对象不同在fragmentA复现过程中必然也进行着fragmentA所持有的viewPage2下的fragment复现刚才已经说了销毁的复现本质上是oncreate生命周期的重新调用此时调用的是原来持有fragment对象的oncreate生命周期而在fragmentA复现的过程中导致fragments持有的对象和历史对象不同这些对象严格来讲仅仅经历了对象实例化阶段未执行fragment的其他生命周期还未创建持有视图如果我们调用视图的相关操作则会导致空指针等异常情况 简单来说就是fragmentA的销毁并不会导致其持有的viewPage2的销毁更不会导致viewPage2所持有的fragment的销毁如果我们对fragments进行重新设置此时创建的fragment对象仅仅创建对象而已。
viewPage2的setAdapter 按照我的问题描述那么是不是我将viewPage2原先持有的fragment对象全都删除就能解决问题删除的途径是调用viewPage2的setAdapter(null)方法。很遗憾该方法并不能解决问题该方法的源码如下 public void setAdapter(Nullable SuppressWarnings(rawtypes) Adapter adapter) {final Adapter? currentAdapter mRecyclerView.getAdapter();mAccessibilityProvider.onDetachAdapter(currentAdapter);unregisterCurrentItemDataSetTracker(currentAdapter);mRecyclerView.setAdapter(adapter);mCurrentItem 0;restorePendingState();mAccessibilityProvider.onAttachAdapter(adapter);registerCurrentItemDataSetTracker(adapter);}
在该方法执行的过程中restorePendingState的源码如下 private void restorePendingState() {if (mPendingCurrentItem NO_POSITION) {// No state to restore, or state is already restoredreturn;}Adapter? adapter getAdapter();if (adapter null) {return;}if (mPendingAdapterState ! null) {if (adapter instanceof StatefulAdapter) {((StatefulAdapter) adapter).restoreState(mPendingAdapterState);}mPendingAdapterState null;}// Now we have an adapter, we can clamp the pending current item and set itmCurrentItem Math.max(0, Math.min(mPendingCurrentItem, adapter.getItemCount() - 1));mPendingCurrentItem NO_POSITION;mRecyclerView.scrollToPosition(mCurrentItem);mAccessibilityProvider.onRestorePendingState();}/**restoreState方法如下**/Overridepublic final void restoreState(NonNull Parcelable savedState) {for (String key : bundle.keySet()) {if (isValidKey(key, KEY_PREFIX_FRAGMENT)) {long itemId parseIdFromKey(key, KEY_PREFIX_FRAGMENT);Fragment fragment mFragmentManager.getFragment(bundle, key);mFragments.put(itemId, fragment);continue;}if (isValidKey(key, KEY_PREFIX_STATE)) {long itemId parseIdFromKey(key, KEY_PREFIX_STATE);Fragment.SavedState state bundle.getParcelable(key);if (containsItem(itemId)) {mSavedStates.put(itemId, state);}continue;}throw new IllegalArgumentException(Unexpected key in savedState: key);}}
需要注意mPendingAdapterState 这一变量该变量将保留了历史fragment的基本信息因此在setAdapter的方法过程中还会将viewPage2的一些信息设置到你新的adapter中是不是很尴尬setAdapter方法并不是简单的把adapter方法设置后就结束了viewPage2内部还将自己历史关心的数据设置到该adapter中 FragmentStateAdapter 的createFragment源码如下 private void ensureFragment(int position) {long itemId getItemId(position);if (!mFragments.containsKey(itemId)) {// TODO(133419201): check if a Fragment provided here is a new FragmentFragment newFragment createFragment(position);newFragment.setInitialSavedState(mSavedStates.get(itemId));mFragments.put(itemId, newFragment);}}mFragments对象对于adapter很重要该对象持有了历史创建的fragment这样就导致无需每次使用的过程中进行重复创建了但这会导致一个尴尬的问题该mFragments默认查找是按照位置进行查找的换句话说一旦viewPage2完成展示以及数据加载在后续的切换过程中就算你用createFragment可以创建fragment对象但是由于相同位置下mFragments中已经存在数据所以根部不会执行createFragment方法 至此闭环setAdapter方法会使用到viewPage2持有的savedState设置adapter的mFragments对象ensureFragment方法会根据mFragments按照position判断fragment是否存在到此结束。
总结
本文内容描述比较粗略主要讲述了viewPage2嵌套使用过程中的一些问题以及导致这些问题的原因总结起来无非以下几点
viewPage2销毁fragment后依旧会持有其对象信息并标记在adapter中的mFragments中在后续复现时不会再进行对象的创建将Binding设置成null并不一定会导致viewPage2的重绘其依旧可能保留自己原始数据。viewPape2在进行setAdapter方法的过程中会将自己持有的fragment对象标记信息设置到FragmentStateAdapter 的mFragments中。