You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用自定义接口打开相机后黑屏冻结问题求助

问题描述

我正在开发一款兼具REST API与UI层的Android应用,需求是实现一个API接口,调用该接口时启动设备相机,拍摄后将图片内容以ByteArray形式返回。目前API功能正常,但启动相机的流程存在问题:CameraActivity启动后屏幕黑屏,应用冻结。已添加FileProvider配置并修改图片Uri获取方式,但问题仍未解决。


相关代码与配置

CameraActivity.kt

class CameraActivity : ListenerCompatActivity() {
    companion object{
        const val TAKE_PICTURE = 1
    }
    private val useCase = Application.useCaseManager.deviceManagement
    private lateinit var ui: ActivityCameraBinding
    var timeStamp: String? = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
    var imageFilePath: String = Environment.getExternalStorageDirectory()
        .toString() + "/Android/data/com.my.app/Image-" + timeStamp + ".png"

    private var resultSent = false
    override fun getActivityID(): ActivityID = ActivityID.Camera

    override fun isKeepScreenOn() = true

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ui = ActivityCameraBinding.inflate(layoutInflater)
        setContentView(ui.root)
        ui.footerLogo.load(R.drawable.logo_name_big)
        ui.cmdQRCancel.setOnClickListener{sendCancelResult(Activity.RESULT_CANCELED)}
        startCameraIntent()
    }

    private fun startCameraIntent() {
        /*************************** Camera Intent Start  */
        val imageFile = File(imageFilePath)
        val imageUri = FileProvider.getUriForFile(applicationContext, "******************", imageFile) //Convert file to Uri

        // Standard Intent action that can be sent to have the camera
        // application capture an image and return it.
        val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri) // set the image file name
        startActivityForResult(intent, TAKE_PICTURE)
        /*************************** Camera Intent End  */
    }

    private fun sendCancelResult(resultCode: Int) {
        sendResult(
            resultCode,
            ApiCameraResponse().also { it.setFailed(Messages.CANCEL_BY_USER) }
        )
    }

    private fun sendResult(resultCode: Int, result: ApiCameraResponse) {
        logger.info("Sending result = $result")
        resultSent = true
        //Keep the base64 content
        //TODO: Save camera result
        useCase.setApiCameraResponse(result)
        val msg = MessageFactory.cameraResult(resultCode, result)
        TaskPool.UI.launch {
            if (!sendToService(msg)) {
                logger.error("Failed to send Camera data to service")
            }
            runOnUiThread { finish() }
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        Log.d("*********", "Request Code:$requestCode")
        //For the ImageCapture Activity
        //For the ImageCapture Activity
        if (requestCode == 1) {
            if (resultCode == Activity.RESULT_OK) {
                /*********** Load Captured Image And Data Start  */

                // Decode it for real
                val bmpFactoryOptions = BitmapFactory.Options()
                bmpFactoryOptions.inJustDecodeBounds = false

                //imageFilePath image path which you pass with intent
                val bitmap = BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions)

                val baos = ByteArrayOutputStream()
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos)
                val byteArray = baos.toByteArray()
                Log.d("**********", "byteArray: $byteArray")
                sendResult(
                    resultCode,
                    ApiCameraResponse(ApiCameraResponse.Info(success = true, result = ApiCameraResponse.Result(
                        failed = false,
                        success = true,
                        message = applicationContext.getString(R.string.camera_save_succesfully),
                        cameraContent = Base64.encode(byteArray, Base64.DEFAULT),
                        cameraType = "PNG",
                    )))
                    .also {
                        resultSent = true
                    }
                /*********** Load Captured Image And Data End  */
            } else {
                Toast.makeText(this, " Picture was not taken ", Toast.LENGTH_SHORT)
                    .show()
            }
        }else{
            sendCancelResult(resultCode)
        }
    }
}

DeviceManagmentUseCase.kt

var cameraResponse: ApiCameraResponse = ApiCameraResponse()
    suspend fun openCamera(request:ApiCameraRequest):ApiCameraResponse{
        logger.info("pinpad/openCamera $request")
        var resp = ApiCameraResponse()
        taskController.runShortTask {
            it.uiActivities.startActivity(
                ActivityID.Camera,
                MessageFactory.forFinish()
            )
            resp = cameraResponse
            Log.d("*********", "Camera response en open camera:resp: $resp cameraResponse; $cameraResponse")
        }
        return resp
    }

    fun setApiCameraResponse(resp: ApiCameraResponse){
        cameraResponse = resp
        Log.d("**********", "Camera response en setCameraResponse: cameraResponse: $cameraResponse resp: $resp")
    }

AndroidManifest.xml相关配置

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="***************">
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature
        android:name="android.hardware.camera"
        android:required="true" />
    <!-- 其他权限省略 -->

<application
        <!-- 其他配置省略 -->

        <activity
            android:name=".ui.activities.CameraActivity"
            android:theme="@style/AppTheme.NoActionBar" />

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="***************.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"/>
        </provider>
</application>
</manifest>

res/xml/file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-files-path
        name="intent_images"
        path="Pictures"
        />
</paths>

排查线索
  • 图片路径与FileProvider配置不匹配
    你手动拼接的imageFilePath指向/Android/data/com.my.app/根目录,但file_paths.xml配置的external-files-path对应实际路径是/Android/data/[包名]/files/Pictures,两者不匹配会导致Uri授权失败,相机无法写入文件引发冻结。建议改用系统API生成正确路径:

    val imageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
    imageFilePath = File(imageDir, "Image-${timeStamp}.png").absolutePath
    
  • 主线程阻塞风险
    DeviceManagmentUseCase的openCamera方法中,taskController.runShortTask若在主线程执行,直接等待cameraResponse赋值会阻塞UI线程,导致CameraActivity黑屏。建议用CompletableDeferred实现异步等待:

    private val cameraDeferred = CompletableDeferred<ApiCameraResponse>()
    
    suspend fun openCamera(request: ApiCameraRequest): ApiCameraResponse {
        logger.info("pinpad/openCamera $request")
        taskController.runShortTask {
            it.uiActivities.startActivity(ActivityID.Camera, MessageFactory.forFinish())
        }
        return cameraDeferred.await()
    }
    
    fun setApiCameraResponse(resp: ApiCameraResponse) {
        cameraDeferred.complete(resp)
        Log.d("**********", "Camera response en setCameraResponse: cameraResponse: $cameraResponse resp: $resp")
    }
    
  • 相机权限未动态申请
    Android 6.0+需动态申请CAMERA权限,仅在Manifest声明会导致相机Intent无法正常启动。建议在CameraActivity初始化前检查权限:

    private const val REQUEST_CAMERA_PERMISSION = 2
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION)
            return
        }
        // 后续初始化逻辑
        ui = ActivityCameraBinding.inflate(layoutInflater)
        setContentView(ui.root)
        startCameraIntent()
    }
    
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == REQUEST_CAMERA_PERMISSION) {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                startCameraIntent()
            } else {
                sendCancelResult(Activity.RESULT_CANCELED)
            }
        }
    }
    
  • 相机Intent启动时机过早
    在CameraActivity的onCreate中直接启动相机Intent,此时Activity UI未完全初始化,可能导致窗口管理混乱。建议将startCameraIntent延迟到onResume中执行:

    override fun onResume() {
        super.onResume()
        if (!resultSent) {
            startCameraIntent()
        }
    }
    
  • 图片文件未提前创建
    直接使用File(imageFilePath)但未确保父目录存在或文件已创建,相机应用可能无法写入路径引发异常。添加文件创建逻辑:

    val imageFile = File(imageFilePath)
    imageFile.parentFile?.mkdirs()
    imageFile.createNewFile()
    val imageUri = FileProvider.getUriForFile(applicationContext, "你的authorities", imageFile)
    
  • 线程调度混乱
    sendResult中TaskPool.UI.launch已在UI线程运行,嵌套runOnUiThread { finish() }会导致线程调度异常,直接调用finish()即可:

    TaskPool.UI.launch {
        if (!sendToService(msg)) {
            logger.error("Failed to send Camera data to service")
        }
        finish()
    }
    

内容的提问来源于stack exchange,提问作者Manuel Lucas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 09:07:02