You need to enable JavaScript to run this app. 使用FragmentScenario测试Fragment生命周期时1.3.1版仍出现onCreateView重复调用问题
优惠活动
大模型
产品
解决方案
定价
更多

使用FragmentScenario测试Fragment生命周期时1.3.1版仍出现onCreateView重复调用问题

问题场景
  • 测试开发阶段使用androidx.fragment:fragment-testing编写仪器测试,核心校验逻辑为:模拟应用被Home键切到后台再切回的场景(通过FragmentScenario.moveToState()将Fragment状态从RESUMED切到CREATED,再切回RESUMED),验证Fragment在停止、恢复状态切换时的底层逻辑正确性。
  • 此前使用androidx.fragment:fragment-testing:1.2.5时所有测试用例均可正常通过,升级依赖版本到1.3.1后,上述生命周期相关测试全部失败。
异常表现
  • 生命周期切换过程中,本不应该重复触发的Fragment.onCreateView()回调被多次调用,直接导致Fragment的View被重置为布局文件定义的初始状态。
  • 手动操作复现场景(正常切后台、切回前台)时不会出现View重置的问题,仅仪器测试环境能复现该异常。
  • 具体用例失败情况:
    • 点击TextView修改自定义文本后,经过一轮停-恢复生命周期切换,文本被重置为布局默认值
    • 点击隐藏Button后,经过一轮停-恢复生命周期切换,Button重新恢复可见状态
    • 仅EditText的文本校验可以正常通过,这是因为EditText的输入状态本身具备系统自动保存机制。

复现代码

被测Fragment代码

class LifecycleBugFragment : Fragment() {

    lateinit var textView: TextView
    lateinit var editText: EditText
    lateinit var button: Button

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        val view =  inflater.inflate(R.layout.fragment_lifecycle_bug, container, false)
        textView = view.findViewById<TextView>(R.id.textView)
        textView.setOnClickListener { textView.text = "I was clicked" }
        editText = view.findViewById<EditText>(R.id.editText)
        button = view.findViewById<Button>(R.id.button)
        button.setOnClickListener { button.visibility = View.GONE }
        return view
    }
}

Fragment布局代码

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".fragmenttesting.LifecycleBugFragment">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="default text" />

    <EditText
        android:id="@+id/editText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:inputType="text"
        />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="click to hide me"
        />
</LinearLayout>

测试类代码

const val TYPED_TEXT = "some example text"
const val DEFAULT_TEXT = "default text"
const val CLICKED_TEXT = "I was clicked"

class LifecycleBugFragmentTest {

    lateinit var fragmentScenario: FragmentScenario<LifecycleBugFragment>

    @Before
    fun setUp() {
        fragmentScenario = FragmentScenario.launchInContainer(LifecycleBugFragment::class.java)
    }

    @Test
    fun whenTextViewclickedAndFragmentLifecycleStoppedAndResumed_ThenTextViewTextIsStillChanged() {
        onView(withId(R.id.textView)).check(matches(withText(DEFAULT_TEXT)))
        onView(withId(R.id.textView)).perform(click())
        onView(withId(R.id.textView)).check(matches(withText(CLICKED_TEXT)))
        stopAndResumeFragment()
        onView(withId(R.id.textView)).check(matches(withText(CLICKED_TEXT)))
    }

    // 此用例可通过,其余用例均失败
    @Test
    fun whenEditTextIsEditedAndFragmentLifecycleStoppedAndResumed_ThenEditTextTextIsStillChanged() {
        onView(withId(R.id.editText)).perform(typeText(TYPED_TEXT))
        stopAndResumeFragment()
        onView(withId(R.id.editText)).check(matches(withText(TYPED_TEXT)))
    }

    @Test
    fun whenButtonIsClickedAndFragmentLifecycleStoppedAndResumed_ThenButtonISStillNotVisible() {
        onView(withId(R.id.button)).perform(click())
        onView(withId(R.id.button)).check(matches(not(isDisplayed())))
        stopAndResumeFragment()
        onView(withId(R.id.button)).check(matches(not(isDisplayed())))
    }

    private fun stopAndResumeFragment() {
        fragmentScenario.moveToState(Lifecycle.State.CREATED)
        fragmentScenario.moveToState(Lifecycle.State.RESUMED)
    }
}

初始依赖配置

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    implementation "org.jetbrains.kotlin:kotlin-stdlib:1.4.31"
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    testImplementation 'junit:junit:4.13'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    androidTestImplementation "androidx.test:runner:1.3.0"
    androidTestImplementation "androidx.test:core:1.3.0"
    androidTestImplementation "androidx.test.ext:junit:1.1.2"
    androidTestImplementation "androidx.test:rules:1.3.0"

    implementation "androidx.navigation:navigation-fragment-ktx:2.3.4"
    implementation "androidx.navigation:navigation-ui-ktx:2.3.4"
    androidTestImplementation "androidx.navigation:navigation-testing:2.3.4"
    debugImplementation "androidx.fragment:fragment-testing:1.3.1"

    implementation "androidx.navigation:navigation-compose:1.0.0-alpha09"

    // 其他无关依赖已省略
}
已排查方向
  • 对照已知历史缺陷:onCreateView()重复调用的问题早在Fragment 1.3.0-alpha08版本就已完成修复,理论上1.3.1版本不存在该已知问题。
  • 初步怀疑依赖解析异常:项目没有直接声明androidx.fragment:fragment主依赖,该库均以传递依赖的形式引入,担心实际解析到的版本低于带修复的阈值,未包含缺陷修复代码。
  • 已添加如下Gradle依赖约束,强制全项目统一解析androidx.fragment:fragment:1.3.1、androidx.fragment:fragment-ktx:1.3.1版本,问题依旧存在,排除显式版本解析错误的可能。
constraints {
    implementation('androidx.fragment:fragment:1.3.1') {
        because 'avoid bug'
    }
    implementation('androidx.fragment:fragment-ktx:1.3.1') {
        because 'avoid bug'
    }
}
问题根因

异常为Gradle传递依赖的版本错配导致:

  • 项目引入的androidx.navigation:navigation-compose:1.0.0-alpha09为早期alpha版本,其内部传递依赖的androidx.fragment:fragment版本为1.3.0-alpha07——该版本刚好早于缺陷修复的阈值(1.3.0-alpha08),包含那个已修复的onCreateView重复调用的问题。
  • 你添加的Gradle依赖约束仅作用于implementation配置范围,没有覆盖到仪器测试运行对应的debugImplementation、androidTestImplementation等配置。最终测试运行时,实际加载的是被Navigation Compose拉低的alpha07版本Fragment类,因此命中已修复的历史缺陷,和我们预期的1.3.1版本行为不一致。
可落地的解决方向
  • 扩展依赖约束的覆盖范围,将Fragment 1.3.1的版本约束应用到所有构建配置,确保主代码、测试代码、传递依赖拉取的Fragment版本全部统一对齐。
  • 升级androidx.navigation:navigation-compose到稳定正式版本,正式版内部依赖的Fragment版本会自动对齐到修复后的阈值,从根源避免旧快照版本的传递依赖拉低整体依赖版本。
  • 验证方式:执行./gradlew :app:dependencies --configuration debugAndroidTest,检查输出中androidx.fragment:fragment的实际解析版本,确认是否存在被低版本依赖拉低的情况。

内容的提问来源于stack exchange,提问作者Piotr Śmietana

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.29 19:54:02