You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,两个选中操作在初始化阶段冲突,直接触发了第一次回调震荡。
  • 监听器残留:重建时没有清空旧的页面变化监听器,旧监听器和新监听器同时生效,放大了状态冲突问题。

修复方案

  1. 修正Tab遍历区间,将(0..tabLayout.tabCount)改为(0 until tabLayout.tabCount),左闭右开区间不会访问越界的Tab索引。
  2. 给页面选中回调加重复拦截,打断循环触发链,修改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)
        }
    }
}
  1. 调整初始化顺序:先绑定ViewPager和TabLayout,再设置页面监听器、自定义Tab布局,最后设置默认选中页。初始化阶段设置默认页统一使用viewPager.setCurrentItem(index, false),不要走业务层的switchScreen逻辑。
  2. 生命周期绑定清理:在Activity的onDestroy方法中调用viewPager.clearOnPageChangeListeners(),避免重建时旧监听器残留。

内容的提问来源于stack exchange,提问作者Martin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 23:09:18