CameraX перейти с альфа-04 на бета-01 сломал код - PullRequest
1 голос
/ 04 марта 2020

Я недавно добавил CameraX в свое приложение для разработки go. Я знаю, что это было в альфа-версии, но я был готов внести изменения, когда будет доступна бета-версия или финальная версия.

Так что я начал работать над этим сегодня. Я обновил с

implementation 'androidx.camera:camera-core:1.0.0-alpha04'
implementation 'androidx.camera:camera-camera2:1.0.0-alpha04'

до этого:

implementation 'androidx.camera:camera-core:1.0.0-beta01'
implementation 'androidx.camera:camera-camera2:1.0.0-beta01'
implementation 'androidx.camera:camera-lifecycle:1.0.0-beta01'

Мой предыдущий рабочий код (альфа-04):

class ScannerX : AppCompatActivity() {
private lateinit var context: Context

var isOtpAuthCode = true

private val immersiveFlagTimeout = 500L
private val flagsFullscreen = View.SYSTEM_UI_FLAG_LOW_PROFILE or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION

private var preview: Preview? = null
private var lensFacing = CameraX.LensFacing.BACK
private var imageAnalyzer: ImageAnalysis? = null

private lateinit var analyzerThread: HandlerThread

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_scanner_x)

    context = this

    btnCancel.setOnClickListener {
        finish()
    }

    analyzerThread = if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS) {
        HandlerThread("BarcodeFirebaseAnalyzer").apply { start() }
    } else {
        HandlerThread("BarcodeZxingAnalyzer").apply { start() }
    }

    Dexter.withActivity(this)
        .withPermissions(Manifest.permission.CAMERA)
        .withListener(object : MultiplePermissionsListener {
            override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
                textureView.post {
                    val metrics = DisplayMetrics().also { textureView.display.getRealMetrics(it) }
                    val screenAspectRatio = Rational(metrics.widthPixels, metrics.heightPixels)

                    val previewConfig = PreviewConfig.Builder().apply {
                        setLensFacing(lensFacing)
                        // We request aspect ratio but no resolution to let CameraX optimize our use cases
                        setTargetAspectRatio(screenAspectRatio)
                        // Set initial target rotation, we will have to call this again if rotation changes
                        // during the lifecycle of this use case
                        setTargetRotation(textureView.display.rotation)
                    }.build()

                    val analyzerConfig = ImageAnalysisConfig.Builder().apply {
                        setLensFacing(lensFacing)
                        // Use a worker thread for image analysis to prevent preview glitches
                        setCallbackHandler(Handler(analyzerThread.looper))
                        // In our analysis, we care more about the latest image than analyzing *every* image
                        setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
                        // Set initial target rotation, we will have to call this again if rotation changes
                        // during the lifecycle of this use case
                        setTargetRotation(textureView.display.rotation)
                    }.build()

                    preview = AutoFitPreviewBuilder.build(previewConfig, textureView)

                    imageAnalyzer = ImageAnalysis(analyzerConfig).apply {
                        analyzer = if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS) {
                            BarcodeFirebaseAnalyzer { qrCode ->
                                if (isOtpAuthCode) {
                                    if (qrCode.startsWith("otpauth")) {
                                        toAddAuth(qrCode)
                                    }
                                } else {
                                    toAddAuth(qrCode)
                                }
                            }
                        } else {
                            BarcodeZxingAnalyzer { qrCode ->
                                if (isOtpAuthCode) {
                                    if (qrCode.startsWith("otpauth")) {
                                        toAddAuth(qrCode)
                                    }
                                } else {
                                    toAddAuth(qrCode)
                                }
                            }
                        }
                    }

                    // Apply declared configs to CameraX using the same lifecycle owner
                    CameraX.bindToLifecycle(this@ScannerX, preview, imageAnalyzer)
                }
            }

            override fun onPermissionRationaleShouldBeShown(permissions: MutableList<PermissionRequest>?, token: PermissionToken?) {
                //
            }
        }).check()
}

override fun onStart() {
    super.onStart()
    // Before setting full screen flags, we must wait a bit to let UI settle; otherwise, we may
    // be trying to set app to immersive mode before it's ready and the flags do not stick
    textureView.postDelayed({
        textureView.systemUiVisibility = flagsFullscreen
    }, immersiveFlagTimeout)
}

override fun onDestroy() {
    analyzerThread.quit()
    super.onDestroy()
}

private fun toAddAuth(scannedCode: String) {
    if (CameraX.isBound(imageAnalyzer)) {
        CameraX.unbind(imageAnalyzer)
    }

    val intent = Intent()
    intent.putExtra("scanResult", scannedCode)
    setResult(RESULT_OK, intent)
    finish()
}

companion object {
    private const val RESULT_OK = 666
}
}

И код, который я изменил (бета-01):

class ScannerX : AppCompatActivity() {
private lateinit var context: Context

var isOtpAuthCode = true

private val immersiveFlagTimeout = 500L
private val flagsFullscreen = View.SYSTEM_UI_FLAG_LOW_PROFILE or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION

private var preview: Preview? = null
private var lensFacing = CameraSelector.DEFAULT_BACK_CAMERA
private var imageAnalyzer: ImageAnalysis? = null

private lateinit var analysisExecutor: ExecutorService
private lateinit var processCameraProvider: ListenableFuture<ProcessCameraProvider>

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_scanner_x)

    context = this

    btnCancel.setOnClickListener {
        finish()
    }

    Dexter.withActivity(this)
        .withPermissions(Manifest.permission.CAMERA)
        .withListener(object : MultiplePermissionsListener {
            override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
                textureView.post {
                    analysisExecutor = Executors.newSingleThreadExecutor()
                    processCameraProvider = ProcessCameraProvider.getInstance(context)

                    preview = Preview.Builder()
                        .setTargetAspectRatio(AspectRatio.RATIO_16_9)
                        .setTargetRotation(textureView.display.rotation)
                        .build()

                    imageAnalyzer = ImageAnalysis.Builder()
                        .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                        .setTargetRotation(textureView.display.rotation)
                        .build()

                    if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS) {
                        imageAnalyzer?.apply {
                            setAnalyzer(analysisExecutor, BarcodeFirebaseAnalyzer { qrCode ->
                                if (isOtpAuthCode) {
                                    if (qrCode.startsWith("otpauth")) {
                                        toAddAuth(qrCode)
                                    }
                                } else {
                                    toAddAuth(qrCode)
                                }
                            })
                        }
                    } else {
                        imageAnalyzer?.apply {
                            setAnalyzer(analysisExecutor, BarcodeZxingAnalyzer { qrCode ->
                                if (isOtpAuthCode) {
                                    if (qrCode.startsWith("otpauth")) {
                                        toAddAuth(qrCode)
                                    }
                                } else {
                                    toAddAuth(qrCode)
                                }
                            })
                        }
                    }

                    processCameraProvider.get().bindToLifecycle(this@ScannerX, lensFacing, imageAnalyzer)
                }
            }

            override fun onPermissionRationaleShouldBeShown(permissions: MutableList<PermissionRequest>?, token: PermissionToken?) {
                //
            }
        }).check()
}

override fun onStart() {
    super.onStart()
    // Before setting full screen flags, we must wait a bit to let UI settle; otherwise, we may
    // be trying to set app to immersive mode before it's ready and the flags do not stick
    textureView.postDelayed({
        textureView.systemUiVisibility = flagsFullscreen
    }, immersiveFlagTimeout)
}

override fun onDestroy() {
    if (!analysisExecutor.isShutdown) {
        analysisExecutor.shutdown()
    }

    super.onDestroy()
}

private fun toAddAuth(scannedCode: String) {
    /*if (CameraX.isBound(imageAnalyzer)) {
        CameraX.unbind(imageAnalyzer)
    }*/

    val intent = Intent()
    intent.putExtra("scanResult", scannedCode)
    setResult(RESULT_OK, intent)
    finish()
}

companion object {
    private const val RESULT_OK = 666
}
}

После того, как я обновил, в библиотеке было так много изменений, и теперь я не могу заставить ее работать.

Я также не могу использовать предоставленный Google класс AutoFitPreview вместе с первоначальным альфа-релизом этой библиотеки. Это не было необходимо даже с alpha04, так как единственной проблемой без этого класса было немного растянутое изображение с камеры, но сканирование и анализ работали правильно.

/**
* Builder for [Preview] that takes in a [WeakReference] of the view finder and [PreviewConfig],
* then instantiates a [Preview] which automatically resizes and rotates reacting to config changes.
*/
class AutoFitPreviewBuilder private constructor(config: PreviewConfig, viewFinderRef: WeakReference<TextureView>) {

/** Public instance of preview use-case which can be used by consumers of this adapter */
val useCase: Preview

/** Internal variable used to keep track of the use case's output rotation */
private var bufferRotation: Int = 0

/** Internal variable used to keep track of the view's rotation */
private var viewFinderRotation: Int? = null

/** Internal variable used to keep track of the use-case's output dimension */
private var bufferDimens: Size = Size(0, 0)

/** Internal variable used to keep track of the view's dimension */
private var viewFinderDimens: Size = Size(0, 0)

/** Internal variable used to keep track of the view's display */
private var viewFinderDisplay: Int = -1

/** Internal reference of the [DisplayManager] */
private lateinit var displayManager: DisplayManager

/**
 * We need a display listener for orientation changes that do not trigger a configuration
 * change, for example if we choose to override config change in manifest or for 180-degree
 * orientation changes.
 */
private val displayListener = object : DisplayManager.DisplayListener {
    override fun onDisplayAdded(displayId: Int) = Unit
    override fun onDisplayRemoved(displayId: Int) = Unit
    override fun onDisplayChanged(displayId: Int) {
        val viewFinder = viewFinderRef.get() ?: return
        if (displayId == viewFinderDisplay) {
            val display = displayManager.getDisplay(displayId)
            val rotation = getDisplaySurfaceRotation(display)
            updateTransform(viewFinder, rotation, bufferDimens, viewFinderDimens)
        }
    }
}

init {
    // Make sure that the view finder reference is valid
    val viewFinder = viewFinderRef.get() ?:
    throw IllegalArgumentException("Invalid reference to view finder used")

    // Initialize the display and rotation from texture view information
    viewFinderDisplay = viewFinder.display.displayId
    viewFinderRotation = getDisplaySurfaceRotation(viewFinder.display) ?: 0

    // Initialize public use-case with the given config
    useCase = Preview(config)

    // Every time the view finder is updated, recompute layout
    useCase.onPreviewOutputUpdateListener = Preview.OnPreviewOutputUpdateListener {
        val viewFinderI = viewFinderRef.get() ?: return@OnPreviewOutputUpdateListener
        Log.d(TAG, "Preview output changed. " +
                "Size: ${it.textureSize}. Rotation: ${it.rotationDegrees}")

        // To update the SurfaceTexture, we have to remove it and re-add it
        val parent = viewFinderI.parent as ViewGroup
        parent.removeView(viewFinderI)
        parent.addView(viewFinderI, 0)

        // Update internal texture
        viewFinderI.surfaceTexture = it.surfaceTexture

        // Apply relevant transformations
        bufferRotation = it.rotationDegrees
        val rotation = getDisplaySurfaceRotation(viewFinderI.display)
        updateTransform(viewFinderI, rotation, it.textureSize, viewFinderDimens)
    }

    // Every time the provided texture view changes, recompute layout
    viewFinder.addOnLayoutChangeListener { view, left, top, right, bottom, _, _, _, _ ->
        val viewFinderII = view as TextureView
        val newViewFinderDimens = Size(right - left, bottom - top)
        Log.d(TAG, "View finder layout changed. Size: $newViewFinderDimens")
        val rotation = getDisplaySurfaceRotation(viewFinderII.display)
        updateTransform(viewFinderII, rotation, bufferDimens, newViewFinderDimens)
    }

    // Every time the orientation of device changes, recompute layout
    // NOTE: This is unnecessary if we listen to display orientation changes in the camera
    //  fragment and call [Preview.setTargetRotation()] (like we do in this sample), which will
    //  trigger [Preview.OnPreviewOutputUpdateListener] with a new
    //  [PreviewOutput.rotationDegrees]. CameraX Preview use case will not rotate the frames for
    //  us, it will just tell us about the buffer rotation with respect to sensor orientation.
    //  In this sample, we ignore the buffer rotation and instead look at the view finder's
    //  rotation every time [updateTransform] is called, which gets triggered by
    //  [CameraFragment] display listener -- but the approach taken in this sample is not the
    //  only valid one.
    displayManager = viewFinder.context
        .getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
    displayManager.registerDisplayListener(displayListener, null)

    // Remove the display listeners when the view is detached to avoid holding a reference to
    //  it outside of the Fragment that owns the view.
    // NOTE: Even though using a weak reference should take care of this, we still try to avoid
    //  unnecessary calls to the listener this way.
    viewFinder.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
        override fun onViewAttachedToWindow(view: View?) =
            displayManager.registerDisplayListener(displayListener, null)
        override fun onViewDetachedFromWindow(view: View?) =
            displayManager.unregisterDisplayListener(displayListener)
    })
}

/** Helper function that fits a camera preview into the given [TextureView] */
private fun updateTransform(textureView: TextureView?, rotation: Int?, newBufferDimens: Size, newViewFinderDimens: Size) {
    // This should not happen anyway, but now the linter knows
    val textureViewI = textureView ?: return

    if (rotation == viewFinderRotation &&
        Objects.equals(newBufferDimens, bufferDimens) &&
        Objects.equals(newViewFinderDimens, viewFinderDimens)) {
        // Nothing has changed, no need to transform output again
        return
    }

    if (rotation == null) {
        // Invalid rotation - wait for valid inputs before setting matrix
        return
    } else {
        // Update internal field with new inputs
        viewFinderRotation = rotation
    }

    if (newBufferDimens.width == 0 || newBufferDimens.height == 0) {
        // Invalid buffer dimens - wait for valid inputs before setting matrix
        return
    } else {
        // Update internal field with new inputs
        bufferDimens = newBufferDimens
    }

    if (newViewFinderDimens.width == 0 || newViewFinderDimens.height == 0) {
        // Invalid view finder dimens - wait for valid inputs before setting matrix
        return
    } else {
        // Update internal field with new inputs
        viewFinderDimens = newViewFinderDimens
    }

    val matrix = Matrix()
    Log.d(TAG, "Applying output transformation.\n" +
            "View finder size: $viewFinderDimens.\n" +
            "Preview output size: $bufferDimens\n" +
            "View finder rotation: $viewFinderRotation\n" +
            "Preview output rotation: $bufferRotation")

    // Compute the center of the view finder
    val centerX = viewFinderDimens.width / 2f
    val centerY = viewFinderDimens.height / 2f

    // Correct preview output to account for display rotation
    matrix.postRotate(-viewFinderRotation!!.toFloat(), centerX, centerY)

    // Buffers are rotated relative to the device's 'natural' orientation: swap width and height
    val bufferRatio = bufferDimens.height / bufferDimens.width.toFloat()

    val scaledWidth: Int
    val scaledHeight: Int
    // Match longest sides together -- i.e. apply center-crop transformation
    if (viewFinderDimens.width > viewFinderDimens.height) {
        scaledHeight = viewFinderDimens.width
        scaledWidth = (viewFinderDimens.width * bufferRatio).roundToInt()
    } else {
        scaledHeight = viewFinderDimens.height
        scaledWidth = (viewFinderDimens.height * bufferRatio).roundToInt()
    }

    // Compute the relative scale value
    val xScale = scaledWidth / viewFinderDimens.width.toFloat()
    val yScale = scaledHeight / viewFinderDimens.height.toFloat()

    // Scale input buffers to fill the view finder
    matrix.preScale(xScale, yScale, centerX, centerY)

    // Finally, apply transformations to our TextureView
    textureViewI.setTransform(matrix)
}

companion object {
    private val TAG = AutoFitPreviewBuilder::class.java.simpleName

    /** Helper function that gets the rotation of a [Display] in degrees */
    fun getDisplaySurfaceRotation(display: Display?) = when(display?.rotation) {
        Surface.ROTATION_0 -> 0
        Surface.ROTATION_90 -> 90
        Surface.ROTATION_180 -> 180
        Surface.ROTATION_270 -> 270
        else -> null
    }

    /**
     * Main entry point for users of this class: instantiates the adapter and returns an instance
     * of [Preview] which automatically adjusts in size and rotation to compensate for
     * config changes.
     */
    fun build(config: PreviewConfig, viewFinder: TextureView) =
        AutoFitPreviewBuilder(config, WeakReference(viewFinder)).useCase
}
}

Пожалуйста, помогите

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...