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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 09:37:32