news 2026/6/15 23:20:22

【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/6/6 7:45:16

继电器技术解析:电磁继电器与磁保持继电器的核心差异与应用场景

1. 电磁继电器与磁保持继电器的本质区别 我第一次接触继电器是在大学实验室里&#xff0c;当时被这个"用小电流控制大电流"的神奇装置深深吸引。后来在实际项目中踩过不少坑才明白&#xff0c;电磁继电器和磁保持继电器虽然外观相似&#xff0c;但骨子里完全是两种不…

作者头像 李华
网站建设 2026/6/10 22:10:36

AI Agent开发首选?通义千问2.5-7B工具调用实战指南

AI Agent开发首选&#xff1f;通义千问2.5-7B工具调用实战指南 1. 为什么是通义千问2.5-7B-Instruct&#xff1f; 在当前AI Agent开发实践中&#xff0c;选对基础模型往往决定了整个项目的落地效率和长期可维护性。不是参数越大越好&#xff0c;也不是推理越快越优——真正关…

作者头像 李华
网站建设 2026/6/4 7:22:03

Jimeng AI Studio:一款让你轻松成为AI艺术家的工具

Jimeng AI Studio&#xff1a;一款让你轻松成为AI艺术家的工具 1. 为什么说它真能“轻松”成为AI艺术家&#xff1f; 你有没有过这样的体验&#xff1a;打开一个AI绘图工具&#xff0c;页面密密麻麻全是参数滑块、模型下拉框、采样器选项……光是搞懂“CFG是什么”“Euler a和…

作者头像 李华
网站建设 2026/6/15 22:57:36

零基础教程:用DDColor一键为老照片智能上色

零基础教程&#xff1a;用DDColor一键为老照片智能上色 你家相册里是否还压着几张泛黄的老照片&#xff1f;爷爷军装上的肩章颜色、外婆旗袍的底纹、老宅门楣的朱漆——这些细节在黑白影像里早已褪成一片灰白。过去&#xff0c;还原它们需要翻查史料、比对老物件&#xff0c;甚…

作者头像 李华
网站建设 2026/6/11 14:56:09

家庭游戏串流自建服务器完全指南:从部署到优化的全流程解析

家庭游戏串流自建服务器完全指南&#xff1a;从部署到优化的全流程解析 【免费下载链接】Sunshine Sunshine: Sunshine是一个自托管的游戏流媒体服务器&#xff0c;支持通过Moonlight在各种设备上进行低延迟的游戏串流。 项目地址: https://gitcode.com/GitHub_Trending/su/S…

作者头像 李华
网站建设 2026/6/15 21:27:20

3步打造智能视频管家:抖音下载器AI分类功能全解析

3步打造智能视频管家&#xff1a;抖音下载器AI分类功能全解析 【免费下载链接】douyin-downloader 项目地址: https://gitcode.com/GitHub_Trending/do/douyin-downloader 问题导入&#xff1a;当你的视频库变成"数字垃圾场"怎么办&#xff1f; 想象一下&am…

作者头像 李华