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

Android View Binding线程中出现NullPointerException问题求助

NullPointerException with View Binding when updating UI from background thread after switching Fragments

I switched my code to use View Binding, and now I'm facing an issue when updating UI from a background thread—this worked fine with synthetic view access. Here's the error I get when switching to another Fragment:

java.lang.NullPointerException at HomeFragment.getBind(HomeFragment.kt:25) at HomeFragment.updateHomeUI$lambda-6(HomeFragment.kt:190) at HomeFragment.$r8$lambda$7K03ZbIZrY_5ngvcMBPsw15TPbw(Unknown Source:0) at HomeFragment$$ExternalSyntheticLambda10.run(Unknown Source:2) at java.lang.Thread.run(Thread.java:919)

Here's my current code:

class HomeFragment : Fragment(R.layout.fragment_home) {
    private var _binding: FragmentHomeBinding? = null
    private val bind get() = _binding!! // <-- line 25

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        _binding = FragmentHomeBinding.inflate(inflater, container, false)
        return bind.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // ... other setup code
        updateHomeUI()
    }

    private fun updateHomeUI() {
        Thread {
            while (bind.tvName != null) { // Try to stop loop when Fragment is switched
                // ... some logic
                // Lots of UI updates like this:
                if (activity != null) (activity as MainActivity).runOnUiThread {
                    bind.tvName?.text = str
                }
                // ...
                Thread.sleep(1000)
            }
        }.start()
    }
}

I know the issue is that after the Fragment runs onDestroyView(), the background thread is still active, and _binding has been set to null. I tried waiting for the thread to finish before nulling the binding, but this caused the app to freeze:

override fun onDestroyView() {
    super.onDestroyView()
    threadStop = true
    while (threadRunning) {
        Thread.sleep(1)
    }
    _binding = null
}

private fun updateHomeUI() {
    Thread {
        threadRunning = true
        threadStop = false
        while (!threadStop) {
            // ... logic
        }
        threadRunning = false
    }
}

What's the correct way to fix this without causing app freezes?


Solutions

Let's break down the problem and fix it properly—your core issue is that the background thread is trying to access a binding that's already been cleared, and your attempt to wait for the thread blocks the main thread (which is why the app freezes). Here are a few robust approaches:

Coroutines are built for this kind of background work, and LifecycleScope automatically cancels coroutines when the Fragment's lifecycle is destroyed—no need to manage thread flags manually.

Replace your background Thread with a coroutine like this:

class HomeFragment : Fragment(R.layout.fragment_home) {
    private var _binding: FragmentHomeBinding? = null
    private val bind get() = _binding!!

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        _binding = FragmentHomeBinding.inflate(inflater, container, false)
        return bind.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        updateHomeUI()
    }

    private fun updateHomeUI() {
        lifecycleScope.launch(Dispatchers.IO) {
            while (isActive) { // isActive stays true until the coroutine is canceled (when Fragment is destroyed)
                // ... your background logic here

                // Update UI safely on main thread
                withContext(Dispatchers.Main) {
                    // Check binding validity before accessing views
                    _binding?.tvName?.text = str
                }

                delay(1000) // Replace blocking Thread.sleep() with non-blocking delay()
            }
        }
    }
}

Why this works:

  • lifecycleScope is tied directly to the Fragment's lifecycle—when onDestroyView() runs, the coroutine is automatically canceled, so the loop stops cleanly.
  • delay(1000) doesn't block the thread, so it uses resources more efficiently than Thread.sleep().
  • We access _binding with a null check instead of using the bind getter (which uses !! and throws NPE), so even if the binding is cleared mid-loop, we don't crash.

2. Add Null Checks and Proper Thread Termination (If you prefer using Threads)

If you want to stick with traditional Threads, avoid blocking the main thread in onDestroyView(). Instead, use a volatile flag to signal the thread to stop, and always validate the binding before accessing it:

class HomeFragment : Fragment(R.layout.fragment_home) {
    private var _binding: FragmentHomeBinding? = null
    private val bind get() = _binding!!
    @Volatile private var shouldStop = false // Volatile ensures thread visibility

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        _binding = FragmentHomeBinding.inflate(inflater, container, false)
        return bind.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        shouldStop = true // Signal thread to stop
        _binding = null
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        updateHomeUI()
    }

    private fun updateHomeUI() {
        Thread {
            while (!shouldStop) {
                // ... background logic

                // Update UI safely
                activity?.runOnUiThread {
                    // Check binding before accessing views
                    _binding?.tvName?.text = str
                }

                try {
                    Thread.sleep(1000)
                } catch (e: InterruptedException) {
                    // Handle interrupt (thread is being stopped)
                    Thread.currentThread().interrupt()
                    break
                }
            }
        }.start()
    }
}

Key fixes here:

  • @Volatile on shouldStop ensures the background thread sees the updated value immediately (no thread-local caching).
  • We don't block the main thread in onDestroyView()—we just set the flag and let the thread exit on its own next loop iteration.
  • We wrap Thread.sleep() in a try-catch to handle interrupts, letting us stop the thread faster if needed.
  • We use _binding?.tvName instead of bind.tvName to avoid the NPE from the !! operator.

3. Avoid Direct Binding Access in Threads (Alternative Approach)

Another option is to store weak references to the views you need to update, so you don't have to access the binding at all in the thread:

class HomeFragment : Fragment(R.layout.fragment_home) {
    private var _binding: FragmentHomeBinding? = null
    private val bind get() = _binding!!
    private var tvNameRef: WeakReference<TextView>? = null
    @Volatile private var shouldStop = false

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        _binding = FragmentHomeBinding.inflate(inflater, container, false)
        return bind.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        tvNameRef = WeakReference(bind.tvName)
        updateHomeUI()
    }

    override fun onDestroyView() {
        super.onDestroyView()
        shouldStop = true
        tvNameRef = null
        _binding = null
    }

    private fun updateHomeUI() {
        Thread {
            while (!shouldStop) {
                // ... background logic

                activity?.runOnUiThread {
                    tvNameRef?.get()?.text = str
                }

                try {
                    Thread.sleep(1000)
                } catch (e: InterruptedException) {
                    Thread.currentThread().interrupt()
                    break
                }
            }
        }.start()
    }
}

Why this works:

  • WeakReference allows the TextView to be garbage collected when the Fragment's view is destroyed, preventing memory leaks.
  • You don't need to interact with the binding in the thread at all—just the weak reference to the specific view.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 06:50:06