调用自定义接口打开相机后黑屏冻结问题求助
我正在开发一款兼具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

