Activity调用recreate()后ViewPager页面监听异常反复触发
ViewPager重建后页面无限来回切换问题修复
问题现象
在MainActivity中初始化ViewPager,正常启动运行时功能无异常,但调用MainActivity的recreate()方法重建Activity后,初始化阶段的ViewPager出现故障:addOnPageChangeListener会在无用户主动操作的情况下持续触发页面选中回调,页面在第一个和第二个标签页之间每秒来回反复切换。
问题相关代码
protected fun createCustomTabScreenFlow(){ pagerAdapter = FlowViewPagerAdapter(flowList) viewPager.adapter = pagerAdapter viewPager.offscreenPageLimit = flowList.size viewPager.addOnPageChangeListener(object: ViewPager.OnPageChangeListener{ override fun onPageScrollStateChanged(state: Int) {} override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} override fun onPageSelected(position: Int) { backButtonStatus(isEnabled = true) App.log("FlowActivity: onPageSelected: $position") App.log("FlowActivity: hasVisibleTabs: $hasVisibleTabs") if (hasVisibleTabs){ val page = flowList[position] if (page != currentPage){ //switch automatically currentPage?.onScreenRemoved() switchScreen(page::class.java) } } } }) tabLayout.setupWithViewPager(viewPager) (0..tabLayout.tabCount).forEach { tab-> flowList.getOrNull(tab)?.icon?.let { tabLayout.getTabAt(tab)?.let { tabItem-> val tabItemView = CustomResources.inflateLayout(layoutInflater, R.layout.tab_item, null, false) as ViewGroup tabItemView.findViewById<ImageView>(R.id.icon).setImageResource(it) tabItem.customView = tabItemView tabItem.customView?.requestLayout() } } } onScreenFlowIntialized() }
故障复现日志
2022-06-13 12:45:21.098 BaseActivity: onPause 2022-06-13 12:45:21.121 onActivityDestroyed(FlowScreen) 2022-06-13 12:45:21.121 Tab1Screen: onActivityDestroyed 2022-06-13 12:45:21.121 onActivityDestroyed(FlowScreen) 2022-06-13 12:45:21.121 Tab2Screen: onActivityDestroyed 2022-06-13 12:45:21.121 onActivityDestroyed(FlowScreen) 2022-06-13 12:45:21.173 MainActivity: onCreate 2022-06-13 12:45:21.196 BaseActivity: LocationProvider: init 2022-06-13 12:45:21.196 MainActivity: initScreens 2022-06-13 12:45:21.208 MainActivity: onScreenFlowIntialized 2022-06-13 12:45:21.208 FlowActivity: switchScreen: class online.project.tab_main.tabs.MenuScreen 2022-06-13 12:45:21.208 FlowActivity: switchScreen: index: 0 2022-06-13 12:45:21.217 FlowActivity: onPageSelected: 1 2022-06-13 12:45:21.217 FlowActivity: hasVisibleTabs: true 2022-06-13 12:45:21.217 FlowActivity: switchScreen: class online.project.tab_main.tabs.MusicScreen 2022-06-13 12:45:21.217 FlowActivity: switchScreen: index: 1 2022-06-13 12:45:21.219 BaseActivity: onResume 2022-06-13 12:45:21.620 FlowActivity: onPageSelected: 0 2022-06-13 12:45:21.621 FlowActivity: hasVisibleTabs: true 2022-06-13 12:45:21.621 FlowActivity: switchScreen: class online.project.tab_main.tabs.MenuScreen 2022-06-13 12:45:21.621 FlowActivity: switchScreen: index: 0 2022-06-13 12:45:21.621 FlowActivity: switchScreen: page: online.project.tab_main.tabs.MenuScreen 2022-06-13 12:45:21.709 FlowActivity: onPageSelected: 1 2022-06-13 12:45:21.709 FlowActivity: hasVisibleTabs: true 2022-06-13 12:45:21.709 FlowActivity: switchScreen: class online.project.tab_main.tabs.MusicScreen 2022-06-13 12:45:21.709 FlowActivity: switchScreen: index: 1 2022-06-13 12:45:21.709 FlowActivity: switchScreen: page: class online.project.tab_main.tabs.MusicScreen
故障原因
- 区间遍历错误:Kotlin中
0..tabLayout.tabCount是双端闭区间,若tab总数为2,会遍历0、1、2三个索引,索引2对应不存在的Tab,自定义Tab布局的逻辑会干扰TabLayout与ViewPager的绑定状态。 - 循环触发链:
tabLayout.setupWithViewPager(viewPager)本身自带双向选中同步逻辑,而onPageSelected回调中调用的switchScreen方法会主动修改页面选中状态,形成「选中0→触发回调切1→触发回调切0」的死循环。 - 初始化状态冲突:Activity重建时ViewPager会自动恢复销毁前保存的选中位置(索引1),但初始化完成后主动调用
switchScreen切到索引0,两个选中操作在初始化阶段冲突,直接触发了第一次回调震荡。 - 监听器残留:重建时没有清空旧的页面变化监听器,旧监听器和新监听器同时生效,放大了状态冲突问题。
修复方案
- 修正Tab遍历区间,将
(0..tabLayout.tabCount)改为(0 until tabLayout.tabCount),左闭右开区间不会访问越界的Tab索引。 - 给页面选中回调加重复拦截,打断循环触发链,修改
onPageSelected逻辑如下:
override fun onPageSelected(position: Int) { // 拦截重复无效回调,避免循环触发 if (viewPager.currentItem == position && currentPage?.javaClass == flowList[position]::class.java) { return } backButtonStatus(isEnabled = true) App.log("FlowActivity: onPageSelected: $position") App.log("FlowActivity: hasVisibleTabs: $hasVisibleTabs") if (hasVisibleTabs){ val page = flowList[position] if (page != currentPage){ currentPage?.onScreenRemoved() switchScreen(page::class.java) } } }
- 调整初始化顺序:先绑定ViewPager和TabLayout,再设置页面监听器、自定义Tab布局,最后设置默认选中页。初始化阶段设置默认页统一使用
viewPager.setCurrentItem(index, false),不要走业务层的switchScreen逻辑。 - 生命周期绑定清理:在Activity的
onDestroy方法中调用viewPager.clearOnPageChangeListeners(),避免重建时旧监听器残留。
内容的提问来源于stack exchange,提问作者Martin
相关产品推荐
相关产品推荐

