Compose Multiplatform应用iOS端相册访问异常排查求助
Compose Multiplatform iOS端相册访问异常(PAErrorDomain Code=10 & com.apple.accounts Code=7)解决指南
错误含义解析
- PAErrorDomain Code=10:权限访问会话被系统守护进程强制终止,通常由权限请求流程不规范、重复请求或状态处理逻辑漏洞导致
- com.apple.accounts Code=7:与iOS账号/权限框架交互时出现异常,大概率和权限请求时机、UI线程处理不当有关
问题定位与修复方案
1. 权限状态未实时监听,请求API未适配iOS 14+
iOS 14及以上新增了「Limited相册权限」,旧版权限请求API虽能返回状态,但无法完整支持细粒度权限流程;同时原代码未监听系统权限变更(比如用户去设置中修改权限后返回App),导致状态不一致。
修改RequestPhotoLibraryPermission代码:
actual class RequestPhotoLibraryPermission { @Composable actual fun RequestCameraPermission( onPermissionGranted: @Composable () -> Unit, onPermissionDenied: @Composable () -> Unit ) { var permissionStatus by remember { mutableStateOf(PHAuthorizationStatusNotDetermined) } // 初始化权限状态并发起请求 LaunchedEffect(Unit) { permissionStatus = PHPhotoLibrary.authorizationStatus() if (permissionStatus == PHAuthorizationStatusNotDetermined) { permissionStatus = if (UIDevice.currentDevice.systemVersion.toDouble() >= 14.0) { requestLimitedPhotoLibraryPermission() } else { requestPhotoLibraryPermission() } } } // 监听系统权限变更通知 DisposableEffect(Unit) { val observer = NSNotificationCenter.defaultCenter.addObserverForName( PHPhotoLibraryChangeNotification, null, NSOperationQueue.mainQueue, ) { _ -> permissionStatus = PHPhotoLibrary.authorizationStatus() } onDispose { NSNotificationCenter.defaultCenter.removeObserver(observer) } } when (permissionStatus) { PHAuthorizationStatusAuthorized, PHAuthorizationStatusLimited -> onPermissionGranted() PHAuthorizationStatusDenied, PHAuthorizationStatusRestricted -> onPermissionDenied() else -> Unit // 等待权限请求结果 } } private suspend fun requestPhotoLibraryPermission(): PHAuthorizationStatus = suspendCancellableCoroutine { continuation -> PHPhotoLibrary.requestAuthorization { status -> continuation.resume(status) } } // 适配iOS 14+的Limited权限请求 private suspend fun requestLimitedPhotoLibraryPermission(): PHAuthorizationStatus = suspendCancellableCoroutine { continuation -> PHPhotoLibrary.requestAuthorization(for: PHAuthorizationStatusLimited) { status -> continuation.resume(status) } } }
2. ImagePicker复用UIImagePickerController实例导致异常
UIImagePickerController不支持重复present,原代码全局复用实例会触发视图控制器栈异常,进而引发权限相关系统日志错误。
修改ImagePicker代码:
actual class ImagePicker { private var onImagePickedCallback: ((ByteArray?) -> Unit)? = null @Composable actual fun PickImage(onImagePicked: (ByteArray?) -> Unit) { val scope = rememberCoroutineScope() LaunchedEffect(Unit) { scope.launch(Dispatchers.Main) { showImagePicker(onImagePicked) } } } private suspend fun showImagePicker(onImagePicked: (ByteArray?) -> Unit) = suspendCancellableCoroutine { continuation -> onImagePickedCallback = { bytes -> onImagePicked(bytes) continuation.resume(Unit) } // 每次选图创建新的UIImagePickerController实例 val pickerController = UIImagePickerController().apply { sourceType = UIImagePickerControllerSourceType.UIImagePickerControllerSourceTypePhotoLibrary delegate = object : NSObject(), UIImagePickerControllerDelegateProtocol, UINavigationControllerDelegateProtocol { override fun imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo: Map<Any?, *>) { val image = didFinishPickingMediaWithInfo[UIImagePickerControllerOriginalImage] as? UIImage picker.dismissViewControllerAnimated(true) { val bytes = image?.let { UIImageJPEGRepresentation(it, 0.8)?.toByteArray() } onImagePickedCallback?.invoke(bytes) onImagePickedCallback = null } } override fun imagePickerControllerDidCancel(picker: UIImagePickerController) { picker.dismissViewControllerAnimated(true) { onImagePickedCallback?.invoke(null) onImagePickedCallback = null } } } } UIApplication.sharedApplication.keyWindow?.rootViewController?.let { rootViewController -> rootViewController.presentViewController(pickerController, animated = true, completion = null) } ?: run { onImagePickedCallback?.invoke(null) continuation.resume(Unit) } } } @OptIn(ExperimentalForeignApi::class) fun NSData.toByteArray(): ByteArray = ByteArray(this.length.toInt()).apply { usePinned { memcpy(it.addressOf(0), this@toByteArray.bytes, this@toByteArray.length) } }
3. 权限确认与图片选择的时序问题
原代码未确保权限完全授予后再触发选图,可能导致权限状态不一致时调用相册API。修改PostScreen逻辑:
@Composable fun PostScreen(onEventSubmitted: () -> Unit) { val viewModel: PostsViewModel = KoinPlatform.getKoin().get() val imagePicker: ImagePicker = KoinPlatform.getKoin().get() val cameraPermissionManager = remember { RequestPhotoLibraryPermission() } val uiState by viewModel.uiState.collectAsState() var selectedImage by remember { mutableStateOf<ByteArray?>(null) } var isPermissionGranted by remember { mutableStateOf(false) } cameraPermissionManager.RequestCameraPermission( onPermissionGranted = { isPermissionGranted = true AddPostEvent( selectedImage = selectedImage, onEventSubmitted = onEventSubmitted, setEvent = { event -> viewModel.setEvent(event) }) }, onPermissionDenied = { isPermissionGranted = false BodyMedium(text = "La fotocamera non è disponibile senza permessi") }) // 仅在权限授予且需要选图时触发 if (uiState.pickImage && isPermissionGranted) { LaunchedEffect(Unit) { imagePicker.PickImage { imageBytes -> viewModel.setEvent(PostsEvents.OnPickImage(false)) selectedImage = imageBytes } } } LaunchedEffect(viewModel) { viewModel.effect.collect { effect -> when (effect) { is PostsUIEffects.ShowSuccess -> { println("XXXXX success") } is PostsUIEffects.ShowErrorMessage -> { println("XXXXX error: ${effect.message}") } } } } }
4. Info.plist权限描述检查
确保iOS项目Info.plist中添加正确的权限描述,否则系统会直接拒绝权限请求:
<key>NSPhotoLibraryUsageDescription</key> <string>需要访问相册以选择图片</string> <!-- iOS 14+ 可选,针对Limited权限 --> <key>NSPhotoLibraryAddUsageDescription</key> <string>需要访问相册以保存图片</string>
内容的提问来源于stack exchange,提问作者DoctorWho
相关产品推荐
相关产品推荐

