英文:
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:
private val binding get() = _binding ?: return // Return null if _binding is null
activity?.runOnUiThread {
binding?.bottomSheetLayout?.inferenceTimeVal?.text =
String.format("%d ms", inferenceTime)
// ...
}
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 -->
private val PERMISSIONS_REQUIRED = arrayOf(Manifest.permission.CAMERA)
class DetectionFragment : Fragment(), ObjectDetectorHelper.DetectorListener {
private var _binding: FragmentDetectionBinding? = null
private val binding get() = _binding!!
private val TAG = "ObjectDetection"
private lateinit var objectDetectorHelper: ObjectDetectorHelper
private lateinit var bitmapBuffer: Bitmap
private var preview: Preview? = null
private var imageAnalyzer: ImageAnalysis? = null
private var camera: Camera? = null
private var cameraProvider: ProcessCameraProvider? = null
/** Blocking camera operations are performed using this executor */
private lateinit var cameraExecutor: ExecutorService
private val requestPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { ...
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
_binding = FragmentDetectionBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onDestroyView() {
_binding = null
super.onDestroyView()
// Shut down our background executor
cameraExecutor.shutdown()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
onBack()
when {
//checkpermission thing
}
objectDetectorHelper = ObjectDetectorHelper(
context = requireContext(),
objectDetectorListener = this)
// Initialize our background executor
cameraExecutor = Executors.newSingleThreadExecutor()
// Wait for the views to be properly laid out
binding.viewFinder.post {
// Set up the camera and its use cases
setUpCamera()
}
// Attach listeners to UI control widgets
initBottomSheetControls()
}
private fun initBottomSheetControls(){
// When clicked, lower detection score threshold floor
binding.bottomSheetLayout.thresholdMinus.setOnClickListener {
if (objectDetectorHelper.threshold >= 0.1) {
objectDetectorHelper.threshold -= 0.1f
updateControlsUi()
}
}
// When clicked, raise detection score threshold floor
binding.bottomSheetLayout.thresholdPlus.setOnClickListener {
if (objectDetectorHelper.threshold <= 0.8) {
objectDetectorHelper.threshold += 0.1f
updateControlsUi()
}
}
// When clicked, reduce the number of objects that can be detected at a time
binding.bottomSheetLayout.maxResultsMinus.setOnClickListener {
if (objectDetectorHelper.maxResults > 1) {
objectDetectorHelper.maxResults--
updateControlsUi()
}
}
// When clicked, increase the number of objects that can be detected at a time
binding.bottomSheetLayout.maxResultsPlus.setOnClickListener {
if (objectDetectorHelper.maxResults < 5) {
objectDetectorHelper.maxResults++
updateControlsUi()
}
}
// When clicked, decrease the number of threads used for detection
binding.bottomSheetLayout.threadsMinus.setOnClickListener {
if (objectDetectorHelper.numThreads > 1) {
objectDetectorHelper.numThreads--
updateControlsUi()
}
}
// When clicked, increase the number of threads used for detection
binding.bottomSheetLayout.threadsPlus.setOnClickListener {
if (objectDetectorHelper.numThreads < 4) {
objectDetectorHelper.numThreads++
updateControlsUi()
}
}
// When clicked, change the underlying hardware used for inference. Current options are CPU
// GPU, and NNAPI
binding.bottomSheetLayout.spinnerDelegate.setSelection(0, false)
binding.bottomSheetLayout.spinnerDelegate.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
objectDetectorHelper.currentDelegate = p2
updateControlsUi()
}
override fun onNothingSelected(p0: AdapterView<*>?) {
/* no op */
}
}
// When clicked, change the underlying model used for object detection
binding.bottomSheetLayout.spinnerModel.setSelection(0, false)
binding.bottomSheetLayout.spinnerModel.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
objectDetectorHelper.currentModel = p2
updateControlsUi()
}
override fun onNothingSelected(p0: AdapterView<*>?) {
/* no op */
}
}
}
private fun updateControlsUi() {
binding.bottomSheetLayout.maxResultsValue.text =
objectDetectorHelper.maxResults.toString()
binding.bottomSheetLayout.thresholdValue.text =
String.format("%.2f", objectDetectorHelper.threshold)
binding.bottomSheetLayout.threadsValue.text =
objectDetectorHelper.numThreads.toString()
// Needs to be cleared instead of reinitialized because the GPU
// delegate needs to be initialized on the thread using it when applicable
objectDetectorHelper.clearObjectDetector()
binding.overlay.clear()
}
// Initialize CameraX, and prepare to bind the camera use cases
private fun setUpCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener(
{
// CameraProvider
cameraProvider = cameraProviderFuture.get()
// Build and bind the camera use cases
bindCameraUseCases()
},
ContextCompat.getMainExecutor(requireContext())
)
}
@Suppress("DEPRECATION")
private fun bindCameraUseCases() {
// CameraProvider
val cameraProvider =
cameraProvider ?: throw IllegalStateException("Camera initialization failed.")
// CameraSelector - makes assumption that we're only using the back camera
val cameraSelector =
CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
// Preview. Only using the 4:3 ratio because this is the closest to our models
preview =
Preview.Builder()
.setTargetAspectRatio(AspectRatio.RATIO_4_3)
.setTargetRotation(binding.viewFinder.display.rotation)
.build()
// ImageAnalysis. Using RGBA 8888 to match how our models work
imageAnalyzer =
ImageAnalysis.Builder()
.setTargetAspectRatio(AspectRatio.RATIO_4_3)
.setTargetRotation(binding.viewFinder.display.rotation)
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
.build()
// The analyzer can then be assigned to the instance
.also {
it.setAnalyzer(cameraExecutor) { image ->
if (!::bitmapBuffer.isInitialized) {
// The image rotation and RGB image buffer are initialized only once
// the analyzer has started running
bitmapBuffer = Bitmap.createBitmap(
image.width,
image.height,
Bitmap.Config.ARGB_8888
)
}
detectObjects(image)
}
}
// Must unbind the use-cases before rebinding them
cameraProvider.unbindAll()
try {
// A variable number of use-cases can be passed here -
// camera provides access to CameraControl & CameraInfo
camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalyzer)
// Attach the viewfinder's surface provider to preview use case
preview?.setSurfaceProvider(binding.viewFinder.surfaceProvider)
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}
private fun detectObjects(image: ImageProxy) {
// Copy out RGB bits to the shared bitmap buffer
image.use { bitmapBuffer.copyPixelsFromBuffer(image.planes[0].buffer) }
val imageRotation = image.imageInfo.rotationDegrees
// Pass Bitmap and rotation to the object detector helper for processing and detection
objectDetectorHelper.detect(bitmapBuffer, imageRotation)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
imageAnalyzer?.targetRotation = binding.viewFinder.display.rotation
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
override fun onError(error: String) {
activity?.runOnUiThread {
Toast.makeText(requireContext(), error, Toast.LENGTH_SHORT).show()
}
}
// Update UI after objects have been detected. Extracts original image height/width
// to scale and place bounding boxes properly through OverlayView
override fun onResults(
results: MutableList<Detection>?,
inferenceTime: Long,
imageHeight: Int,
imageWidth: Int
) {
activity?.runOnUiThread {
binding.bottomSheetLayout.inferenceTimeVal.text =
String.format("%d ms", inferenceTime)
// Pass necessary information to OverlayView for drawing on the canvas
binding.overlay.setResults(
results ?: LinkedList<Detection>(),
imageHeight,
imageWidth
)
// Force a redraw
binding.overlay.invalidate()
}
}
}
And I have this error when I press back button on this screen and immediately close the app.
FATAL EXCEPTION: main
Process: id.naufalfajar.go, PID: 5383
at id.naufalfajar.go.view.detection.DetectionFragment.getBinding(DetectionFragment.kt:34)
at id.naufalfajar.go.view.detection.DetectionFragment.onResults$lambda$14(DetectionFragment.kt:316)
at id.naufalfajar.go.view.detection.DetectionFragment.$r8$lambda$TqTYva0DaM8jzJqCVNkqO0J7EE4(Unknown Source:0)
at id.naufalfajar.go.view.detection.DetectionFragment$$ExternalSyntheticLambda10.run(Unknown Source:10)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:224)
at android.app.ActivityThread.main(ActivityThread.java:7590)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
The error is in this line of code.
private val binding get() = _binding!! //DetectionFragment.kt:34
activity?.runOnUiThread {
binding.bottomSheetLayout.inferenceTimeVal.text =
String.format("%d ms", inferenceTime) //DetectionFragment.kt:316
...
}
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)
。
activity?.runOnUiThread {
if (!activity.isFinishing() && isAdded) {
binding.bottomSheetLayout.inferenceTimeVal.text =
String.format("%d ms", inferenceTime) //DetectionFragment.kt:316
// ...
}
// ...
}
英文:
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.
activity?.runOnUiThread {
if(!activity.isFinishing() && isAdded) {
binding.bottomSheetLayout.inferenceTimeVal.text =
String.format("%d ms", inferenceTime) //DetectionFragment.kt:316
...
}
...
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论