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

Unity获取Android设备全部音乐文件的权限异常问题求助

问题原因分析
  1. Scoped Storage限制:Android 10(API 29)及以上引入了Scoped Storage机制,即便授予READ_EXTERNAL_STORAGE权限,也禁止直接遍历/storage根目录或其他应用私有目录。你的文件获取代码用Directory.GetDirectories遍历/storage,这在Android 10+设备上会被系统拦截,抛出权限异常。
  2. 权限申请逻辑缺陷:原协程固定等待3秒再检查权限,无法精准捕捉用户授权时机,可能导致权限未完全授予就跳转场景;同时权限请求顺序和检查逻辑存在漏洞,比如未等待用户完成READ权限授权就进入循环,可能导致该权限未被正确获取。
  3. Manifest配置缺失:你的targetSdkVersion为24,虽低于29,但Android 10+设备会对低版本targetSdk应用强制启用部分Scoped Storage限制,需添加requestLegacyExternalStorage属性兼容旧存储模型(仅作为临时过渡方案)。
解决方案

一、修正权限申请逻辑

修改ButtonPressing脚本,确保权限请求和检查逻辑更可靠,等待用户完成授权后再跳转场景:

using System.Collections;
using UnityEngine;
using UnityEngine.Android;
using UnityEngine.SceneManagement;

public class ButtonPressing : MonoBehaviour
{
    private void Start()
    {
#if PLATFORM_ANDROID
        StartCoroutine(RequestStoragePermissions());
#endif
    }

    private IEnumerator RequestStoragePermissions()
    {
        // 先检查是否已拥有所有需要的权限
        if (Permission.HasUserAuthorizedPermission(Permission.ExternalStorageRead) && 
            Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite))
        {
            SceneManager.LoadScene(1);
            yield break;
        }

        // 请求WRITE权限(Android中WRITE权限隐含READ权限,但仍需显式申请)
        if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite))
        {
            Permission.RequestUserPermission(Permission.ExternalStorageWrite);
            // 等待用户授权结果
            while (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite))
            {
                yield return null;
            }
        }

        // 确认READ权限
        if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageRead))
        {
            Permission.RequestUserPermission(Permission.ExternalStorageRead);
            while (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageRead))
            {
                yield return null;
            }
        }

        // 所有权限授予完成,跳转场景
        SceneManager.LoadScene(1);
    }
}

二、临时兼容旧存储模型(仅针对targetSdk<29)

在AndroidManifest.xml的application标签中添加android:requestLegacyExternalStorage="true",让Android 10+设备允许应用使用旧存储访问方式:

<application 
    android:theme="@style/UnityThemeSelector" 
    android:icon="@drawable/app_icon" 
    android:label="@string/app_name" 
    android:debuggable="false" 
    android:isGame="true" 
    android:banner="@drawable/app_banner"
    android:requestLegacyExternalStorage="true"> <!-- 新增该行 -->
    <!-- 其他内容保持不变 -->
</application>

注意:这是临时过渡方案,Android 11(API 30)及以上会逐步限制该属性,长期需适配Scoped Storage。

三、适配Scoped Storage的正确方式(推荐长期方案)

放弃直接遍历目录的方式,使用Android官方推荐的MediaStore API查询设备音乐文件,完全符合Scoped Storage要求:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class MusicFileLoader : MonoBehaviour
{
    public Text directoryTexts;

    public void GetFiles()
    {
#if PLATFORM_ANDROID
        if (Permission.HasUserAuthorizedPermission(Permission.ExternalStorageRead))
        {
            directoryTexts.text = "正在获取音乐文件...\n";
            List<string> musicPaths = GetMusicFilesViaMediaStore();
            
            if (musicPaths.Count > 0)
            {
                directoryTexts.text += $"找到{musicPaths.Count}个音乐文件:\n";
                foreach (string path in musicPaths)
                {
                    directoryTexts.text += path + "\n";
                }
            }
            else
            {
                directoryTexts.text += "未找到音乐文件\n";
            }
        }
        else
        {
            directoryTexts.text += "缺少读取存储权限\n";
        }
#endif
    }

    private List<string> GetMusicFilesViaMediaStore()
    {
        List<string> musicPaths = new List<string>();
        
        using (AndroidJavaClass mediaStoreClass = new AndroidJavaClass("android.provider.MediaStore"))
        using (AndroidJavaObject contentResolver = new AndroidJavaClass("com.unity3d.player.UnityPlayer")
               .GetStatic<AndroidJavaObject>("currentActivity")
               .Call<AndroidJavaObject>("getContentResolver"))
        {
            // 音乐文件的URI
            AndroidJavaObject audioUri = mediaStoreClass.GetStatic<AndroidJavaObject>("Audio.Media.EXTERNAL_CONTENT_URI");
            
            // 需要查询的列:文件路径
            string[] projection = new string[] { mediaStoreClass.GetStatic<string>("Audio.Media.DATA") };
            
            // 查询所有音乐文件
            AndroidJavaObject cursor = contentResolver.Call<AndroidJavaObject>("query", 
                audioUri, projection, null, null, null);
            
            if (cursor.Call<bool>("moveToFirst"))
            {
                int dataColumnIndex = cursor.Call<int>("getColumnIndex", mediaStoreClass.GetStatic<string>("Audio.Media.DATA"));
                
                do
                {
                    string filePath = cursor.Call<string>("getString", dataColumnIndex);
                    musicPaths.Add(filePath);
                } while (cursor.Call<bool>("moveToNext"));
            }
            
            cursor.Call("close");
        }
        
        return musicPaths;
    }
}

额外注意事项

  • 若将targetSdkVersion升级到29及以上,可移除WRITE_EXTERNAL_STORAGE权限(Scoped Storage下访问公共媒体文件无需该权限,除非要修改其他应用的文件)。
  • 测试时使用Android 10+真实设备,模拟器可能存在存储模拟异常问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 18:10:35