Unity 发布 Android 安卓端所有文件可读写

现在有个需求,读写安卓系统的根目录下的文档

路径为

/storage/emulated/0/Documents/

在Unity PlayerSetting里有一个设置访问非持久化路径的设置

但是实际打包后发现还是访问不了。

解决方法

申请与配置方法

先自动生成一下AndroidManifest.xml,然后添加授权

csharp 复制代码
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

MANAGE_EXTERNAL_STORAGE 是 Android 10(API 级别 29)引入的系统权限,允许应用访问和管理设备外部存储上的所有文件,但需用户显式授权。‌

授权方法

csharp 复制代码
    /// <summary>
    /// 检查授权状态
    /// </summary>
    /// <returns></returns>
    private bool HasExternalStoragePermission()
    {
#if (!UNITY_EDITOR && UNITY_ANDROID)
        using (var version = new AndroidJavaClass("android.os.Build$VERSION"))
        {
            if (version.GetStatic<int>("SDK_INT") >= 30)
            {
                using (var environment = new AndroidJavaClass("android.os.Environment"))
                {
                    return environment.CallStatic<bool>("isExternalStorageManager");
                }
            }
        }
#endif
        return true;
    }
    /// <summary>
    /// 唤醒授权
    /// </summary>
    private void RequestExternalStoragePermission()
    {
#if (!UNITY_EDITOR && UNITY_ANDROID)
        using (var version = new AndroidJavaClass("android.os.Build$VERSION"))
        {
            if (version.GetStatic<int>("SDK_INT") >= 30)
            {
                using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
                {
                    using (var currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"))
                    {
                        using (var intent = new AndroidJavaObject("android.content.Intent"))
                        {
                            intent.Call<AndroidJavaObject>("setAction", "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION");
                            using (var uriClass = new AndroidJavaClass("android.net.Uri"))
                            {
                                using (var uri = uriClass.CallStatic<AndroidJavaObject>("parse", "package:" + currentActivity.Call<string>("getPackageName")))
                                {
                                    intent.Call<AndroidJavaObject>("setData", uri);
                                }
                            }
                            currentActivity.Call("startActivity", intent);
                        }
                    }
                }
            }
        }
#endif
    }

完整的测试代码

功能是要在安卓根目录的文档中,必须存在test.txt,没有就写入,有就读取。

(比如实现写入某个唯一密钥文件,用于激活软件)

csharp 复制代码
public string path = "/storage/emulated/0/Documents/test.txt";
    public Text mt;

    void Start()
    {
        GetPermission();
        CheckFileAndProcess();
    }

    private void OnApplicationFocus(bool focus)
    {
        if (focus)
        {
            GetPermission();
        }
    }

    void GetPermission()
    {
        if (!HasExternalStoragePermission())
        {
            RequestExternalStoragePermission();
        }
        else
        {
            SceneManager.LoadSceneAsync(1);
        }
    }

    private bool HasExternalStoragePermission()
    {
#if (!UNITY_EDITOR && UNITY_ANDROID)
        using (var version = new AndroidJavaClass("android.os.Build$VERSION"))
        {
            if (version.GetStatic<int>("SDK_INT") >= 30)
            {
                using (var environment = new AndroidJavaClass("android.os.Environment"))
                {
                    return environment.CallStatic<bool>("isExternalStorageManager");
                }
            }
        }
#endif
        return true;
    }
    
    private void RequestExternalStoragePermission()
    {
#if (!UNITY_EDITOR && UNITY_ANDROID)
        using (var version = new AndroidJavaClass("android.os.Build$VERSION"))
        {
            if (version.GetStatic<int>("SDK_INT") >= 30)
            {
                using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
                {
                    using (var currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"))
                    {
                        using (var intent = new AndroidJavaObject("android.content.Intent"))
                        {
                            intent.Call<AndroidJavaObject>("setAction", "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION");
                            using (var uriClass = new AndroidJavaClass("android.net.Uri"))
                            {
                                using (var uri = uriClass.CallStatic<AndroidJavaObject>("parse", "package:" + currentActivity.Call<string>("getPackageName")))
                                {
                                    intent.Call<AndroidJavaObject>("setData", uri);
                                }
                            }
                            currentActivity.Call("startActivity", intent);
                        }
                    }
                }
            }
        }
#endif
    }

    private void CheckFileAndProcess()
    {
        if (!HasExternalStoragePermission())
        {
            SetTextSafe("无文件访问权限,无法执行文件操作!");
            RequestExternalStoragePermission();
            return;
        }

        while (!File.Exists(path))
        {
            try
            {
                string directoryPath = Path.GetDirectoryName(path);
                if (!Directory.Exists(directoryPath))
                {
                    Directory.CreateDirectory(directoryPath);
                    Debug.Log("自动创建目录:" + directoryPath);
                }
                string writeContent = "HelloWorld";
                File.WriteAllText(path, writeContent, System.Text.Encoding.UTF8);
                Debug.Log("文件不存在,已自动写入:" + path + " 内容:" + writeContent);
            }
            catch (Exception ex)
            {
                SetTextSafe("文件写入失败,重试中:" + ex.Message);
            }
        }

        try
        {
            string fileContent = File.ReadAllText(path, System.Text.Encoding.UTF8);
            SetTextSafe("程序启动检测到文件,内容:\n" + fileContent);
        }
        catch (Exception ex)
        {
            SetTextSafe("文件存在但读取失败:" + ex.Message);
            Debug.LogError("文件读取异常:" + ex.ToString());
        }
    }

    public void ReadFileContentToText()
    {
        if (!HasExternalStoragePermission())
        {
            SetTextSafe("请先授予文件访问权限!");
            RequestExternalStoragePermission();
            return;
        }

        if (mt == null)
        {
            Debug.LogError("请在Inspector面板为mt赋值Text组件!");
            return;
        }

        try
        {
            if (!File.Exists(path))
            {
                SetTextSafe("文件不存在:" + path);
                return;
            }

            string fileContent = File.ReadAllText(path, System.Text.Encoding.UTF8);
            SetTextSafe("文件内容:\n" + fileContent);
            Debug.Log("文件读取成功:" + path);
        }
        catch (Exception ex)
        {
            SetTextSafe("读取失败:" + ex.Message);
            Debug.LogError("文件读取异常:" + ex.ToString());
        }
    }
    public void WriteHelloWorldToFile()
    {
        if (!HasExternalStoragePermission())
        {
            SetTextSafe("请先授予文件访问权限!");
            RequestExternalStoragePermission();
            return;
        }

        try
        {
            string directoryPath = Path.GetDirectoryName(path);
            if (!Directory.Exists(directoryPath))
            {
                Directory.CreateDirectory(directoryPath);
                Debug.Log("目录已创建:" + directoryPath);
            }

            string writeContent = "HelloWorld";
            File.WriteAllText(path, writeContent, System.Text.Encoding.UTF8);

            if (mt != null)
            {
                SetTextSafe("写入成功!\n路径:" + path + "\n内容:" + writeContent);
            }
            Debug.Log("文件写入成功:" + path);
        }
        catch (Exception ex)
        {
            if (mt != null)
            {
                SetTextSafe("写入失败:" + ex.Message);
            }
            Debug.LogError("文件写入异常:" + ex.ToString());
        }
    }


    private void SetTextSafe(string content)
    {
        if (mt != null && !string.IsNullOrEmpty(content))
        {
            mt.text = content;
        }
    }

验证

相关推荐
数智工坊1 分钟前
机器人运动控制:采样、优化与学习三大流派深度对比与实战
android·学习·机器人
故渊at2 小时前
第二板块:Android 四大组件标准化学理 | 第八篇:Service 后台执行实体与优先级
android·gitee·service·前台服务·后台服务
会Tk矩阵群控的小木2 小时前
安卓群控系统对于游戏工作室实战教程
android·运维·游戏·adb·开源软件·个人开发
qeen873 小时前
【C++】类与对象之类的默认成员函数(二)
android·c语言·开发语言·c++·笔记·学习
故渊at3 小时前
第二板块:Android 四大组件标准化学理 | 第九篇:BroadcastReceiver 事件分发与有序广播
android·gitee·broadcast·广播·动态注册·静态注册
游乐码3 小时前
Unity基础(十二)资源异步加载
unity·游戏引擎
JohnnyDeng943 小时前
【Android】Room 数据库高级用法与性能调优:从查询瓶颈到毫秒级响应
android·性能优化·kotlin·room
zeqinjie4 小时前
Flutter 折叠屏 iPad / 宽屏适配实践
android·前端·flutter
ab_dg_dp4 小时前
Android 17+ 提取 AIDL 生成 Java 文件的实用脚本
android·java·python