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

ViewStub布局无法隐藏求助:网络恢复后仍显示无网界面

问题:ViewStub填充的无网络布局在网络恢复后无法隐藏

我用ViewStub实现了无网络连接布局,仅在网络断开时填充显示。已经通过NetworkListener监听网络状态,用MutableStateFlow的isNetworkAvailable接收状态,但网络恢复后,这个布局没法隐藏,一直显示。


相关代码

无网络布局(no_internet_connection_layout.xml)

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <ImageView
        android:id="@+id/no_wifi_image"
        android:layout_width="match_parent"
        android:layout_height="@dimen/_250sdp"
        android:contentDescription="@string/no_wifi_image"
        android:src="@drawable/no_internet_connection_icon"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/no_wifi_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="8dp"
        android:text="@string/no_internet_connection_message"
        android:textSize="@dimen/_20ssp" />

</androidx.constraintlayout.widget.ConstraintLayout>

Fragment布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/backgroundColor"
    android:orientation="vertical"
    tools:context=".ui.MainFragment">

    <com.facebook.shimmer.ShimmerFrameLayout
        android:id="@+id/shimmerLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:shimmer_auto_start="false"
        app:shimmer_repeat_mode="restart"
        app:shimmer_shape="radial">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <include layout="@layout/placeholder_row" />
            <include layout="@layout/placeholder_row" />
            <include layout="@layout/placeholder_row" />
            <include layout="@layout/placeholder_row" />
            <include layout="@layout/placeholder_row" />
            <include layout="@layout/placeholder_row" />
        </LinearLayout>
    </com.facebook.shimmer.ShimmerFrameLayout>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        android:orientation="vertical"
        android:paddingBottom="50dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|center"
        android:visibility="invisible"
        app:backgroundTint="@color/buttonColor"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        tools:visibility="visible">
    </ProgressBar>

    <ViewStub
        android:id="@+id/no_internet_connection_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout="@layout/no_internet_connection_layout"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        tools:visibility="gone">
    </ViewStub>

</androidx.constraintlayout.widget.ConstraintLayout>

Fragment逻辑代码

lifecycleScope.launchWhenStarted {
    networkListener.checkNetworkAvailability(requireContext()).collect { stats ->
        Log.d(TAG, "networkListener: $stats")
        postViewModel.networkStats = stats
        networkStats = stats
        postViewModel.showNetworkStats()
        if (stats) {
            mainViewModel.getData()
        } else {
            noInternetConnectionLayout()
            Log.e(TAG, "onViewCreated: no internet")
        }
    }
}

private fun noInternetConnectionLayout() {
    hideShimmerEffect()
    binding.apply {
        progressBar.visibility = View.GONE
        noInternetConnectionLayout.inflate().also {
            if(networkStats){
                it.visibility = View.GONE
                recyclerView.visibility = View.VISIBLE
                progressBar.visibility = View.VISIBLE
            }
        }
    }
}

NetworkListener代码

class NetworkListener : ConnectivityManager.NetworkCallback() {

    private var isNetworkAvailable = MutableStateFlow(false)

    fun checkNetworkAvailability(context: Context): MutableStateFlow<Boolean> {
        val connectivityManager =
            context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

        var isConnected = false

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            connectivityManager.registerDefaultNetworkCallback(this)
            connectivityManager.allNetworks.forEach { network ->
                val networkCapabilities = connectivityManager.getNetworkCapabilities(network)

                networkCapabilities?.let {
                    if (it.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
                        isConnected = true
                        return@forEach
                    }
                }
            }
            isNetworkAvailable.value = isConnected
        } else {
            val networkChangeFilter = NetworkRequest.Builder().build()
            connectivityManager.registerNetworkCallback(networkChangeFilter, this)
            connectivityManager.allNetworks.forEach { network ->
                val networkCapabilities = connectivityManager.getNetworkCapabilities(network)

                networkCapabilities?.let {
                    if (it.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
                        isConnected = true
                        return@forEach
                    }
                }
            }
            isNetworkAvailable.value = isConnected
        }
        return isNetworkAvailable
    }

    override fun onAvailable(network: Network) {
        isNetworkAvailable.value = true
    }

    override fun onLost(network: Network) {
        isNetworkAvailable.value = false
    }
}

问题原因

  1. ViewStub特性限制:inflate()方法仅能调用一次,调用后ViewStub会被替换为填充后的布局。当前逻辑仅在网络断开时调用填充方法,网络恢复时未对已填充的布局执行隐藏操作。
  2. 状态处理不完整:仅处理网络断开的情况,网络恢复时没有同步更新布局显示状态,也未控制主内容控件的可见性。

修复方案

步骤1:保存填充后的布局引用

在Fragment中添加变量存储无网络布局的实例,避免重复调用inflate():

private var noInternetView: View? = null

步骤2:统一处理网络状态的布局更新

替换原有的noInternetConnectionLayout()方法,同时处理网络断开和恢复的情况:

private fun updateNetworkLayout(hasNetwork: Boolean) {
    hideShimmerEffect()
    binding.apply {
        if (!hasNetwork) {
            // 网络断开时:显示无网络布局,隐藏主内容
            progressBar.visibility = View.GONE
            recyclerView.visibility = View.GONE
            // 复用已填充的布局,避免重复inflate
            noInternetView = noInternetView ?: no_internet_connection_layout.inflate()
            noInternetView?.visibility = View.VISIBLE
        } else {
            // 网络恢复时:隐藏无网络布局,显示主内容
            noInternetView?.visibility = View.GONE
            recyclerView.visibility = View.VISIBLE
            progressBar.visibility = View.VISIBLE
            mainViewModel.getData()
        }
    }
}

步骤3:修改Flow收集逻辑

在状态收集时直接调用统一处理方法,覆盖两种网络状态:

lifecycleScope.launchWhenStarted {
    networkListener.checkNetworkAvailability(requireContext()).collect { stats ->
        Log.d(TAG, "networkListener: $stats")
        postViewModel.networkStats = stats
        networkStats = stats
        postViewModel.showNetworkStats()
        updateNetworkLayout(stats) // 统一处理网络状态变化
    }
}

额外优化:避免重复注册NetworkCallback

当前checkNetworkAvailability()每次调用都会注册新的回调,可能引发内存泄漏,建议在Fragment生命周期内仅注册一次,并在销毁时注销:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    
    lifecycleScope.launchWhenStarted {
        networkListener.checkNetworkAvailability(requireContext()).collect { stats ->
            // 状态处理逻辑
            updateNetworkLayout(stats)
        }
    }

    // 在Fragment销毁时注销网络回调
    viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
        override fun onDestroy(owner: LifecycleOwner) {
            val connectivityManager = requireContext().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            connectivityManager.unregisterNetworkCallback(networkListener)
        }
    })
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 15:20:33