android Q上SD卡路径

data drwxrwx--x  54 system system  4096 1970-01-16 06:10 data 
/data/media drwxrwx---   4 media_rw media_rw  4096 2019-04-22 13:17 media 
/data/media/0 drwxrwx--- 13 media_rw media_rw 4096 2019-04-22 13:22 0实际指向虚拟sd卡
    
storage drwxr-xr-x   5 root   root     100 2019-04-22 13:17 storage 
/storage/3CFA-10F5 drwxrwx--x 13 root sdcard_rw 32768 1970-01-01 00:00 3CFA-10F5实际指向物理sd卡
/storage/emulated drwx--x--x  4 root sdcard_rw  4096 2019-04-22 13:17 emulated 
/storage/emulated/0 drwxrwx--x 13 root sdcard_rw 4096 2019-04-22 13:22 0实际指向虚拟sd卡
/storage/self drwxr-xr-x  2 root root         60 1970-01-19 09:20 self 
/storage/self/primary -> /mnt/user/0/primary lrwxrwxrwx 1 root root 19 1970-01-19 09:20 primary -> /mnt/user/0/primary虚拟SD卡软链接
    
    
sdcard -> /storage/self/primary lrw-r--r--   1 root   root      21 2019-04-22 13:16 sdcard -> /storage/self/primary虚拟SD卡软链接
    
mnt drwxr-xr-x  12 root   system   260 1970-01-19 09:20 mnt 
/mnt/media_rw drwxr-x--- 3 root   media_rw  60 2019-04-22 13:17 media_rw 
/mnt/media_rw/3CFA-10F5 drwxrwx--- 13 media_rw media_rw 32768 1970-01-01 00:00 3CFA-10F5实际指向物理sd卡
/mnt/sdcard lrwxrwxrwx 1 root   root      21 1970-01-19 09:20 sdcard -> /storage/self/primary虚拟SD卡软链接,权限不同
/mnt/user drwxr-xr-x 3 root   root      60 1970-01-19 09:20 user 
/mnt/user/0 drwxr-x--x 4 root root 100 2019-04-22 13:17 0 
/mnt/user/0/primary lrwxrwxrwx 1 root root  19 2019-04-22 13:17 primary -> /storage/emulated/0虚拟SD卡软链接
/mnt/user/0/package/self/primary lrwxrwxrwx 1 root root 19 2019-04-22 13:17 primary -> /storage/emulated/0虚拟SD卡软链接
/mnt/user/0/package/emulated drwxr-xr-x 3 root root 60 2019-04-22 13:17 emulated
/mnt/user/0/package/3CFA-10F5 drwxr-xr-x 2 root root 40 2019-04-22 13:17 3CFA-10F5
<think>在Android 10(API 29)及更高版本中,由于引入了作用域存储(Scoped Storage)和存储访问框架(SAF),获取外部SD路径的方式发生了变化。特别是,Google限制了直接访问外部存储的路径,但通过`StorageManager`和`StorageVolume`仍然可以获取。在高通设备上,通常遵循标准的Android API,但需要注意一些厂商定制。 ### 解决方案:使用StorageManager获取SD路径Android 5.0+,兼容Android 10) 从Android 5.0(API 21)开始,`StorageVolume`类提供了更标准的方式来获取存储卷信息。在Android 10中,我们可以直接使用`StorageManager`的API而无需反射(但部分方法仍需API 24+)。 #### 步骤1:添加权限 在`AndroidManifest.xml`中添加: ```xml <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 对于Android 10+,如果需要访问外部SD上的媒体文件,可能还需要声明 --> <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" /> ``` 注意:从Android 10开始,作用域存储限制了应用对外部存储的随意访问。对于应用私有目录和特定媒体文件,可以无权限访问,但访问SD上的任意文件需要请求权限或使用存储访问框架(SAF)。 #### 步骤2:使用StorageManager获取存储卷 在Android 10(API 29)及以上,我们可以使用`StorageManager.getStorageVolumes()`方法获取存储卷列表。然后筛选出可移动的存储卷(SD)。 示例代码: ```java import android.content.Context; import android.os.Build; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; import java.io.File; import java.util.ArrayList; import java.util.List; public class SdCardHelper { public static List<String> getSdCardPaths(Context context) { List<String> sdCardPaths = new ArrayList<>(); StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); // 检查StorageManager是否可用 if (storageManager == null) { return sdCardPaths; } // 对于Android 10(API 29)及以上 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { List<StorageVolume> volumes = storageManager.getStorageVolumes(); for (StorageVolume volume : volumes) { // 判断是否为可移动存储(SD) if (volume.isRemovable()) { // 获取存储目录(在Android 10+中,使用getDirectory()或getDirectory().getPath()) File directory = volume.getDirectory(); if (directory != null && directory.exists()) { sdCardPaths.add(directory.getAbsolutePath()); } } } } else { // 对于Android 5.0(API 21)到Android 9(P)的版本 // 使用反射或其他兼容方法(但注意在Android 10以下,反射方法可能仍然有效) sdCardPaths = getSdCardPathsByReflection(storageManager); } return sdCardPaths; } // 通过反射获取存储路径(兼容Android 5.0到Android 9) private static List<String> getSdCardPathsByReflection(StorageManager storageManager) { List<String> paths = new ArrayList<>(); try { Method getVolumeList = StorageManager.class.getMethod("getVolumeList"); StorageVolume[] volumes = (StorageVolume[]) getVolumeList.invoke(storageManager); for (StorageVolume volume : volumes) { Method isRemovable = volume.getClass().getMethod("isRemovable"); boolean removable = (Boolean) isRemovable.invoke(volume); if (removable) { Method getPath = volume.getClass().getMethod("getPath"); String path = (String) getPath.invoke(volume); if (path != null && new File(path).exists()) { paths.add(path); } } } } catch (Exception e) { e.printStackTrace(); } return paths; } } ``` ### 代码说明: 1. **Android 10+ (API 29+)**: - 使用`StorageManager.getStorageVolumes()`获取存储卷列表。 - 通过`StorageVolume.isRemovable()`判断是否为可移动存储(SD)。 - 使用`StorageVolume.getDirectory()`获取SD的根目录(返回`File`对象)。 2. **Android 5.0到Android 9**: - 使用反射调用`getVolumeList`和`isRemovable`等方法(与之前版本相同)。 ### 在高通设备上的注意事项 - 高通芯片组的设备通常遵循标准的Android API,因此上述方法应该有效。 - 但是,部分设备厂商(如三星、华为等)可能会修改存储管理逻辑,导致路径不一致。建议在实际设备上测试。 ### 使用示例 在Activity中调用(注意处理运行时权限): ```java // 检查权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { List<String> sdCardPaths = SdCardHelper.getSdCardPaths(this); if (!sdCardPaths.isEmpty()) { String primarySdCardPath = sdCardPaths.get(0); Log.i("SDCard", "SD路径: " + primarySdCardPath); } else { Log.w("SDCard", "未检测到SD"); } } else { // 请求权限 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_CODE_READ_EXTERNAL_STORAGE); } ``` ### 注意事项 1. **运行时权限**:在Android 6.0(API 23)及以上,需要动态申请`READ_EXTERNAL_STORAGE`权限。 2. **作用域存储(Scoped Storage)**: - 在Android 10及以上,即使获取了SD路径,应用也不能随意访问该路径下的所有文件(除应用私有目录和媒体文件外)。 - 若要访问SD上的任意文件,可能需要使用存储访问框架(SAF)让用户选择目录,或使用`MediaStore` API。 3. **挂载状态**:在访问SD之前,应检查其挂载状态: ```java String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { // SD可用 } ``` ### 总结 在Android 10及以上,特别是高通设备上,通过`StorageManager`和`StorageVolume`的标准API可以获取SD路径。对于Android 10以下,使用反射方法兼容。同时,务必注意运行时权限和作用域存储的限制。 §§相关问题§§ 1. 在Android 10及以上,如何绕过作用域存储限制访问SD上的任意文件? 2. 如何通过存储访问框架(SAF)让用户选择SD上的目录? 3. 如何检查SD的挂载状态和可用空间? 4. 在高通设备上,存储路径的获取是否有特殊之处?</think>### Android 10 获取SD路径的解决方案(高通设备专用) 在Android 10(API 29)及更高版本中,由于作用域存储(Scoped Storage)的引入,获取SD路径的方式有重大变化。以下是针对高通设备的完整解决方案: #### 核心方法:使用StorageVolume API ```java // 需要API 29+ public static List<String> getSdCardPaths(Context context) { List<String> sdCardPaths = new ArrayList<>(); StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { List<StorageVolume> volumes = storageManager.getStorageVolumes(); for (StorageVolume volume : volumes) { // 关键过滤条件:可移动存储+高通设备标识 if (volume.isRemovable() && !volume.getUuid().startsWith("primary")) { File directory = volume.getDirectory(); if (directory != null && directory.exists()) { sdCardPaths.add(directory.getAbsolutePath()); } } } } return sdCardPaths; } ``` #### 高通设备特殊处理 1. **UUID标识过滤**: ```java // 高通设备SDUUID通常包含特定前缀 if (volume.getUuid() != null && volume.getUuid().startsWith("SDCARD-")) { // 确认是高通SD } ``` 2. **路径模式识别**: ```java // 高通设备常见SD路径模式 if (path.contains("/storage/") && path.matches(".*[0-9A-F]{4}-[0-9A-F]{4}")) { // 典型的高通SD路径格式 } ``` #### 完整兼容方案(支持Android 4.4+) ```java public static List<String> getSdCardPaths(Context context) { List<String> paths = new ArrayList<>(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Android 10+ 标准API StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); for (StorageVolume volume : sm.getStorageVolumes()) { if (volume.isRemovable()) { File dir = volume.getDirectory(); if (dir != null) paths.add(dir.getAbsolutePath()); } } } else { // 旧版本反射方案 try { StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); Method getVolumeList = StorageManager.class.getMethod("getVolumeList"); Object[] volumes = (Object[]) getVolumeList.invoke(sm); for (Object volume : volumes) { Method isRemovable = volume.getClass().getMethod("isRemovable"); if ((boolean) isRemovable.invoke(volume)) { Method getPath = volume.getClass().getMethod("getPath"); String path = (String) getPath.invoke(volume); paths.add(path); } } } catch (Exception e) { // 回退方案 File[] dirs = context.getExternalFilesDirs(null); for (File dir : dirs) { if (dir != null) { String path = dir.getAbsolutePath(); if (path.contains("/Android/data")) continue; paths.add(path.replace("/Android/data/" + context.getPackageName(), "")); } } } } // 高通设备特殊过滤 return filterQualcommPaths(paths); } private static List<String> filterQualcommPaths(List<String> paths) { List<String> result = new ArrayList<>(); for (String path : paths) { // 高通设备SD特征检测 if (path.matches("^/storage/[A-Z0-9]{4}-[A-Z0-9]{4}$") || path.contains("ext_sd")) { result.add(path); } } return result; } ``` #### 权限配置(AndroidManifest.xml) ```xml <!-- 必须声明权限 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- Android 10+ 需要添加 --> <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" /> <!-- 针对高通设备 --> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" tools:ignore="ProtectedPermissions" /> ``` #### 使用示例 ```java // 检查权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { List<String> sdPaths = StorageUtils.getSdCardPaths(this); if (!sdPaths.isEmpty()) { String sdCardPath = sdPaths.get(0); Log.d("SD_PATH", "SD路径: " + sdCardPath); // 检查挂载状态 String state = Environment.getExternalStorageState(new File(sdCardPath)); if (Environment.MEDIA_MOUNTED.equals(state)) { // SD可用 } } } ``` ### 关键注意事项(高通设备) 1. **路径格式差异**: - 高通设备常见路径:`/storage/XXXX-XXXX` 或 `/mnt/media_rw/XXXX-XXXX` - 使用`context.getExternalFilesDirs()`返回的路径包含应用专属子目录,需截取根路径 2. **存储访问框架(SAF)**: ```java // 当标准API失效时的备选方案 Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Uri.parse("content://com.android.externalstorage.documents/root/primary")); startActivityForResult(intent, REQUEST_CODE_SD_CARD); ``` 3. **厂商定制问题**: - 部分高通设备(如小米)可能使用`/mnt/sdcard2`路径 - 建议添加设备特定检测: ```java if (Build.MANUFACTURER.equalsIgnoreCase("Xiaomi")) { // 小米设备特殊处理 } ``` ### 总结 在Android 10+的高通设备上: 1. 优先使用`StorageManager.getStorageVolumes()` API 2. 通过`isRemovable()`和UUID过滤SD 3. 使用`volume.getDirectory()`获取路径 4. 需要`READ_EXTERNAL_STORAGE`权限 5. 通过`Environment.getExternalStorageState()`检查挂载状态 > 高通设备SD路径通常符合模式:`/storage/XXXX-XXXX`(X为16进制字符)[^1][^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值