解决Play Store上报的MainFragment空指针异常问题
我的应用在Play Store收到如下空指针异常堆栈信息,无法定位异常原因,而我的摩托罗拉G31(Android SDK 32)设备上运行一切正常,且该异常在各类设备均出现,并非SDK或设备特定问题。
异常堆栈信息
Exception java.lang.NullPointerException: at in.vikalp.gridforart.ui.MainFragment$onCreateView$1.invoke (MainFragment.java:192) at in.vikalp.gridforart.ui.MainFragment$onCreateView$1.invoke (MainFragment.java:192) at in.vikalp.gridforart.ui.MainFragment$sam$androidx_lifecycle_Observer$0.onChanged (MainFragment.java) at androidx.lifecycle.LiveData.considerNotify (LiveData.java) at androidx.lifecycle.LiveData.dispatchingValue (LiveData.java:56) at androidx.lifecycle.LiveData.setValue (LiveData.java) at androidx.lifecycle.MutableLiveData.setValue (MutableLiveData.java) at androidx.lifecycle.LiveDataScopeImpl$emit$2.invokeSuspend (LiveDataScopeImpl.java:44) at androidx.lifecycle.LiveDataScopeImpl$emit$2.invoke (LiveDataScopeImpl.java) at androidx.lifecycle.LiveDataScopeImpl$emit$2.invoke (LiveDataScopeImpl.java) at kotlin.coroutines.jvm.internal.DebugProbesKt.j0 (DebugProbes.kt:5) at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext (BuildersKt__Builders_common.kt:91) at kotlinx.coroutines.BuildersKt.withContext (Builders.kt:91) at androidx.lifecycle.LiveDataScopeImpl.emit (LiveDataScopeImpl.java) at androidx.lifecycle.FlowLiveDataConversions$asLiveData$1$1.emit (FlowLiveDataConversions.java) at in.vikalp.gridforart.datastoreRepo.DatastoreRepository$special$$inlined$map$1$2.emit (DatastoreRepository.java:270)
涉及的MainFragment代码
@AndroidEntryPoint class MainFragment : Fragment(), EasyPermissions.PermissionCallbacks { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { // Inflate the layout for this fragment _binding = FragmentMainBinding.inflate(inflater, container, false) binding.lifecycleOwner = this binding.mainViewModel = mainViewModel gridSettingsViewModels.readGridSettings.observe(viewLifecycleOwner) { Log.e(TAG, "onCreateView: $it") mainViewModel.setSettings(it) if (oldImageUri.toString() != EMPTY_URI) mainViewModel.createBitmap(oldImageUri!!) } mainViewModel.imageUriLiveData.observe(viewLifecycleOwner) { Log.e(TAG, "onCreateView: $it") oldImageUri = Uri.parse(it) } binding.gridButton.setOnClickListener { findNavController().navigate(MainFragmentDirections.actionMainFragmentToGridBottomSheet()) } binding.filterButton.setOnClickListener { findNavController().navigate(MainFragmentDirections.actionMainFragmentToFilterDialogFragment()) } binding.tapBarMenu.setOnClickListener { binding.tapBarMenu.toggle() } binding.galleryButton.setOnClickListener { getImageFromGallery() } binding.downloadButton.setOnClickListener { mainViewModel.liveBitmapReady.observeOnce(viewLifecycleOwner){ savedImageBitmap(it) } } return binding.root } } private fun getImageFromGallery() { if (PermissionUtils.hasCameraPermission(requireContext())) { getImage.launch("image/*") } else { requestPermissions() } } private fun requestPermissions() { EasyPermissions.requestPermissions( this, "You need to accept storage permission to use this app", REQUEST_CODE_CAMERA_PERMISSIONS, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ) } override fun onPermissionsGranted(requestCode: Int, perms: MutableList<String>) { } // 标注:此处为代码中的192行位置 override fun onPermissionsDenied(requestCode: Int, perms: MutableList<String>) { if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { AppSettingsDialog.Builder(this).build().show() } } override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray ) { EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) }
从堆栈信息看,空指针异常实际发生在MainFragment.onCreateView中的第一个LiveData Observer回调里(编译后代码对应192行,即gridSettingsViewModels.readGridSettings.observe的lambda内部,而非你标注的权限方法行)。结合代码细节,主要原因及解决方式如下:
1. oldImageUri为空触发空指针
你的代码中:
if (oldImageUri.toString() != EMPTY_URI) mainViewModel.createBitmap(oldImageUri!!)
oldImageUri应为Fragment内的可空变量(初始值为null),当gridSettingsViewModels.readGridSettings的回调先于mainViewModel.imageUriLiveData触发时,oldImageUri还未被赋值,此时调用oldImageUri.toString()会直接抛出空指针,后续的oldImageUri!!强制非空也会触发异常。
解决方法:
- 增加空判断,避免直接调用可空变量的方法:
if (oldImageUri != null && oldImageUri.toString() != EMPTY_URI) { mainViewModel.createBitmap(oldImageUri) } - 或使用安全调用简化逻辑:
oldImageUri?.takeIf { it.toString() != EMPTY_URI }?.let { mainViewModel.createBitmap(it) } - 给
oldImageUri设置初始值,避免为空:private var oldImageUri: Uri = Uri.parse(EMPTY_URI)
2. 验证ViewModel注入有效性(概率较低)
虽然使用了@AndroidEntryPoint,但如果Hilt注入的gridSettingsViewModels或mainViewModel为空,也会导致回调中的空指针。这种情况通常会在Fragment初始化时崩溃,可通过日志验证:
// 在onCreateView开头添加 Log.d(TAG, "gridSettingsViewModels: $gridSettingsViewModels, mainViewModel: $mainViewModel")
同时检查Hilt配置,确保ViewModel被正确绑定到Fragment。
3. 调整LiveData观察时机
gridSettingsViewModels.readGridSettings可能在Fragment视图未完全初始化时触发回调,可将观察逻辑移至onViewCreated中,确保所有变量和View都已准备完毕:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) gridSettingsViewModels.readGridSettings.observe(viewLifecycleOwner) { // 原逻辑 } mainViewModel.imageUriLiveData.observe(viewLifecycleOwner) { // 原逻辑 } }
内容的提问来源于stack exchange,提问作者Vikalp Ranjan

