Android:消除文件路径的歧义

Posted

技术标签:

【中文标题】Android:消除文件路径的歧义【英文标题】:Android: disambiguating file paths 【发布时间】:2013-04-05 18:53:17 【问题描述】:

在我的应用中,用户选择文件。在内部,我存储有关文件的信息,我根据文件路径对其进行键控。下次使用该文件时,我会处理存储的信息。问题是我用以下方式实例化我的文件:

File file1 = new File(Environment.getExternalStorageDirectory() + "/test.txt");

然后,在特定的 JB 设备上,file1.getCanonicalPath() 给出:“/storage/emulated/0/test.txt”。

问题在于,当其他应用使用 Intent 中的文件路径启动我的应用时,它们发送的路径往往看起来像:“/mnt/sdcard/test.txt”。

是否有一个聪明的策略来消除这两条路径的歧义?可能我应该以不同的方式实例化我的文件?

编辑:

问题是,两个文件的两个规范路径不相等。对于以下内容,cp1=="mnt/sdcard/test/txt"cp2=="/storage/emulated/0/text/txt"

File file1 = new File("/mnt/sdcard/test.txt");
File file2 = new File("/storage/emulated/0/test.txt");

String cp1 = file1.getCanonicalPath();
String cp2 = file2.getCanonicalPath();

【问题讨论】:

同一个文件有2个不同的绝对路径?这些路径是不同的,并且可以存在于存储上而不会互斥。你能详细说明一下这个场景吗?发送给您的Intent 是如何准备的?何时发送? 我希望有办法将两者等同起来,因为不同的路径指向同一个文件。或者,如果我将 Environment.getExternalStorageDirectory() 评估为“/mnt/sdcard/”而不是“/storage/emulated/0”,它将减少此问题的发生(但我不知道该怎么做) 【参考方案1】:

首先,获取外部路径的唯一正确方法是在 android 中使用getExternalStorageDirectory 和其他getExternalStorageXXX

Android 会首先尝试解析两个系统变量:

String rawExternalStorage = System.getenv(ENV_EXTERNAL_STORAGE);
String rawEmulatedStorageTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET);

ENV_EXTERNAL_STORAGE = "EXTERNAL_STORAGE"ENV_EMULATED_STORAGE_TARGET = "EMULATED_STORAGE_TARGET"。如果设置了EMULATED_STORAGE_TARGET变量,则表示设备有模拟存储,则存储路径为EMULATED_STORAGE_TARGET。(Android 4.2以后支持多用户外部存储,则后面会有/0或0路径)但是如果没有设置并且设置了EXTERNAL_STORAGE,那么路径将是EXTERNAL_STORAGE。如果两者都没有设置,则默认路径为/storage/sdcard0。因此,不同的设备可能包含不同的外部存储路径。

正如External Storage Technical Information 所说,您可以通过设置init.rc 文件来自定义设备的存储。例如在默认的金鱼一号中:

export EXTERNAL_STORAGE /mnt/sdcard
mkdir /mnt/sdcard 0000 system system
symlink /mnt/sdcard /sdcard

如果您使用getExternalStorageDirectory,您将获得/mnt/sdcard,但/sdcard 是指向该目录的符号链接。

所以在你的情况下,init.rc 可能包含:

export EMULATED_STORAGE_TARGET /storage/emulated
symlink /storage/emulated/0 /mnt/sdcard

所以它们不是模棱两可的,它们实际上是相同的。

我认为getCanonicalPath() 可能适用于您的绝大多数用例。

规范路径名既是绝对的又是唯一的。精确的 规范形式的定义是系统相关的。这个方法先 如有必要,将此路径名转换为绝对形式,就像通过 调用 getAbsolutePath() 方法,然后将其映射到其唯一的 以系统相关的方式形成。这通常涉及删除 冗余名称,例如“.”和路径名中的“..”,解析 符号链接(在 UNIX 平台上),并将驱动器号转换为 标准大小写(在 Microsoft Windows 平台上)。

每个表示现有文件或目录的路径名都有一个唯一的 规范形式。每个表示不存在文件的路径名或 目录也有一个独特的规范形式。的规范形式 不存在的文件或目录的路径名可能与 文件或目录后相同路径名的规范形式 创建的。类似地,现有路径名的规范形式 文件或目录可能与相同的规范形式不同 删除文件或目录后的路径名。

【讨论】:

问题是,给定两个路径“/mnt/sdcard/test.txt”和“/storage/emulated/0/test.txt”:File file1 = new File(path1), File file 2 = 新文件(路径 2)。 file1.getCanonicalPath().equals(file2.getCanonicalPath()) 的计算结果为 false,尽管它们指向同一个文件。 您确定它们引用的是同一个文件吗?修改一个文件会影响另一个文件吗?我在我的设备上进行了测试,它可以工作。 @ab11 FWIW,您使用的 Android 版本越新,性能越差,这不仅适用于存储,还适用于 /sys 文件夹等其他路径!在 Marshmallow 上,getCanonicalPath 大多数时候返回原始路径,即使 ls -l 显示路径是链接。我找到的唯一解决方案是使用 shell 运行 ls -l 或 readlink 命令。【参考方案2】:

对此可能没有简单的解决方案。问题在于,文件系统中显然有两个不同的挂载点实际上指向同一个位置。 File.getCanonicalPath() 只能解析符号链接,不能解析不同的挂载点。

例如在我的 Nexus 4 上这个代码:

File file1 = new File(Environment.getExternalStorageDirectory() + "/Android");
System.out.println("file 1: " + file1.getCanonicalPath());
File file2 = new File("/sdcard/Android");
System.out.println("file 2: " + file2.getCanonicalPath());

打印

file 1: /storage/emulated/0/Android
file 2: /storage/emulated/legacy/Android

我使用这段代码执行“mount”并打印输出:

Process exec = Runtime.getRuntime().exec("mount");
InputStream in = exec.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
while (true) 
    String line = br.readLine();
    if (line == null)
        break;
    System.out.println(line);

in.close();

相关输出为:

/dev/fuse /storage/emulated/0 fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0
/dev/fuse /storage/emulated/legacy fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0

【讨论】:

是的,这就是问题所在。 Android 4.2 的多用户存储真的很麻烦 使用 genymotion AOSP 模拟器,只有一个挂载点,/mnt/shell/emulated,但 getCanonicalPath 仍然不起作用。 @Harvey,我无法确认。 Genymotion Nexus 4 API 19 显示了两个挂载点,/storage/emulated/0 和 /storage/emulated/legacy。【参考方案3】:

看看答案here。它也使用规范路径,但方式略有不同,可能适合您

【讨论】:

如果尚未创建文件夹,则 getCanonicalPath() 可能会返回与传递给 File 构造函数的路径相同的路径,就像 OP 报告文件一样。我觉得关键大概是在创建文件之后调用方法吧。【参考方案4】:

在较新的 Android 版本上,SD 存储可通过多种途径获得,例如:

/storage/emulated/0
/storage/emulated/legacy (root account most of the time)
/sdcard
/data/media

如果您检查这些路径位于哪个设备上,有些位于不同的设备上(因为 fuse 'virtual' 文件系统),因此获取它们的规范路径不会导致相同的文件路径,即使它们实际上是相同的文件。

此外,在 Marshmallow 上,情况似乎变得更糟,甚至 /sys 下的文件路径(充满重定向/链接)都没有正确报告,并且 getCanonicalPath() 返回原始路径而不是实际的规范路径。

虽然给定文件路径上的 ls -l(或 readlink)将显示实际的规范路径,但 API 不再起作用。不幸的是,运行 readlink 或 ls -l 非常慢(shell 已经运行时平均为 130 毫秒),与已经很慢但快得多的 getCanonicalPath() 相比,这很遗憾。

结论,getCanonicalPath坏了,一直坏。

【讨论】:

以上是关于Android:消除文件路径的歧义的主要内容,如果未能解决你的问题,请参考以下文章

启动游戏失败,请检查安装路径是不是过长或含有特殊字符

python之文件目录和路径

手机下载东西提示请检查文件名是不是过长或包含特殊字符

Android 文件绝对路径和Content开头的Uri互相转换

Android 11 (R) 文件路径访问

Android 通过uri获取文件路径path