news 2026/9/28 2:18:39

【Unity】安卓相机与相册权限适配实战:从6.0到高版本兼容方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Unity】安卓相机与相册权限适配实战:从6.0到高版本兼容方案

1. 安卓权限管理机制演进与Unity适配挑战

安卓6.0引入的动态权限系统彻底改变了应用获取敏感权限的方式。我记得第一次在Unity项目里调用相机功能时,明明在AndroidManifest.xml里声明了权限,却遭遇了闪退事故。后来才发现,像相机、存储这类危险权限(Dangerous Permissions)必须运行时动态申请。

动态权限的核心机制其实很简单:应用启动时只有基础权限,需要用户敏感数据时再弹窗申请。但Unity作为跨平台引擎,默认不处理这些原生系统特性,这就导致三个典型问题:

  1. 权限弹窗闪现消失:Unity的Activity会拦截系统弹窗
  2. Android 7.0文件共享崩溃:File Uri暴露路径被禁止
  3. Android 10分区存储:直接访问外部存储受限

这里有个容易忽略的细节:不同厂商ROM对权限弹窗的处理差异很大。比如小米会默认关闭某些权限的弹窗,需要引导用户去设置页手动开启。我在华为P30上测试时,就发现相机权限弹窗的样式和原生安卓有明显区别。

2. 基础权限配置与动态请求实战

2.1 清单文件声明

首先在Assets/Plugins/Android/AndroidManifest.xml中添加必要权限声明:

<uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

注意Android 10+需要添加requestLegacyExternalStorage属性应对分区存储:

<application android:requestLegacyExternalStorage="true" ... >

2.2 Unity中的动态权限请求

创建PermissionRequester.cs脚本处理动态逻辑:

#if UNITY_ANDROID using UnityEngine; using UnityEngine.Android; public class PermissionRequester : MonoBehaviour { const int CAMERA_CODE = 1001; const int STORAGE_CODE = 1002; public void RequestCameraPermission() { if (Permission.HasUserAuthorizedPermission(Permission.Camera)) { OpenCamera(); } else { var callbacks = new PermissionCallbacks(); callbacks.PermissionGranted += _ => OpenCamera(); Permission.RequestUserPermission(Permission.Camera, callbacks); } } public void RequestStoragePermission() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 6.0以下版本无需动态申请 AccessGallery(); return; } if (Permission.HasUserAuthorizedPermission(Permission.ExternalStorageRead)) { AccessGallery(); } else { var callbacks = new PermissionCallbacks(); callbacks.PermissionGranted += _ => AccessGallery(); Permission.RequestUserPermission(Permission.ExternalStorageRead, callbacks); } } void OpenCamera() { /* 相机逻辑 */ } void AccessGallery() { /* 相册逻辑 */ } } #endif

这个方案比原生Android代码简洁很多,利用了Unity 2018.3引入的PermissionAPI。但要注意几个坑点:

  1. iOS需要额外处理NSCameraUsageDescription描述
  2. 华为设备上可能需要检查CanRequestPermission()返回值
  3. 用户拒绝后再次请求时需要解释必要性

3. Android 7.0+文件共享方案

3.1 FileProvider配置

在AndroidManifest.xml的<application>标签内添加:

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

创建Assets/Plugins/Android/res/xml/file_paths.xml:

<?xml version="1.0" encoding="utf-8"?> <paths> <external-path name="external_files" path="."/> <cache-path name="cache_files" path="."/> </paths>

3.2 Unity与Android交互

修改相机调用代码处理版本差异:

void TakePhoto() { string path = Path.Combine(Application.persistentDataPath, "photo.jpg"); AndroidJavaObject intent = new AndroidJavaObject("android.content.Intent", "android.media.action.IMAGE_CAPTURE"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { AndroidJavaClass fileProvider = new AndroidJavaClass("androidx.core.content.FileProvider"); AndroidJavaObject file = new AndroidJavaObject("java.io.File", path); AndroidJavaObject uri = fileProvider.CallStatic<AndroidJavaObject>( "getUriForFile", GetCurrentActivity(), Application.identifier + ".fileprovider", file); intent.Call<AndroidJavaObject>("putExtra", "android.media.action.IMAGE_CAPTURE", uri); } else { AndroidJavaObject uri = new AndroidJavaObject("android.net.Uri", "file://" + path); intent.Call<AndroidJavaObject>("putExtra", "android.media.action.IMAGE_CAPTURE", uri); } GetCurrentActivity().Call("startActivityForResult", intent, PHOTO_CODE); }

这里有个实际项目中的经验:部分国产ROM会修改相机应用包名,建议先检查设备是否支持标准ACTION_IMAGE_CAPTURE:

bool IsCameraAvailable() { AndroidJavaObject packageManager = GetCurrentActivity() .Call<AndroidJavaObject>("getPackageManager"); AndroidJavaObject intent = new AndroidJavaObject( "android.content.Intent", "android.media.action.IMAGE_CAPTURE"); return intent.Call<bool>("resolveActivity", packageManager) != null; }

4. 完整实现方案与优化建议

4.1 相册访问最佳实践

对于相册访问,推荐直接使用Intent.ACTION_GET_CONTENT避免文件路径问题:

void OpenGallery() { AndroidJavaObject intent = new AndroidJavaObject( "android.content.Intent", "android.intent.action.GET_CONTENT"); intent.Call<AndroidJavaObject>("setType", "image/*"); GetCurrentActivity().Call("startActivityForResult", intent, GALLERY_CODE); }

处理返回结果时,建议使用内容解析器获取真实路径:

// 在Android插件代码中 public static String getRealPathFromUri(Context context, Uri uri) { String path = ""; if (context.getContentResolver() != null) { try (Cursor cursor = context.getContentResolver() .query(uri, null, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { int idx = cursor.getColumnIndex(MediaStore.Images.Media.DATA); path = cursor.getString(idx); } } } return path; }

4.2 性能优化技巧

  1. 纹理压缩:大图加载前先采样

    Texture2D CompressTexture(Texture2D source, int maxSize) { int width = Mathf.Min(source.width, maxSize); int height = Mathf.Min(source.height, maxSize); var rt = RenderTexture.GetTemporary(width, height); Graphics.Blit(source, rt); var result = new Texture2D(width, height); RenderTexture.active = rt; result.ReadPixels(new Rect(0, 0, width, height), 0, 0); result.Apply(); RenderTexture.ReleaseTemporary(rt); return result; }
  2. 异步加载:避免主线程卡顿

    IEnumerator LoadImageAsync(string path) { using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(path)) { yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { Texture2D texture = DownloadHandlerTexture.GetContent(request); // 使用纹理... } } }
  3. 缓存管理:使用Application.persistentDataPath存储用户图片,定期清理缓存

4.3 厂商适配经验

  • 小米设备:在AndroidManifest.xml添加:

    <meta-data android:name="xiaomi.permission.CAMERA_USE_SYSTEM_UI" android:value="true"/>
  • 华为设备:检查HuaweiApiAvailability.getInstance().isHuaweiMobileServicesAvailable()

  • OPPO/Realme:需要单独申请Settings.ACTION_MANAGE_OVERLAY_PERMISSION

在华为Mate40 Pro上测试时,发现相册返回的Uri格式与其他设备不同,需要额外处理content://com.huawei.hidisk.fileprovider/开头的路径。这种厂商差异在实际开发中很常见,建议准备主流设备的真机测试方案。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 1:00:22

Python金融数据处理实战指南:如何用mootdx破解通达信数据读取难题

Python金融数据处理实战指南&#xff1a;如何用mootdx破解通达信数据读取难题 【免费下载链接】mootdx 通达信数据读取的一个简便使用封装 项目地址: https://gitcode.com/GitHub_Trending/mo/mootdx 作为数据工作者&#xff0c;你是否曾面临这样的困境&#xff1a;耗费…

作者头像 李华
网站建设 2026/9/28 2:30:09

如何利用网盘下载工具突破限制?高效获取直连链接的完整指南

如何利用网盘下载工具突破限制&#xff1f;高效获取直连链接的完整指南 【免费下载链接】ctfileGet 获取城通网盘一次性直连地址 项目地址: https://gitcode.com/gh_mirrors/ct/ctfileGet 在数字时代&#xff0c;网盘已成为我们存储和分享文件的重要工具&#xff0c;但下…

作者头像 李华
网站建设 2026/9/26 0:47:52

升级YOLOv12后推理速度翻倍,性能优化实战指南

升级YOLOv12后推理速度翻倍&#xff0c;性能优化实战指南 YOLOv12不是简单迭代&#xff0c;而是一次架构跃迁。当你的模型在T4上跑出1.6毫秒的推理延迟&#xff0c;当同样一张图的检测耗时从3.2ms直接砍到1.6ms——这不是参数微调带来的边际提升&#xff0c;而是注意力机制与底…

作者头像 李华
网站建设 2026/9/27 2:01:59

嵌入式工控设备中任务创建的一文说清

以下是对您提供的博文内容进行 深度润色与结构重构后的技术文章 。全文已彻底去除AI腔调、模板化表达和教科书式分节&#xff0c;转而以一位有十年工控RTOS实战经验的嵌入式系统工程师视角&#xff0c;用自然、精准、略带现场感的语言重写——就像在技术分享会上&#xff0c;…

作者头像 李华
网站建设 2026/9/26 3:03:39

Clawdbot+Qwen3:32B法律应用:合同智能审查

ClawdbotQwen3:32B法律应用&#xff1a;合同智能审查实战指南 1. 引言&#xff1a;法律工作的智能化变革 想象一下这样的场景&#xff1a;一位律师正在审阅一份长达50页的商业合同&#xff0c;需要在两小时内找出所有潜在风险条款并提出修改建议。传统方式下&#xff0c;这可…

作者头像 李华
网站建设 2026/9/25 21:47:30

突破Steam创意工坊限制:WorkshopDL的3大创新与实战指南

突破Steam创意工坊限制&#xff1a;WorkshopDL的3大创新与实战指南 【免费下载链接】WorkshopDL WorkshopDL - The Best Steam Workshop Downloader 项目地址: https://gitcode.com/gh_mirrors/wo/WorkshopDL WorkshopDL是一款开源Steam创意工坊下载工具&#xff0c;无需…

作者头像 李华