安卓:消除文件路径的歧义

11
在我的应用程序中,用户选择文件。内部,我会基于文件路径存储有关该文件的信息。下次使用该文件时,我会处理存储的信息。问题是我使用以下代码实例化文件:
``` 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();

同一个文件有两个不同的绝对路径?这些路径是不同的,可以在存储上存在而不互斥。您能详细说明一下情况吗?Intent发送给您时是如何准备的,何时发送的? - Deepak Bala
我希望有一种方法可以将它们等同起来,因为不同的路径指向同一个文件。或者,如果我将Environment.getExternalStorageDirectory()评估为"/mnt/sdcard/"而不是"/storage/emulated/0",这将减少此问题的发生(但我不知道如何做到这一点)。 - ab11
4个回答

6

首先,获取外部路径的唯一正确方式是使用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。因此,不同的设备可能包含外部存储的不同路径。

《外部存储技术信息》所述,您可以通过设置 init.rc 文件来自定义设备的存储。例如在默认的 goldfish 中:

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平台上)。
每个表示现有文件或目录的路径名都有一个唯一的规范形式。每个表示不存在的文件或目录的路径名也有一个唯一的规范形式。一个不存在的文件或目录的路径名的规范形式可能与创建该文件或目录后相同路径名的规范形式不同。同样,现有文件或目录的路径名的规范形式可能与删除相同路径名的文件或目录后的规范形式不同。

3
问题是,给定两个路径 "/mnt/sdcard/test.txt" 和 "/storage/emulated/0/test.txt":File file1 = new File(path1),File file2 = new File(path2)。尽管它们指向同一个文件,但file1.getCanonicalPath().equals(file2.getCanonicalPath()) 的值为false。 - ab11
你确定它们引用的是同一个文件吗?修改一个文件会影响另一个吗?我在我的设备上进行了测试,它可以正常工作。@ab11 - StarPinkER
就我所知,你使用的Android版本越新,情况就越糟糕,这不仅适用于存储,还适用于其他路径,比如/sys文件夹!在Marshmallow上,getCanonicalPath大多数时候返回原始路径,即使ls -l显示该路径是一个链接。我找到的唯一解决方案是使用shell运行ls -l或readlink命令。 - 3c71

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

1
是的,这就是问题。Android 4.2的多用户存储确实非常麻烦。 - Romulus Urakagi Ts'ai
使用Genymotion AOSP模拟器时,只有一个挂载点/mnt/shell/emulated,但getCanonicalPath仍然无法工作。 - Harvey
@Harvey,我无法确认。Genymotion Nexus 4 API 19显示了两个挂载点,/storage/emulated/0和/storage/emulated/legacy。 - devconsole

0

在较新的Android版本中,SD存储可从许多路径访问,例如:

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

如果您查看这些路径所在的设备,有些位于不同的设备上(因为fuse“虚拟”文件系统),因此获取它们的规范路径并不会导致相同的文件路径,即使它们实际上是相同的文件。

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

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

结论:getCanonicalPath已经损坏并且一直被损坏。


0

请看这里的答案here。它也使用了规范路径,但是以稍微不同的方式,可能适合您。


如果文件夹尚未创建,则getCanonicalPath()可能会返回与传递给File构造函数的相同路径,就像OP对文件报告的那样。我认为关键可能是在文件创建后调用该方法。 - Carl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接