如何检测 Android 片段内扩展的空指针异常的原因

How to detect cause of Null Pointer Exception on Android Extensions inside fragments

在我的 android 应用程序中,我使用 Kotlin Android Extensions 从片段和活动中的 XML 布局访问视图。这在几乎所有情况下都适用,但当实际用户使用应用程序时,我不断收到关于 crashlytics 的空指针崩溃报告。我无法重现此问题或无法理解此问题的原因。

崩溃发生在具有 Kotlin Android Extensions 用法的片段上。

下面是我的代码:

Kotlin 文件

import kotlinx.android.synthetic.main.fragment_documents.*

@Keep
class BottomNavFragment : Fragment() {
    private val activityViewModel: MainActivityViewModel by activityViewModels()
    lateinit var mainActivity: MainActivity

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        val rootView = inflater.inflate(R.layout.fragment_documents, container, false)
        mainActivity = activity as MainActivity
        return rootView
    }

    companion object {
        @JvmStatic
        fun newInstance() =
            BottomNavFragment().apply {

            }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        try {
            setUpTabLayout()
            handleViewModelObservers()
        } catch (ex: Exception) {
            ex.printStackTrace()
        }
    }

    private fun handleViewModelObservers() {
        activityViewModel.isBackToHomeEnabled.observe(viewLifecycleOwner,
            { isHomeEnabled ->
                if (isHomeEnabled!!) {
                    tabLayout.getTabAt(1)?.select()

                }
            })
    }


    private fun setUpTabLayout() {
        try {
            if (mainActivity != null && activityViewModel != null && viewPager != null &&tabLayout!=null) {
                val adapter = FilesViewPagerAdapter(childFragmentManager)
                adapter.addFragments()
                viewPager.adapter = adapter
                viewPager.offscreenPageLimit = 4
                viewPager.currentItem = 1
                viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
                    override fun onPageScrolled(
                        position: Int,
                        positionOffset: Float,
                        positionOffsetPixels: Int
                    ) {

                    }

                    override fun onPageSelected(position: Int) {
                        mainActivity.clearSearch()
                    }

                    override fun onPageScrollStateChanged(state: Int) {
                    }

                })
                tabLayout.setupWithViewPager(viewPager)
                tabLayout.getTabAt(0)?.icon = mainActivity.getDrawable(R.drawable.test1)
                tabLayout.getTabAt(1)?.icon = mainActivity.getDrawable(R.drawable.test2)
                tabLayout.getTabAt(2)?.icon = mainActivity.getDrawable(R.drawable.test3)
                tabLayout.getTabAt(3)?.icon = mainActivity.getDrawable(R.drawable.test3)
                tabLayout.getTabAt(4)?.icon = mainActivity.getDrawable(R.drawable.test5)

                tabLayout.getTabAt(0)?.text = getString(R.string.test1)
                tabLayout.getTabAt(1)?.text = getString(R.string.test2)
                tabLayout.getTabAt(2)?.text = getString(R.string.test3)
                tabLayout.getTabAt(3)?.text = getString(R.string.test4)
                tabLayout.getTabAt(4)?.text = getString(R.string.test5)
            }
        } catch (ex: Exception) {
            ex.printStackTrace()
        }
    }


}

XML布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    tools:context=".views.fragments.BottomNavFragment">

    <View
        android:id="@+id/view"
        android:layout_width="match_parent"
        android:layout_height="@dimen/_50sdp"
        android:background="@color/colorPrimary" />


    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tabLayout"
        android:layout_width="match_parent"
        android:layout_height="@dimen/_60sdp"
        android:layout_marginStart="@dimen/_10sdp"
        android:layout_marginTop="@dimen/_20sdp"
        android:layout_marginEnd="@dimen/_10sdp"
        android:background="@drawable/bg_round_view_white"
        app:tabSelectedTextColor="@color/colorPrimary"
        app:tabTextAppearance="@style/ThemeTextAppearanceTab">

    </com.google.android.material.tabs.TabLayout>

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/tabLayout" />

</RelativeLayout>

崩溃报告

Fatal Exception: java.lang.NullPointerException: viewPager must not be null
       at mypackagename.views.fragments.BottomNavFragment$setUpTabLayout.run(BottomNavFragment.java)
       at android.os.Handler.handleCallback(Handler.java:739)
       at android.os.Handler.dispatchMessage(Handler.java:95)
       at android.os.Looper.loop(Looper.java:145)
       at android.app.ActivityThread.main(ActivityThread.java:6946)
       at java.lang.reflect.Method.invoke(Method.java)
       at java.lang.reflect.Method.invoke(Method.java:372)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)

到目前为止我学到了什么:

Kotlin Android 扩展实际上有 findViewByIdfindViewCacheclearFindViewByIdCache 实现。 正如本 article

中所述

The problem with fragments is that the view can be recreated but the fragment instance will be kept alive. What happens then? This means that the views inside the cache would be no longer valid.

那么,我怎么知道视图不再有效?或者,我可以做些什么来防止这次崩溃?

任何帮助将不胜感激。

Android 扩展插件已弃用,官方提议使用 ViewBinding 代替,它提供编译时访问 xml 布局的视图,而无需执行 viewFindById 在您的每个视图上。

This 此处的页面将为您提供设置代码以使用视图绑定的步骤,确保检查片段上的代码片段以清除您对视图的引用