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

Unity开发Android应用进行文件操作是否需申请权限及配置方法

Unity开发Android应用执行文件/媒体操作必须申请对应权限,PC端Editor运行正常是因为Windows环境没有Android系统级的沙箱权限管控,打包为APK后未配置、申请权限的情况下,所有跨沙箱的文件访问、修改、删除操作都会被系统直接拦截。

一、Manifest静态权限配置

你的猜测是对的,权限声明首先要在AndroidManifest.xml中完成,操作步骤:

  1. 找到Unity对应编辑器版本的基础Manifest模板,路径一般在Unity安装目录下的Editor/Data/PlaybackEngines/AndroidPlayer/Apk/AndroidManifest.xml,复制到你项目的Assets/Plugins/Android目录下再修改,不要直接改安装目录内的模板文件。
  2. 根据你要支持的最低安卓版本,在<manifest>标签内添加对应权限声明:
  • 适配Android 12及以下(API 32及以下),访问外部公共存储需要添加:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
  • 适配Android 13及以上(API 33+),WRITE_EXTERNAL_STORAGE权限已被废弃,谷歌按媒体类型拆分了读权限,按需添加:
<!-- 访问图片类文件 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<!-- 访问视频类文件 -->
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<!-- 访问音频类文件 -->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<!-- 如果需要操作文档、压缩包等非媒体类任意文件,额外添加全文件访问权限 -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

补充说明:如果你的所有文件操作都在应用私有沙箱目录(即Application.persistentDataPath对应路径)内,不需要申请任何存储权限。
3. 适配Android 10(API 29)的分区存储兼容,在Manifest的<application>标签内添加属性:

<application
    android:requestLegacyExternalStorage="true"
    ...其他原有属性>
二、运行时动态权限申请

从Android 6.0(API 23)开始,存储类属于危险权限,仅在Manifest中静态声明不会生效,必须在运行时调用系统API弹出授权窗口,让用户手动同意后才能操作文件。Unity内置了Android权限申请接口,不需要额外开发原生插件,参考实现代码:

using UnityEngine;
using UnityEngine.Android;

public class StoragePermissionHandler : MonoBehaviour
{
    private string[] targetPermissions;

    void Awake()
    {
        if (Application.platform != RuntimePlatform.Android) return;
        
        int sdkVersion = GetCurrentAndroidSDK();
        // 按系统版本匹配需要申请的权限列表
        if (sdkVersion >= 33)
        {
            targetPermissions = new string[]
            {
                Permission.ReadMediaImages,
                Permission.ReadMediaVideo,
                Permission.ReadMediaAudio,
                "android.permission.MANAGE_EXTERNAL_STORAGE"
            };
        }
        else
        {
            targetPermissions = new string[]
            {
                Permission.ExternalStorageRead,
                Permission.ExternalStorageWrite
            };
        }

        CheckAndRequestPermissions();
    }

    private void CheckAndRequestPermissions()
    {
        bool allPermissionGranted = true;
        foreach (var perm in targetPermissions)
        {
            if (!Permission.HasUserAuthorizedPermission(perm))
            {
                allPermissionGranted = false;
                break;
            }
        }

        if (allPermissionGranted) return;
        
        // 发起权限申请
        Permission.RequestUserPermissions(targetPermissions, new PermissionCallbacks
        {
            PermissionGranted = permName => Debug.Log($"权限{permName}授权成功"),
            PermissionDenied = permName => Debug.Log($"权限{permName}被拒绝,对应文件操作会失败"),
            PermissionDeniedAndDontAskAgain = permName => Debug.Log($"权限{permName}被永久拒绝,需要引导用户到系统设置页手动开启")
        });
    }

    // 获取当前设备Android SDK版本
    private int GetCurrentAndroidSDK()
    {
        using (AndroidJavaClass buildVersion = new AndroidJavaClass("android.os.Build$VERSION"))
        {
            return buildVersion.GetStatic<int>("SDK_INT");
        }
    }
}

注意:MANAGE_EXTERNAL_STORAGE全文件访问权限不支持普通弹窗申请,需要引导用户跳转到系统专门的「所有文件访问权限」页面手动开启,对应跳转代码:

public void JumpToAllFileAccessSetting()
{
    using (AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
    using (AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"))
    using (AndroidJavaClass uriClass = new AndroidJavaClass("android.net.Uri"))
    using (AndroidJavaObject intent = new AndroidJavaObject("android.content.Intent", "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION"))
    {
        string packageName = currentActivity.Call<string>("getPackageName");
        using (AndroidJavaObject uri = uriClass.CallStatic<AndroidJavaObject>("parse", $"package:{packageName}"))
        {
            intent.Call<AndroidJavaObject>("setData", uri);
            currentActivity.Call("startActivity", intent);
        }
    }
}
三、常见问题排查
  • 不要硬编码类似/sdcard/xxx的绝对路径,优先使用Unity官方提供的路径API获取目录,避免不同厂商系统路径映射差异导致的操作失败。
  • Editor下运行正常不代表移动端权限配置正确,PC端不会触发Android的沙箱权限校验,必须打真机包通过Logcat查看日志,文件操作失败最常见的报错就是java.io.IOException: Permission denied。
  • 如果应用仅个人使用,也可以在系统设置的应用权限页手动给应用开启存储/全文件访问权限,但正式逻辑里还是要做运行时申请,避免不同ROM默认权限策略差异导致功能失效。

内容的提问来源于stack exchange,提问作者Harsh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 09:01:06