1. 项目概述为什么Unity安卓权限管理是个“坑”如果你用Unity开发过安卓应用并且需要用到摄像头、麦克风、位置这些敏感权限那你大概率已经踩过或者即将踩进权限管理的“坑”里。这项目标题“Unity安卓权限管理避坑指南”可以说精准地戳中了无数开发者的痛点。表面上看Unity把安卓开发封装得很好一个CheckPermission、RequestPermission的API似乎就搞定了但实际跑起来你会发现从打包时的AndroidManifest.xml配置到运行时多权限申请的混乱回调每一步都暗藏玄机。我见过太多项目功能都做完了最后卡在权限弹窗不出现、用户拒绝后无法再次引导、或者多个权限请求回调顺序错乱导致逻辑崩溃。这不仅仅是写几行代码的问题它涉及到Unity与安卓原生系统的交互机制、不同安卓版本的策略差异以及一个健壮应用所必需的用户体验设计。今天我就结合自己趟过的这些坑从最基础的Manifest配置讲起一直深入到最让人头疼的多权限异步回调处理给你理清一条清晰的实践路径。2. 核心思路拆解权限管理的三层架构要系统性地避开权限管理的坑我们不能只盯着某个API怎么调用而是要先建立起一个清晰的认知框架。在我看来一个完整的Unity安卓权限管理方案可以分为三个层次声明层、运行时层和用户体验层。这三层环环相扣任何一层的疏忽都会导致问题。2.1 声明层AndroidManifest.xml的“静态”配置这是所有权限管理的基础也是最容易出错的起点。AndroidManifest.xml文件是你的应用向安卓系统声明的“权利清单”。Unity会在打包时将你在Player Settings中配置的权限以及插件可能声明的权限合并生成最终的Manifest文件。这里的坑在于“合并冲突”和“权限分类”。首先权限分类你必须门儿清。安卓权限主要分为两类普通权限Normal Permissions例如网络访问、震动等这些权限只需要在Manifest中声明系统会在安装时自动授予不会提示用户。危险权限Dangerous Permissions涉及用户隐私和设备资源如相机CAMERA、录音RECORD_AUDIO、精确位置ACCESS_FINE_LOCATION等。这些权限不仅需要在Manifest中声明还必须在运行时向用户动态申请。很多新手开发者第一个坑就是在这里只在Manifest里加了权限就以为万事大吉结果运行时功能失效还找不到原因。记住对于危险权限Manifest声明只是拿到了“参赛资格”真正的“授权”需要在运行时进行。其次合并冲突。当你使用Asset Store的插件或者自己导入了Android库AAR/JAR时这些插件可能自带Manifest文件。Unity在打包时会尝试合并它们但如果两个Manifest定义了相同的组件比如activity但属性不同就会导致合并失败轻则功能异常重则编译报错。一个典型的错误信息可能就包含“manifest”和“merge”关键词。实操心得我习惯在打包前检查一下最终生成的Manifest文件。你可以在Unity项目的Temp目录下找到它路径类似Temp\StagingArea\AndroidManifest.xml。用文本编辑器打开看看你声明的权限是否在里面有没有重复或冲突的条目。这是排查声明层问题最直接的方法。2.2 运行时层Unity API与原生回调的“动态”交互这一层是我们写代码的主要战场。Unity提供了一个UnityEngine.Android.Permission类来封装权限请求。核心API就两个Permission.HasUserAuthorizedPermission用来检查权限状态Permission.RequestUserPermission用来请求权限。看起来简单但坑都在细节里。最大的坑在于异步回调。Permission.RequestUserPermission是一个异步方法它向系统弹出权限请求对话框但你的代码并不会停下来等待用户选择。你需要通过监听回调或者使用协程/异步等待的方式来处理结果。Unity旧版的回调是通过在AndroidManifest.xml中配置一个特定的Activity来实现而较新的方式则更推荐使用Permission.RequestUserPermission返回的PermissionCallbacks对象或者直接使用async/await模式如果你用的是支持C#新特性的Unity版本。但这里又引出一个更深的问题单次请求多个权限。Unity的API设计是每次请求一个权限字符串。如果你需要相机和麦克风权限新手可能会写成连续调用两次RequestUserPermission。这会导致系统连续弹出两个权限请求对话框用户体验极差并且两个回调的触发顺序和时机难以控制程序状态很容易混乱。2.3 用户体验层引导、解释与降级处理的“软”策略这是很多技术开发者容易忽略但却直接决定用户留存和评分的一层。系统弹窗是最后一道防线在这之前和之后都需要我们精心设计。请求前引导不要突然弹出一个冰冷的系统对话框。应该在应用内先通过友好的UI界面向用户解释为什么需要这个权限例如“我们需要使用相机来扫描二维码以实现登录功能”让用户有心理准备从而大大提高授权率。处理拒绝用户拒绝权限太正常了。你的应用不能崩溃也不能再无休止地请求。你需要设计降级方案比如用相册选择代替相机并在合适的时机比如用户再次点击相关功能时再次友好地解释并引导用户去系统设置页手动开启权限。这里涉及到检查“用户是否选择了‘不再询问’”这个标志位需要通过安卓原生接口来获取Unity API没有直接暴露这是另一个需要绕行的坑。多权限请求的体验正如运行时层提到的连续弹窗是灾难。理想的体验应该是应用内引导 - 一次性请求所有相关权限这需要自己封装 - 清晰处理每个权限的授权结果。理解了这三层架构我们就能有的放矢地填坑了。接下来我们从最具体的操作开始。3. 从零开始正确配置AndroidManifest.xml很多教程会告诉你在Unity的Player Settings - Android - Publishing Settings里勾选权限。这没错但对于复杂的项目这不够。我们可能需要手动修改或扩展Manifest。3.1 基础配置在Unity编辑器中声明对于大多数常见权限在Player Settings里勾选是最简单的方式。Unity会帮你把对应的uses-permission标签写入最终Manifest。找到Player SettingsFile - Build Settings - 选择Android平台 - Player Settings在Publishing Settings区块找到Build子区块下的Permissions列表勾选你需要的权限比如Camera、Microphone。注意这个列表是Unity预定义的一些常用权限可能不全。如果你需要的权限不在列表中比如一些特殊的蓝牙或后台定位权限你就需要用到下面的手动配置方法。3.2 高级配置使用自定义Manifest文件当我们需要添加Unity未提供的权限或者需要修改Manifest中的其他组件如application属性、添加activity时就必须使用自定义Manifest。找到模板在Unity安装目录下找到AndroidManifest.xml的模板文件。通常路径是{Unity安装路径}\Editor\Data\PlaybackEngines\AndroidPlayer\Apk\AndroidManifest.xml。将这个文件复制到你的Unity项目目录中例如Assets/Plugins/Android/目录下。如果Android文件夹不存在就新建一个。修改自定义Manifest用文本编辑器打开你刚复制过来的AndroidManifest.xml。你会发现它已经包含了很多基础配置。你需要做的就是在适当的位置添加你的内容。添加权限在manifest标签内与其他uses-permission并列的位置添加你需要的权限。例如添加精确定位权限uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION /处理兼容性对于安卓6.0API Level 23及以上版本危险权限需要运行时申请但Manifest声明依然必须。对于安卓10API Level 29及以上如果你需要后台定位还必须额外申请ACCESS_BACKGROUND_LOCATION权限并声明uses-feature吗不uses-feature通常用于声明硬件功能如android.hardware.camera用于在Google Play商店上进行设备过滤。权限和功能特性要区分开。处理Manifest合并冲突这是大坑。如果你同时使用了多个第三方SDK如登录、支付、广告它们都可能自带Manifest文件。冲突最常见于application的android:theme、android:allowBackup等属性或者activity的android:configChanges、android:windowSoftInputMode等属性。工具检查使用Unity打包如果出现合并错误控制台会给出相对明确的错误信息指出是哪个属性在哪个文件里冲突了。解决之道通常的解决方法是使用tools:replace或tools:node属性。例如如果多个Manifest都定义了android:allowBackup你可以在你自定义的Manifest的application标签里这样写来指定使用你的值并替换掉其他的application android:allowBackuptrue tools:replaceandroid:allowBackup ... 这需要你在manifest根标签里加入xmlns:toolshttp://schemas.android.com/tools命名空间声明。踩坑实录我曾经接入一个广告SDK它强制将android:allowBackup设为false而我的应用需要备份功能。如果不加处理最终打包的Manifest中这个值会是false导致功能问题。通过在自定义Manifest中使用tools:replace我成功将其覆盖为true。记住合并规则是后处理的覆盖先处理的但明确使用tools指令是最稳妥的。4. 运行时权限申请封装一个健壮的权限管理器配置好Manifest只是拿到了入场券。现在我们来处理运行时逻辑。目标是封装一个PermissionManager它能优雅地处理单个和多个权限的申请并管理回调。4.1 检查与请求单个权限我们先用最基础的API实现单权限流程。在Unity中你需要引用UnityEngine.Android命名空间注意在Unity 2019.3及以后安卓相关API被整合到了UnityEngine.Android命名空间下旧版可能在UnityEngine下。using UnityEngine; using UnityEngine.Android; // 关键命名空间 public class SimplePermissionExample : MonoBehaviour { void Start() { RequestCameraPermission(); } void RequestCameraPermission() { // 1. 定义权限字符串常量。最好统一定义避免拼写错误。 const string cameraPermission Permission.Camera; // Unity内置常量等同于 android.permission.CAMERA // 2. 检查是否已有权限 if (Permission.HasUserAuthorizedPermission(cameraPermission)) { Debug.Log(相机权限已授予); // 执行需要相机权限的操作... OpenCamera(); } else { Debug.Log(未获得相机权限开始请求...); // 3. 请求权限 Permission.RequestUserPermission(cameraPermission); // 注意这里是异步的代码不会等待。 } } void OpenCamera() { // 打开相机的具体逻辑 Debug.Log(相机已开启); } }但上面的代码有个问题请求发出后我们不知道用户什么时候做出选择结果如何。我们需要监听回调。4.2 处理权限请求回调处理回调有几种方式我推荐使用PermissionCallbacks对象它更现代和清晰。using UnityEngine; using UnityEngine.Android; public class PermissionCallbackExample : MonoBehaviour { void Start() { RequestCameraPermissionWithCallback(); } void RequestCameraPermissionWithCallback() { const string cameraPermission Permission.Camera; if (Permission.HasUserAuthorizedPermission(cameraPermission)) { OpenCamera(); return; } // 创建并配置回调 var callbacks new PermissionCallbacks(); callbacks.PermissionGranted (permissionName) { Debug.Log(${permissionName} 权限已被授予); if (permissionName cameraPermission) { OpenCamera(); } }; callbacks.PermissionDenied (permissionName) { Debug.LogWarning(${permissionName} 权限被拒绝); // 可以在这里提示用户并引导去设置页 ShowPermissionDeniedTip(); }; callbacks.PermissionDeniedAndDontAskAgain (permissionName) { Debug.LogError(${permissionName} 权限被拒绝且用户选择了‘不再询问’); // 这种情况必须引导用户去应用设置页面手动开启 ShowGoToSettingsTip(); }; // 发起请求并传入回调对象 Permission.RequestUserPermission(cameraPermission, callbacks); } void ShowPermissionDeniedTip() { // 显示一个UI弹窗解释权限用途并提供“重试”和“取消”按钮 // 点击“重试”可以再次调用 RequestCameraPermissionWithCallback } void ShowGoToSettingsTip() { // 显示一个更强烈的提示告诉用户必须去系统设置里打开权限并可能提供一个跳转按钮 // 跳转系统设置页的代码通常需要调用安卓原生接口可以使用Unity的AndroidJavaClass } }现在单权限的请求和回调处理就比较完整了。但现实项目往往是需要多个权限的。4.3 封装多权限顺序请求管理器连续请求多个权限会导致糟糕的用户体验。我们需要设计一个队列让权限一个接一个地申请并且统一管理所有结果。下面是一个简化但可用的多权限管理器核心思路using System.Collections.Generic; using UnityEngine; using UnityEngine.Android; public class PermissionManager : MonoBehaviour { private static PermissionManager _instance; public static PermissionManager Instance _instance; private Queuestring _permissionQueue new Queuestring(); private System.Actionbool _onAllCompleted; // 全部完成后的回调参数代表是否全部授予 private Dictionarystring, bool _permissionResults new Dictionarystring, bool(); private bool _isRequesting false; void Awake() { if (_instance ! null _instance ! this) { Destroy(gameObject); return; } _instance this; DontDestroyOnLoad(gameObject); // 常驻方便全局管理 } /// summary /// 请求一组权限 /// /summary /// param namepermissions权限数组/param /// param nameonAllCompleted全部请求完成后的回调bool参数表示是否全部被授予/param public void RequestPermissions(string[] permissions, System.Actionbool onAllCompleted) { if (permissions null || permissions.Length 0) { onAllCompleted?.Invoke(true); return; } _permissionResults.Clear(); _permissionQueue.Clear(); foreach (var perm in permissions) { // 只将未授权的权限加入队列 if (!Permission.HasUserAuthorizedPermission(perm)) { _permissionQueue.Enqueue(perm); } else { _permissionResults[perm] true; // 记录已授予 } } _onAllCompleted onAllCompleted; if (_permissionQueue.Count 0) { // 所有权限都已拥有 _onAllCompleted?.Invoke(true); return; } _isRequesting true; // 开始请求队列中的第一个权限 RequestNextPermission(); } private void RequestNextPermission() { if (_permissionQueue.Count 0) { // 队列已空所有权限请求完毕 _isRequesting false; bool allGranted true; foreach (var result in _permissionResults.Values) { if (!result) { allGranted false; break; } } _onAllCompleted?.Invoke(allGranted); return; } string currentPermission _permissionQueue.Dequeue(); var callbacks new PermissionCallbacks(); callbacks.PermissionGranted (permissionName) { _permissionResults[permissionName] true; Debug.Log($权限授予: {permissionName}); // 继续请求下一个 RequestNextPermission(); }; callbacks.PermissionDenied (permissionName) { _permissionResults[permissionName] false; Debug.LogWarning($权限拒绝: {permissionName}); // 即使被拒绝也继续请求队列中的下一个权限 RequestNextPermission(); }; callbacks.PermissionDeniedAndDontAskAgain (permissionName) { _permissionResults[permissionName] false; Debug.LogError($权限拒绝且不再询问: {permissionName}); // 记录下这个“硬拒绝”的权限后续可能需要特殊处理 // 继续请求下一个 RequestNextPermission(); }; Debug.Log($正在请求权限: {currentPermission}); Permission.RequestUserPermission(currentPermission, callbacks); } // 提供一个便捷方法请求常用的权限组 public void RequestARPermissions(System.Actionbool onCompleted) { string[] arPermissions new string[] { Permission.Camera, // 注意在安卓上ARCore可能需要额外的权限如写外部存储具体看AR插件要求 }; RequestPermissions(arPermissions, onCompleted); } }使用这个管理器时你可以这样调用void Start() { string[] neededPermissions { Permission.Camera, Permission.Microphone, Permission.ExternalStorageWrite }; PermissionManager.Instance.RequestPermissions(neededPermissions, (allGranted) { if (allGranted) { Debug.Log(所有必要权限已获取可以进入主功能了); StartMainFeature(); } else { Debug.LogWarning(有部分权限未授予功能可能受限。); // 可以在这里显示一个总结性的提示告诉用户哪些功能不可用并引导去设置 ShowSummaryWarning(); } }); }这个管理器实现了权限的顺序请求避免了弹窗堆叠并汇总了所有结果。但它仍然是顺序的用户需要点多次。有没有办法一次性弹窗请求多个权限呢这需要调用安卓原生接口复杂度会更高且在不同厂商的ROM上表现可能不一致Unity官方API并未直接提供。因此顺序请求配合良好的UI引导是目前在Unity中最稳健的跨平台方案。5. 处理“不再询问”与引导至系统设置当用户勾选了“不再询问”并拒绝后再次调用Permission.RequestUserPermission将不会弹出系统对话框回调会直接进入PermissionDeniedAndDontAskAgain。此时唯一的方法是引导用户手动去系统的应用信息页面开启权限。5.1 检测“不再询问”状态安卓原生交互Unity的API没有直接提供检查“是否被永久拒绝”的方法。我们需要写一点安卓原生代码通过JNI来获取这个信息。思路是调用安卓的Activity.shouldShowRequestPermissionRationale方法。我们可以写一个简单的Android Java插件或者直接用Unity的AndroidJavaClass在C#里调用。后者更轻量using UnityEngine; public class AndroidPermissionHelper { /// summary /// 检查是否应该向用户展示请求权限的理由即用户是否选择了“不再询问” /// 返回false可能有两种情况1. 用户之前同意了2. 用户拒绝了并点了“不再询问”。 /// 通常需要结合 Permission.HasUserAuthorizedPermission 来判断。 /// /summary public static bool ShouldShowRequestPermissionRationale(string permission) { if (Application.platform ! RuntimePlatform.Android) return false; try { using (var unityPlayer new AndroidJavaClass(com.unity3d.player.UnityPlayer)) using (var currentActivity unityPlayer.GetStaticAndroidJavaObject(currentActivity)) { // 调用Activity的shouldShowRequestPermissionRationale方法 bool shouldShow currentActivity.Callbool(shouldShowRequestPermissionRationale, permission); return shouldShow; } } catch (System.Exception e) { Debug.LogError($调用安卓原生API失败: {e}); return false; } } /// summary /// 判断权限是否被永久拒绝即需要引导去设置页 /// /summary public static bool IsPermissionPermanentlyDenied(string permission) { // 逻辑如果没有权限且不应该显示请求理由即shouldShow...返回false则很可能是永久拒绝 // 注意第一次请求前shouldShow...也返回false所以这个判断要结合业务逻辑使用比如在第一次请求被拒后再调用 if (Permission.HasUserAuthorizedPermission(permission)) { return false; // 已有权限当然不是永久拒绝 } return !ShouldShowRequestPermissionRationale(permission); } }重要提示ShouldShowRequestPermissionRationale的行为在安卓不同版本和不同厂商ROM上可能有差异。最可靠的方法是在PermissionDeniedAndDontAskAgain回调被触发时就认定为永久拒绝直接引导去设置。5.2 跳转到应用系统设置页引导用户去设置页也需要调用安卓原生接口。public class AndroidPermissionHelper { // ... 上面的代码 ... /// summary /// 打开当前应用的系统设置页面让用户手动修改权限 /// /summary public static void OpenAppSystemSettings() { if (Application.platform ! RuntimePlatform.Android) return; try { using (var unityPlayer new AndroidJavaClass(com.unity3d.player.UnityPlayer)) using (var currentActivity unityPlayer.GetStaticAndroidJavaObject(currentActivity)) { // 构建一个打开应用详情页的Intent AndroidJavaClass intentClass new AndroidJavaClass(android.content.Intent); AndroidJavaObject intentObject new AndroidJavaObject(android.content.Intent); // 设置Action和Data string packageName currentActivity.Callstring(getPackageName); AndroidJavaClass uriClass new AndroidJavaClass(android.net.Uri); AndroidJavaObject uriObject uriClass.CallStaticAndroidJavaObject(fromParts, package, packageName, null); intentObject.CallAndroidJavaObject(setAction, android.settings.APPLICATION_DETAILS_SETTINGS); intentObject.CallAndroidJavaObject(setData, uriObject); intentObject.CallAndroidJavaObject(addCategory, android.intent.category.DEFAULT); intentObject.CallAndroidJavaObject(setFlags, 0x10000000); // FLAG_ACTIVITY_NEW_TASK // 启动Activity currentActivity.Call(startActivity, intentObject); } } catch (System.Exception e) { Debug.LogError($跳转到系统设置页失败: {e}); } } }在你的UI提示框中可以提供一个“去设置”按钮点击后调用AndroidPermissionHelper.OpenAppSystemSettings()。6. 实战避坑与疑难问题排查即使按照上面的步骤做了在实际项目中还是会遇到各种稀奇古怪的问题。这里我总结几个最常见的坑和排查思路。6.1 坑一权限弹窗不出现回调立即返回“拒绝”现象调用RequestUserPermission后没有任何系统弹窗回调几乎立刻触发PermissionDenied或PermissionDeniedAndDontAskAgain。可能原因与排查Manifest未声明这是最可能的原因。请务必检查最终打包的APK中的AndroidManifest.xml文件确认你的危险权限uses-permission标签是否存在。可以用解压软件打开APK查看AndroidManifest.xml可能是二进制格式需要工具反编译查看。权限分组与已授权安卓将权限分组管理。例如如果你已经授予了WRITE_EXTERNAL_STORAGE写存储权限那么同组的READ_EXTERNAL_STORAGE读存储权限会被自动授予。此时请求读存储权限系统不会弹窗回调会立即返回“已授权”。但反之则不成立。所以检查一下是否同组权限已被授予。在后台线程请求权限请求必须在主线程Unity游戏线程发起。在某些异步回调或子线程中直接调用可能会失败。Target API Level过高与权限策略变更如果你的targetSdkVersion设置得很高例如安卓13/API 33一些权限策略会变得更严格。比如安卓13将通知权限POST_NOTIFICATIONS设为危险权限需要运行时申请。如果你用旧版本的Unity或配置可能没有适配。确保你的Unity版本和Player Settings中的Target API Level符合你所需权限的最新要求。6.2 坑二在Unity Editor中测试权限无效现象在Unity编辑器里运行权限相关的代码好像没执行或者Permission.HasUserAuthorizedPermission总是返回true/false。原因与解决Unity编辑器环境不是真实的安卓系统权限API的行为是模拟的或者根本不起作用。Permission.HasUserAuthorizedPermission在编辑器里可能默认返回true。正确做法权限逻辑必须在真机上进行测试。你可以通过条件编译来区分编辑器和真机逻辑void CheckPermission() { #if UNITY_ANDROID !UNITY_EDITOR // 真实的安卓权限检查与请求代码 if (!Permission.HasUserAuthorizedPermission(Permission.Camera)) { Permission.RequestUserPermission(Permission.Camera); } #else // 编辑器或其它平台下的模拟行为比如直接调用成功回调 Debug.Log(在编辑器下模拟权限已授予); OnCameraPermissionGranted(); #endif }6.3 坑三多权限回调顺序错乱或丢失现象使用自己封装的队列请求多个权限时偶尔会出现回调没触发或者触发顺序不对导致状态机混乱。可能原因回调被垃圾回收如果你在局部函数中创建了PermissionCallbacks对象并且没有保持引用它可能在异步等待期间被垃圾回收器GC回收导致回调永远无法触发。确保你的回调对象在请求期间有有效的引用例如作为类的成员变量或者在协程中yield return等待。Activity生命周期问题权限请求对话框会启动一个新的系统Activity。如果你的Unity应用在请求弹出期间被切到后台比如用户突然点了Home键或者发生了屏幕旋转等导致Activity重建的情况可能会打断请求流程。虽然Unity会尽力处理但在极端情况下可能出问题。自己封装的队列逻辑Bug仔细检查你的队列管理逻辑确保在每一个回调Granted/Denied中都正确地调用了“请求下一个”的方法并且处理好所有异常路径。解决建议对于第1点将PermissionCallbacks对象保存在一个不会被提前销毁的MonoBehaviour的成员变量中。对于第2点需要在OnApplicationPause等生命周期函数中做好状态保护。对于第3点充分测试你的管理器尤其是网络请求、场景切换等并发情况下。6.4 坑四不同安卓版本与厂商ROM的兼容性问题现象在A手机上工作正常在B手机上弹窗样式怪异或者在C手机上根本不会进入PermissionDeniedAndDontAskAgain回调。原因这是安卓生态的碎片化问题。特别是国内各手机厂商小米、华为、OPPO、vivo等都对原生安卓的权限管理进行了深度定制它们有自家的权限管理页面和弹窗样式行为可能与原生安卓有细微差别。应对策略降低Target SDK这曾是临时解决方案但Google Play商店有最低API要求且这不是治本之策不推荐。测试、测试、再测试在你的目标用户群使用的主流机型上进行充分测试。准备一些“备用机”进行兼容性测试是必要的。使用第三方插件如果权限管理非常复杂且关键可以考虑使用成熟的第三方Unity插件例如一些移动工具包如Mobile Native Popups、Native Plugins等它们可能封装了更好的兼容性处理。但引入插件也会增加复杂度和包体。强化引导文案在应用内引导弹窗中使用更普适的文案比如“请在接下来的系统提示中允许XX权限”而不是具体描述系统弹窗的样子因为厂商的弹窗可能完全不同。7. 总结与最佳实践清单走完这一整套流程你会发现Unity安卓权限管理就像在雷区排雷但只要你按照清晰的路线图就能安全通过。最后我把自己总结的几条核心最佳实践列出来你可以作为检查清单声明是基础任何危险权限务必在AndroidManifest.xml中声明。使用自定义Manifest文件来处理复杂需求和合并冲突打包前检查最终生成的Manifest。运行时请求是必须Manifest声明后必须在代码中调用运行时API请求授权。用Permission.HasUserAuthorizedPermission检查用Permission.RequestUserPermission请求。异步回调是核心使用PermissionCallbacks对象来可靠地处理授权结果。不要假设请求会立即完成。多权限要排队避免同时弹出多个系统对话框。实现一个权限请求队列管理器顺序请求统一处理结果。用户体验是关键请求前做好解释被拒绝后提供清晰的引导。对于“不再询问”的情况必须提供跳转系统设置页的途径。真机测试是铁律权限相关功能绝不能在编辑器里“以为没问题”。必须在真实安卓设备上进行全面测试。处理“永久拒绝”利用PermissionDeniedAndDontAskAgain回调或调用安卓原生API判断及时引导用户去系统设置。注意生命周期与引用确保回调对象在异步操作期间不会被销毁处理好应用暂停、切后台等情况。关注Target API Level保持Unity版本和Target API Level更新以适配安卓系统最新的权限政策。权限管理看似是边缘功能但它直接影响应用的第一印象和核心功能可用性。把这些坑填平你的应用就离“稳定可靠”更近了一大步。在实际项目中我将上面提到的PermissionManager和AndroidPermissionHelper封装成一个单独的模块在所有需要权限的项目中复用省去了重复踩坑的麻烦。你也完全可以这么做一次封装终身受益。