NullPointerException在按返回键时出现在getBinding中。

huangapple go评论93阅读模式
英文:

NullPointerException in getBinding when pressing back

问题

I see the issue you're facing in your Android app. It looks like you're getting a null pointer exception because you're trying to access _binding when it's null. This can happen when the fragment is destroyed, and you're still trying to update the UI on the main thread.

To solve this issue, you can check if _binding is not null before accessing it. Here's the updated code that should help avoid the null pointer exception:

  1. private val binding get() = _binding ?: return // Return null if _binding is null
  2. activity?.runOnUiThread {
  3. binding?.bottomSheetLayout?.inferenceTimeVal?.text =
  4. String.format("%d ms", inferenceTime)
  5. // ...
  6. }

With this change, if _binding is null, the code will exit early and not attempt to update the UI, preventing the null pointer exception.

Additionally, it's a good practice to cancel or handle any background tasks or threads in your fragment when it's destroyed. Make sure to clean up any resources and stop any threads that might be running to prevent issues like this.

英文:

I build an app with multiple fragments and single activity. I have a detection fragment
<!-- language: lang-kotlin -->

  1. private val PERMISSIONS_REQUIRED = arrayOf(Manifest.permission.CAMERA)
  2. class DetectionFragment : Fragment(), ObjectDetectorHelper.DetectorListener {
  3. private var _binding: FragmentDetectionBinding? = null
  4. private val binding get() = _binding!!
  5. private val TAG = &quot;ObjectDetection&quot;
  6. private lateinit var objectDetectorHelper: ObjectDetectorHelper
  7. private lateinit var bitmapBuffer: Bitmap
  8. private var preview: Preview? = null
  9. private var imageAnalyzer: ImageAnalysis? = null
  10. private var camera: Camera? = null
  11. private var cameraProvider: ProcessCameraProvider? = null
  12. /** Blocking camera operations are performed using this executor */
  13. private lateinit var cameraExecutor: ExecutorService
  14. private val requestPermissionLauncher =
  15. registerForActivityResult(
  16. ActivityResultContracts.RequestPermission()
  17. ) { ...
  18. }
  19. override fun onCreateView(
  20. inflater: LayoutInflater, container: ViewGroup?,
  21. savedInstanceState: Bundle?
  22. ): View? {
  23. // Inflate the layout for this fragment
  24. _binding = FragmentDetectionBinding.inflate(layoutInflater, container, false)
  25. return binding.root
  26. }
  27. override fun onDestroyView() {
  28. _binding = null
  29. super.onDestroyView()
  30. // Shut down our background executor
  31. cameraExecutor.shutdown()
  32. }
  33. override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
  34. super.onViewCreated(view, savedInstanceState)
  35. onBack()
  36. when {
  37. //checkpermission thing
  38. }
  39. objectDetectorHelper = ObjectDetectorHelper(
  40. context = requireContext(),
  41. objectDetectorListener = this)
  42. // Initialize our background executor
  43. cameraExecutor = Executors.newSingleThreadExecutor()
  44. // Wait for the views to be properly laid out
  45. binding.viewFinder.post {
  46. // Set up the camera and its use cases
  47. setUpCamera()
  48. }
  49. // Attach listeners to UI control widgets
  50. initBottomSheetControls()
  51. }
  52. private fun initBottomSheetControls(){
  53. // When clicked, lower detection score threshold floor
  54. binding.bottomSheetLayout.thresholdMinus.setOnClickListener {
  55. if (objectDetectorHelper.threshold &gt;= 0.1) {
  56. objectDetectorHelper.threshold -= 0.1f
  57. updateControlsUi()
  58. }
  59. }
  60. // When clicked, raise detection score threshold floor
  61. binding.bottomSheetLayout.thresholdPlus.setOnClickListener {
  62. if (objectDetectorHelper.threshold &lt;= 0.8) {
  63. objectDetectorHelper.threshold += 0.1f
  64. updateControlsUi()
  65. }
  66. }
  67. // When clicked, reduce the number of objects that can be detected at a time
  68. binding.bottomSheetLayout.maxResultsMinus.setOnClickListener {
  69. if (objectDetectorHelper.maxResults &gt; 1) {
  70. objectDetectorHelper.maxResults--
  71. updateControlsUi()
  72. }
  73. }
  74. // When clicked, increase the number of objects that can be detected at a time
  75. binding.bottomSheetLayout.maxResultsPlus.setOnClickListener {
  76. if (objectDetectorHelper.maxResults &lt; 5) {
  77. objectDetectorHelper.maxResults++
  78. updateControlsUi()
  79. }
  80. }
  81. // When clicked, decrease the number of threads used for detection
  82. binding.bottomSheetLayout.threadsMinus.setOnClickListener {
  83. if (objectDetectorHelper.numThreads &gt; 1) {
  84. objectDetectorHelper.numThreads--
  85. updateControlsUi()
  86. }
  87. }
  88. // When clicked, increase the number of threads used for detection
  89. binding.bottomSheetLayout.threadsPlus.setOnClickListener {
  90. if (objectDetectorHelper.numThreads &lt; 4) {
  91. objectDetectorHelper.numThreads++
  92. updateControlsUi()
  93. }
  94. }
  95. // When clicked, change the underlying hardware used for inference. Current options are CPU
  96. // GPU, and NNAPI
  97. binding.bottomSheetLayout.spinnerDelegate.setSelection(0, false)
  98. binding.bottomSheetLayout.spinnerDelegate.onItemSelectedListener =
  99. object : AdapterView.OnItemSelectedListener {
  100. override fun onItemSelected(p0: AdapterView&lt;*&gt;?, p1: View?, p2: Int, p3: Long) {
  101. objectDetectorHelper.currentDelegate = p2
  102. updateControlsUi()
  103. }
  104. override fun onNothingSelected(p0: AdapterView&lt;*&gt;?) {
  105. /* no op */
  106. }
  107. }
  108. // When clicked, change the underlying model used for object detection
  109. binding.bottomSheetLayout.spinnerModel.setSelection(0, false)
  110. binding.bottomSheetLayout.spinnerModel.onItemSelectedListener =
  111. object : AdapterView.OnItemSelectedListener {
  112. override fun onItemSelected(p0: AdapterView&lt;*&gt;?, p1: View?, p2: Int, p3: Long) {
  113. objectDetectorHelper.currentModel = p2
  114. updateControlsUi()
  115. }
  116. override fun onNothingSelected(p0: AdapterView&lt;*&gt;?) {
  117. /* no op */
  118. }
  119. }
  120. }
  121. private fun updateControlsUi() {
  122. binding.bottomSheetLayout.maxResultsValue.text =
  123. objectDetectorHelper.maxResults.toString()
  124. binding.bottomSheetLayout.thresholdValue.text =
  125. String.format(&quot;%.2f&quot;, objectDetectorHelper.threshold)
  126. binding.bottomSheetLayout.threadsValue.text =
  127. objectDetectorHelper.numThreads.toString()
  128. // Needs to be cleared instead of reinitialized because the GPU
  129. // delegate needs to be initialized on the thread using it when applicable
  130. objectDetectorHelper.clearObjectDetector()
  131. binding.overlay.clear()
  132. }
  133. // Initialize CameraX, and prepare to bind the camera use cases
  134. private fun setUpCamera() {
  135. val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
  136. cameraProviderFuture.addListener(
  137. {
  138. // CameraProvider
  139. cameraProvider = cameraProviderFuture.get()
  140. // Build and bind the camera use cases
  141. bindCameraUseCases()
  142. },
  143. ContextCompat.getMainExecutor(requireContext())
  144. )
  145. }
  146. @Suppress(&quot;DEPRECATION&quot;)
  147. private fun bindCameraUseCases() {
  148. // CameraProvider
  149. val cameraProvider =
  150. cameraProvider ?: throw IllegalStateException(&quot;Camera initialization failed.&quot;)
  151. // CameraSelector - makes assumption that we&#39;re only using the back camera
  152. val cameraSelector =
  153. CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
  154. // Preview. Only using the 4:3 ratio because this is the closest to our models
  155. preview =
  156. Preview.Builder()
  157. .setTargetAspectRatio(AspectRatio.RATIO_4_3)
  158. .setTargetRotation(binding.viewFinder.display.rotation)
  159. .build()
  160. // ImageAnalysis. Using RGBA 8888 to match how our models work
  161. imageAnalyzer =
  162. ImageAnalysis.Builder()
  163. .setTargetAspectRatio(AspectRatio.RATIO_4_3)
  164. .setTargetRotation(binding.viewFinder.display.rotation)
  165. .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
  166. .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
  167. .build()
  168. // The analyzer can then be assigned to the instance
  169. .also {
  170. it.setAnalyzer(cameraExecutor) { image -&gt;
  171. if (!::bitmapBuffer.isInitialized) {
  172. // The image rotation and RGB image buffer are initialized only once
  173. // the analyzer has started running
  174. bitmapBuffer = Bitmap.createBitmap(
  175. image.width,
  176. image.height,
  177. Bitmap.Config.ARGB_8888
  178. )
  179. }
  180. detectObjects(image)
  181. }
  182. }
  183. // Must unbind the use-cases before rebinding them
  184. cameraProvider.unbindAll()
  185. try {
  186. // A variable number of use-cases can be passed here -
  187. // camera provides access to CameraControl &amp; CameraInfo
  188. camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalyzer)
  189. // Attach the viewfinder&#39;s surface provider to preview use case
  190. preview?.setSurfaceProvider(binding.viewFinder.surfaceProvider)
  191. } catch (exc: Exception) {
  192. Log.e(TAG, &quot;Use case binding failed&quot;, exc)
  193. }
  194. }
  195. private fun detectObjects(image: ImageProxy) {
  196. // Copy out RGB bits to the shared bitmap buffer
  197. image.use { bitmapBuffer.copyPixelsFromBuffer(image.planes[0].buffer) }
  198. val imageRotation = image.imageInfo.rotationDegrees
  199. // Pass Bitmap and rotation to the object detector helper for processing and detection
  200. objectDetectorHelper.detect(bitmapBuffer, imageRotation)
  201. }
  202. override fun onConfigurationChanged(newConfig: Configuration) {
  203. super.onConfigurationChanged(newConfig)
  204. imageAnalyzer?.targetRotation = binding.viewFinder.display.rotation
  205. }
  206. override fun onDestroy() {
  207. super.onDestroy()
  208. _binding = null
  209. }
  210. override fun onError(error: String) {
  211. activity?.runOnUiThread {
  212. Toast.makeText(requireContext(), error, Toast.LENGTH_SHORT).show()
  213. }
  214. }
  215. // Update UI after objects have been detected. Extracts original image height/width
  216. // to scale and place bounding boxes properly through OverlayView
  217. override fun onResults(
  218. results: MutableList&lt;Detection&gt;?,
  219. inferenceTime: Long,
  220. imageHeight: Int,
  221. imageWidth: Int
  222. ) {
  223. activity?.runOnUiThread {
  224. binding.bottomSheetLayout.inferenceTimeVal.text =
  225. String.format(&quot;%d ms&quot;, inferenceTime)
  226. // Pass necessary information to OverlayView for drawing on the canvas
  227. binding.overlay.setResults(
  228. results ?: LinkedList&lt;Detection&gt;(),
  229. imageHeight,
  230. imageWidth
  231. )
  232. // Force a redraw
  233. binding.overlay.invalidate()
  234. }
  235. }
  236. }

And I have this error when I press back button on this screen and immediately close the app.

  1. FATAL EXCEPTION: main
  2. Process: id.naufalfajar.go, PID: 5383
  3. at id.naufalfajar.go.view.detection.DetectionFragment.getBinding(DetectionFragment.kt:34)
  4. at id.naufalfajar.go.view.detection.DetectionFragment.onResults$lambda$14(DetectionFragment.kt:316)
  5. at id.naufalfajar.go.view.detection.DetectionFragment.$r8$lambda$TqTYva0DaM8jzJqCVNkqO0J7EE4(Unknown Source:0)
  6. at id.naufalfajar.go.view.detection.DetectionFragment$$ExternalSyntheticLambda10.run(Unknown Source:10)
  7. at android.os.Handler.handleCallback(Handler.java:883)
  8. at android.os.Handler.dispatchMessage(Handler.java:100)
  9. at android.os.Looper.loop(Looper.java:224)
  10. at android.app.ActivityThread.main(ActivityThread.java:7590)
  11. at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
  12. at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)

The error is in this line of code.

  1. private val binding get() = _binding!! //DetectionFragment.kt:34
  2. activity?.runOnUiThread {
  3. binding.bottomSheetLayout.inferenceTimeVal.text =
  4. String.format(&quot;%d ms&quot;, inferenceTime) //DetectionFragment.kt:316
  5. ...
  6. }

My guessing is because I have a thread running on my background and when I press back, the binding is set to null.

Anyone know the solution to this?

答案1

得分: 2

预期值应为 null,因为您在 onDestroy 中明确将其设置为 null。理想情况下,当 fragment 不处于活动状态时,您应该取消后台任务,但在您的情况下,可以在访问后台任务中的 binding 之前检查 if(isAdded)

  1. activity?.runOnUiThread {
  2. if (!activity.isFinishing() && isAdded) {
  3. binding.bottomSheetLayout.inferenceTimeVal.text =
  4. String.format("%d ms", inferenceTime) //DetectionFragment.kt:316
  5. // ...
  6. }
  7. // ...
  8. }
英文:

It is expected to be null as you are explicitly setting it to null in onDestroy, Ideally you should cancel the background task when fragment is not active but in your case you could simply check if(isAdded) before accessing binding inside background task.

  1. activity?.runOnUiThread {
  2. if(!activity.isFinishing() &amp;&amp; isAdded) {
  3. binding.bottomSheetLayout.inferenceTimeVal.text =
  4. String.format(&quot;%d ms&quot;, inferenceTime) //DetectionFragment.kt:316
  5. ...
  6. }
  7. ...
  8. }

huangapple
  • 本文由 发表于 2023年6月9日 07:01:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/76436210.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定