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

Unity开发Meta Quest 3 APK时TXT文件存储权限问题求助

Meta Quest 3 Unity开发:TXT文件存储权限问题排查

问题背景

我正在为研究目的用Unity开发Meta Quest 3的APK,需要将用户使用应用时输入的内容保存为TXT文件,怀疑遇到了Android权限问题。

存储代码

using System;
using System.IO;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class LoggerQuest : MonoBehaviour
{
    public GameObject textObject1;
    private DateTime startTime;
    private DateTime sceneStartTime;


    // Start is called before the first frame update
    void Start()
    {
        string fname = System.DateTime.Now.ToString("HH-mm-ss") + ".txt";
        string path = $"/mnt/sdcard/This Headset/Oculus/{fname}";

        startTime = DateTime.Now;
        sceneStartTime = DateTime.Now;
        SceneManager.sceneLoaded += OnSceneLoaded;

        if (textObject1 != null)
        {
            Text text1 = textObject1.GetComponent<Text>();
            if (text1 != null)
                text1.RegisterDirtyVerticesCallback(() => SaveText(text1));
        }

        void OnSceneLoaded(Scene scene, LoadSceneMode mode)
        {
            DateTime sceneEndTime = DateTime.Now;
            TimeSpan sceneElapsedTime = sceneEndTime - sceneStartTime;


            using (StreamWriter writer = new StreamWriter(path, true))
            {
                writer.WriteLine("Scene Name: " + scene.name);
                writer.WriteLine("Scene Start Time: " + sceneStartTime.ToString("HH:mm:ss"));
                writer.WriteLine("Scene End Time: " + sceneEndTime.ToString("HH:mm:ss"));
                writer.WriteLine("Scene Elapsed Time: " + sceneElapsedTime.ToString());
                writer.WriteLine("-------------------------------------");
            }

            // Update the start time for the new scene
            sceneStartTime = DateTime.Now;
        }

        void OnApplicationQuit()
        {
            DateTime endTime = DateTime.Now;
            Debug.Log("Scene ended at: " + endTime.ToString("HH:mm:ss"));

            TimeSpan elapsedTime = endTime - startTime;
            TimeSpan sceneElapsedTime = endTime - sceneStartTime;

            using (StreamWriter writer = new StreamWriter(path, true))
            {
                writer.WriteLine("Start Time: " + startTime.ToString("HH:mm:ss"));
                writer.WriteLine("End Time: " + endTime.ToString("HH:mm:ss"));
                writer.WriteLine("Elapsed Time: " + elapsedTime.ToString());
                writer.WriteLine("Scene Elapsed Time: " + sceneElapsedTime.ToString());
                writer.WriteLine("-------------------------------------");
            }
        }

        void SaveText(Text textComponent)
        {
            DateTime inputTime = DateTime.Now;
            using (StreamWriter writer = new StreamWriter(path, true))
            {
                writer.WriteLine("Text Object: " + textComponent.name);
                writer.WriteLine("Text: " + textComponent.text);
                writer.WriteLine("Text Change Time: " + inputTime.ToString("HH:mm:ss"));
                writer.WriteLine("-------------------------------------");
            }
        }

        void LogInteraction(GameObject interactedObject)
        {
            DateTime interactionTime = DateTime.Now;
            using (StreamWriter writer = new StreamWriter(path, true))
            {
                writer.WriteLine("Interaction with: " + interactedObject.name);
                writer.WriteLine("Interaction Time: " + interactionTime.ToString("HH:mm:ss"));
                writer.WriteLine("-------------------------------------");
            }
        }
    }
}

调试错误日志

07-19 16:42:37.876 19528 19601 E DeviceConfig: getProperties failed: Permission denial: reading from settings requires:android.permission.READ_DEVICE_CONFIG
07-19 16:42:40.018   744   744 E DeviceConfig: getProperties failed: 
07-19 16:42:40.019   759  1048 E hw-IPCThreadState: Process seems to be sending too many oneway calls.

已添加的Android权限

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

解决建议

1. 适配Android 13+权限规则

Meta Quest 3基于Android 13,原有的WRITE/READ_EXTERNAL_STORAGE权限已被弱化。针对TXT这类非媒体文件,需添加全存储访问权限:

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

该权限无法通过代码直接申请,需引导用户到系统设置手动开启。

2. 动态申请基础权限

在代码中主动申请所需权限,而非仅依赖Manifest声明:

#if UNITY_ANDROID && !UNITY_EDITOR
void Start()
{
    // 先申请基础存储权限
    string[] basicPerms = new string[] {
        "android.permission.READ_EXTERNAL_STORAGE",
        "android.permission.WRITE_EXTERNAL_STORAGE"
    };
    foreach (string perm in basicPerms)
    {
        if (!Permission.HasUserAuthorizedPermission(perm))
        {
            Permission.RequestUserPermission(perm);
        }
    }

    // 检查全存储权限,未授权则跳转设置
    if (!Permission.HasUserAuthorizedPermission("android.permission.MANAGE_EXTERNAL_STORAGE"))
    {
        OpenStorageSettings();
    }

    // 原有Start方法逻辑...
}

void OpenStorageSettings()
{
    AndroidJavaClass intentClass = new AndroidJavaClass("android.content.Intent");
    AndroidJavaObject intentObject = new AndroidJavaObject("android.content.Intent");
    AndroidJavaClass uriClass = new AndroidJavaClass("android.net.Uri");
    AndroidJavaObject uriObject = uriClass.CallStatic<AndroidJavaObject>("parse", "package:" + Application.identifier);
    intentObject.Call<AndroidJavaObject>("setAction", "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION");
    intentObject.Call<AndroidJavaObject>("setData", uriObject);
    AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
    AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
    currentActivity.Call("startActivity", intentObject);
}
#endif

3. 更换推荐存储路径

避免使用硬编码的/mnt/sdcard路径,改用官方推荐路径:

  • 应用专属存储(无需额外权限):卸载应用时数据会被删除,适合研究数据存储
string path = Path.Combine(Application.persistentDataPath, fname);
  • 公共文档目录(需权限):数据可在设备文件管理器中查看
#if UNITY_ANDROID && !UNITY_EDITOR
AndroidJavaClass environment = new AndroidJavaClass("android.os.Environment");
AndroidJavaObject publicDir = environment.CallStatic<AndroidJavaObject>("getExternalStoragePublicDirectory", environment.GetStatic<string>("DIRECTORY_DOCUMENTS"));
string docsPath = publicDir.Call<string>("getAbsolutePath");
string path = Path.Combine(docsPath, "YourResearchApp", fname);
Directory.CreateDirectory(Path.GetDirectoryName(path)); // 确保目录存在
#endif

4. 关于READ_DEVICE_CONFIG错误

该错误属于系统层面的内部调用问题,和你的文件存储逻辑无关,可暂时忽略,重点解决存储权限问题即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 21:55:06