从 Lollipop 中的 google 驱动器获取文件路径(MediaStore.MediaColumns.DATA == null)
Get filepath from google drive in Lollipop (MediaStore.MediaColumns.DATA == null)
当用户单击 google 驱动器中的“发送文件”按钮并选择我的应用程序时。我想获取该文件的文件路径,然后允许用户将其上传到不同的位置。
我检查了这些类似的 SO post 以寻找 kitkat 手机:Get real path from URI, Android KitKat new storage access framework
Android - Convert URI to file path on lollipop
然而,该解决方案似乎不再适用于 Lollipop 设备。
问题似乎是 MediaStore.MediaColumns.DATA returns 当 运行 ContentResolver 上的查询时为空。
https://code.google.com/p/android/issues/detail?id=63651
You should use ContentResolver.openFileDescriptor() instead of trying to get a raw filesystem path. The "_data" column is not part of the CATEGORY_OPENABLE contract, so Drive is not required to return it.
我读过这个 blog post by CommonsWare,它建议我“尝试将 Uri 直接与 ContentResolver 一起使用”,我不明白。如何将 URI 直接与 ContentResolvers 一起使用?
但是,我仍然不清楚如何最好地处理这些类型的 URI。
我找到的最佳解决方案是调用 openFileDescriptor,然后将文件流复制到一个新文件中,然后将该新文件路径传递给我的上传 activity.
private static String getDriveFileAbsolutePath(Activity context, Uri uri) {
if (uri == null) return null;
ContentResolver resolver = context.getContentResolver();
FileInputStream input = null;
FileOutputStream output = null;
String outputFilePath = new File(context.getCacheDir(), fileName).getAbsolutePath();
try {
ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
FileDescriptor fd = pfd.getFileDescriptor();
input = new FileInputStream(fd);
output = new FileOutputStream(outputFilePath);
int read = 0;
byte[] bytes = new byte[4096];
while ((read = input.read(bytes)) != -1) {
output.write(bytes, 0, read);
}
return new File(outputFilePath).getAbsolutePath();
} catch (IOException ignored) {
// nothing we can do
} finally {
input.close();
output.close();
}
return "";
}
这里唯一的问题是我丢失了那个文件的文件名。这似乎有点过于复杂,只是为了从驱动器中获取文件路径。有更好的方法吗?
谢谢。
编辑:
所以我可以使用普通查询来获取文件名。然后我可以将其传递到我的 getDriveAbsolutePath() 方法中。这将使我非常接近我想要的,现在唯一的问题是我缺少文件扩展名。我所做的所有搜索都建议使用文件路径来获取扩展名,而我不能使用 openFileDescriptor() 来做到这一点。有帮助吗?
String filename = "";
final String[] projection = {
MediaStore.MediaColumns.DISPLAY_NAME
};
ContentResolver cr = context.getApplicationContext().getContentResolver();
Cursor metaCursor = cr.query(uri, projection, null, null, null);
if (metaCursor != null) {
try {
if (metaCursor.moveToFirst()) {
filename = metaCursor.getString(0);
}
} finally {
metaCursor.close();
}
}
但是,我并不完全相信这是执行此操作的“正确”方法?
The only problem here is that I lose the filename of that file. This seems a bit over complicated just to get a filePath from drive. Is there a better way to do this?
你似乎错过了一个重要的点。 Linux 中的文件不需要有名称。它们可能存在于内存中(例如 android.os.MemoryFile
),甚至驻留在没有名称的目录中(例如文件、创建的
带有 O_TMPFILE
标志)。他们需要的是 文件描述符 .
简短摘要:文件描述符比简单文件更好,应该始终使用,除非在你自己关闭它们后你的负担太大。如果您可以使用 JNI,它们可以用于与 File
对象相同的事情,甚至更多。它们由特殊的 ContentProvider 提供,可以通过 ContentResolver 的 openFileDescriptor
方法访问(它接收 Uri,与目标提供者相关联)。
也就是说,只是说人们,习惯于 File
对象,用描述符替换它们听起来确实很奇怪。如果您想尝试一下,请阅读下面的详尽解释。如果不这样做,请跳至 "simple" 解决方案答案的底部。
编辑: 下面的答案是在 Lollipop 流行之前写的。现在有 a handy class 可以直接访问 Linux 系统调用,这使得使用 JNI 处理文件描述符成为可选的。
关于描述符的快速简报
文件描述符来自Linux open
系统调用和C库中相应的open()
函数。您无需访问文件即可对其描述符进行操作。大多数访问检查将被简单地跳过,但是一些关键信息,例如访问类型(read/write/read-and-write 等)是 "hardcoded" 到描述符中,并且在创建后不能更改。文件描述符由从 0 开始的非负整数表示。这些数字对于每个进程都是本地的,没有任何持久性或系统范围的意义,它们只是区分句柄
给定进程的文件(0、1 和 2 传统上引用 stdin
、stdout
和 stderr
)。
每个描述符都由对描述符 table 中条目的引用表示,存储在 OS 内核中。 table 中有每个进程和系统范围的 limits 到条目数,因此请快速关闭描述符,除非您希望打开事物并创建新描述符的尝试突然失败。
对描述符进行操作
在Linux中有两种C库函数和系统调用:使用名称(如readdir()
、stat()
、chdir()
、chown()
, open()
, link()
) 和操作描述符:getdents
, fstat()
, fchdir()
, fchown()
, fchownat()
, openat()
、linkat()
等。在阅读几个手册页并研究一些黑暗的 JNI 魔法后,您可以轻松调用这些函数和系统调用。这将通过屋顶提高您的软件质量! (以防万一:我说的是阅读和学习,而不是一直盲目使用JNI。
在 Java 中有一个 class 用于处理描述符:java.io.FileDescriptor
。它 can be used 具有 FileXXXStream
classes,因此间接具有所有框架 IO classes,包括内存映射和随机访问文件、通道和通道锁。这是一个棘手的class。由于需要与某些专有 OS 兼容,此跨平台 class 不公开底层整数。它甚至无法关闭!相反,您应该关闭相应的 IO classes,它们(再次出于兼容性原因)彼此共享相同的底层描述符:
FileInputStream fileStream1 = new FileInputStream("notes.db");
FileInputStream fileStream2 = new FileInputStream(fileStream1.getFD());
WritableByteChannel aChannel = fileStream1.getChannel();
// pass fileStream1 and aChannel to some methods, written by clueless people
...
// surprise them (or get surprised by them)
fileStream2.close();
没有支持的方法从 FileDescriptor
中获取整数值,但您可以(几乎)安全地假设,在旧的 OS 版本上有一个私有整数 descriptor
字段,可以通过反射访问。
搬起石头砸自己的脚
在 Android 框架中有一个专门的 class 用于处理 Linux 文件描述符:android.os.ParcelFileDescriptor
。不幸的是,它几乎和 FileDescriptor 一样糟糕。为什么?有两个原因:
1) 它有一个 finalize()
方法。阅读它的 javadoc 以了解这对您的性能意味着什么。如果你不想面对突然的 IO 错误,你仍然必须关闭它。
2) 由于可终结,一旦对 class 实例的引用超出范围,虚拟机将自动关闭它。这就是为什么在某些框架 classes 上使用 finalize()
, 尤其是 MemoryFile
是部分框架开发人员的错误:
public FileOutputStream giveMeAStream() {
ParcelFileDescriptor fd = ParcelFileDescriptor.open("myfile", MODE_READ_ONLY);
return new FileInputStream(fd.getDescriptor());
}
...
FileInputStream aStream = giveMeAStream();
// enjoy having aStream suddenly closed during garbage collection
幸运的是,有一种方法可以解决这种恐怖问题:神奇的 dup
系统调用:
public FileOutputStream giveMeAStream() {
ParcelFileDescriptor fd = ParcelFileDescriptor.open("myfile", MODE_READ_ONLY);
return new FileInputStream(fd.dup().getDescriptor());
}
...
FileInputStream aStream = giveMeAStream();
// you are perfectly safe now...
// Just kidding! Also close original ParcelFileDescriptor like this:
public FileOutputStream giveMeAStreamProperly() {
// Use try-with-resources block, because closing things in Java is hard.
// You can employ Retrolambda for backward compatibility,
// it can handle those too!
try (ParcelFileDescriptor fd = ParcelFileDescriptor.open("myfile", MODE_READ_ONLY)) {
return new FileInputStream(fd.dup().getDescriptor());
}
}
dup
系统调用克隆整数文件描述符,这使得相应的 FileDescriptor
独立于原始文件描述符。请注意,跨进程传递描述符不需要手动复制:已收到
描述符独立于源进程。传递 MemoryFile
的描述符(如果您通过反射获得它) 是否 需要调用 dup
:共享内存区域在发起时被破坏
过程将使每个人都无法访问它。此外,您必须在本机代码中执行 dup
或保留对创建的 ParcelFileDescriptor
的引用,直到接收器完成您的 MemoryFile
.
提供和接收描述符
有两种方法可以提供和接收文件描述符:让子进程继承创建者的描述符和通过进程间通信。
让进程的子进程继承由创建者打开的文件、管道和套接字是 Linux 中的常见做法,但需要在 Android – Runtime.exec()
上分叉本机代码并且ProcessBuilder
关闭所有多余的
创建子进程后的描述符。如果您选择,请确保也 close unnecessary descriptors
fork
你自己。
目前唯一支持在 Android 上传递文件描述符的 IPC 工具是 Binder 和 Linux 域套接字。
Binder 允许您将 ParcelFileDescriptor
提供给任何接受 parcelable 对象的东西,包括将它们放入 Bundle、从内容提供者返回以及通过 AIDL 调用传递给服务。
请注意,大多数尝试在进程外部传递带有描述符的 Bundle,包括调用 startActivityForResult
都会被系统拒绝,这可能是因为及时关闭这些描述符太难了。更好的选择是创建一个 ContentProvider(它为您管理描述符生命周期,并通过 ContentResolver
发布文件)或编写一个 AIDL 接口并在描述符传输后立即关闭它。另请注意,坚持 ParcelFileDescriptor
任何地方都没有多大意义:它只会在进程死亡之前有效,并且相应的整数很可能会指向其他东西,一旦你的进程被重新创建。
域套接字是低级的,用于描述符传输有点痛苦,尤其是与提供程序和 AIDL 相比。然而,它们是本机进程的一个很好的(也是唯一记录的)选项。如果你是
被迫打开文件 and/or 使用本机二进制文件移动数据(这通常是应用程序的情况,使用 root 权限),请考虑不要浪费你的努力和 CPU 与复杂通信的资源
这些二进制文件,而不是写一个 open helper。 [无耻的广告] 顺便说一下,你可以使用 the one I wrote,而不是创建你自己的。 [/无耻广告]
确切问题的答案
我希望这个答案给了你一个好主意,MediaStore.MediaColumns.DATA 有什么问题,以及为什么 Android 开发团队创建这个专栏用词不当。
就是说,如果您仍然不信服,请不惜一切代价想要那个文件名,或者只是没能阅读上面铺天盖地的文字墙,这里 – 准备好 –待办 JNI 功能;灵感来自 Getting Filename from file descriptor in C (EDIT: now has a ):
// src/main/jni/fdutil.c
JNIEXPORT jstring Java_com_example_FdUtil_getFdPathInternal(JNIEnv *env, jint descriptor)
{
// The filesystem name may not fit in PATH_MAX, but all workarounds
// (as well as resulting strings) are prone to OutOfMemoryError.
// The proper solution would, probably, include writing a specialized
// CharSequence. Too much pain, too little gain.
char buf[PATH_MAX + 1] = { 0 };
char procFile[25];
sprintf(procFile, "/proc/self/fd/%d", descriptor);
if (readlink(procFile, buf, sizeof(buf)) == -1) {
// the descriptor is no more, became inaccessible etc.
jclass exClass = (*env) -> FindClass(env, "java/io/IOException");
(*env) -> ThrowNew(env, exClass, "readlink() failed");
return NULL;
}
if (buf[PATH_MAX] != 0) {
// the name is over PATH_MAX bytes long, the caller is at fault
// for dealing with such tricky descriptors
jclass exClass = (*env) -> FindClass(env, "java/io/IOException");
(*env) -> ThrowNew(env, exClass, "The path is too long");
return NULL;
}
if (buf[0] != '/') {
// the name is not in filesystem namespace, e.g. a socket,
// pipe or something like that
jclass exClass = (*env) -> FindClass(env, "java/io/IOException");
(*env) -> ThrowNew(env, exClass, "The descriptor does not belong to file with name");
return NULL;
}
// doing stat on file does not give any guarantees, that it
// will remain valid, and on Android it likely to be
// inaccessible to us anyway let's just hope
return (*env) -> NewStringUTF(env, buf);
}
这是一个 class,与之相伴的是:
// com/example/FdUtil.java
public class FdUtil {
static {
System.loadLibrary(System.mapLibraryName("fdutil"));
}
public static String getFdPath(ParcelFileDescriptor fd) throws IOException {
int intFd = fd.getFd();
if (intFd <= 0)
throw new IOException("Invalid fd");
return getFdPathInternal(intFd);
}
private static native String getFdPathInternal(int fd) throws IOException;
}
当用户单击 google 驱动器中的“发送文件”按钮并选择我的应用程序时。我想获取该文件的文件路径,然后允许用户将其上传到不同的位置。
我检查了这些类似的 SO post 以寻找 kitkat 手机:Get real path from URI, Android KitKat new storage access framework
Android - Convert URI to file path on lollipop
然而,该解决方案似乎不再适用于 Lollipop 设备。
问题似乎是 MediaStore.MediaColumns.DATA returns 当 运行 ContentResolver 上的查询时为空。
https://code.google.com/p/android/issues/detail?id=63651
You should use ContentResolver.openFileDescriptor() instead of trying to get a raw filesystem path. The "_data" column is not part of the CATEGORY_OPENABLE contract, so Drive is not required to return it.
我读过这个 blog post by CommonsWare,它建议我“尝试将 Uri 直接与 ContentResolver 一起使用”,我不明白。如何将 URI 直接与 ContentResolvers 一起使用?
但是,我仍然不清楚如何最好地处理这些类型的 URI。
我找到的最佳解决方案是调用 openFileDescriptor,然后将文件流复制到一个新文件中,然后将该新文件路径传递给我的上传 activity.
private static String getDriveFileAbsolutePath(Activity context, Uri uri) {
if (uri == null) return null;
ContentResolver resolver = context.getContentResolver();
FileInputStream input = null;
FileOutputStream output = null;
String outputFilePath = new File(context.getCacheDir(), fileName).getAbsolutePath();
try {
ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
FileDescriptor fd = pfd.getFileDescriptor();
input = new FileInputStream(fd);
output = new FileOutputStream(outputFilePath);
int read = 0;
byte[] bytes = new byte[4096];
while ((read = input.read(bytes)) != -1) {
output.write(bytes, 0, read);
}
return new File(outputFilePath).getAbsolutePath();
} catch (IOException ignored) {
// nothing we can do
} finally {
input.close();
output.close();
}
return "";
}
这里唯一的问题是我丢失了那个文件的文件名。这似乎有点过于复杂,只是为了从驱动器中获取文件路径。有更好的方法吗?
谢谢。
编辑: 所以我可以使用普通查询来获取文件名。然后我可以将其传递到我的 getDriveAbsolutePath() 方法中。这将使我非常接近我想要的,现在唯一的问题是我缺少文件扩展名。我所做的所有搜索都建议使用文件路径来获取扩展名,而我不能使用 openFileDescriptor() 来做到这一点。有帮助吗?
String filename = "";
final String[] projection = {
MediaStore.MediaColumns.DISPLAY_NAME
};
ContentResolver cr = context.getApplicationContext().getContentResolver();
Cursor metaCursor = cr.query(uri, projection, null, null, null);
if (metaCursor != null) {
try {
if (metaCursor.moveToFirst()) {
filename = metaCursor.getString(0);
}
} finally {
metaCursor.close();
}
}
但是,我并不完全相信这是执行此操作的“正确”方法?
The only problem here is that I lose the filename of that file. This seems a bit over complicated just to get a filePath from drive. Is there a better way to do this?
你似乎错过了一个重要的点。 Linux 中的文件不需要有名称。它们可能存在于内存中(例如 android.os.MemoryFile
),甚至驻留在没有名称的目录中(例如文件、创建的
带有 O_TMPFILE
标志)。他们需要的是 文件描述符 .
简短摘要:文件描述符比简单文件更好,应该始终使用,除非在你自己关闭它们后你的负担太大。如果您可以使用 JNI,它们可以用于与 File
对象相同的事情,甚至更多。它们由特殊的 ContentProvider 提供,可以通过 ContentResolver 的 openFileDescriptor
方法访问(它接收 Uri,与目标提供者相关联)。
也就是说,只是说人们,习惯于 File
对象,用描述符替换它们听起来确实很奇怪。如果您想尝试一下,请阅读下面的详尽解释。如果不这样做,请跳至 "simple" 解决方案答案的底部。
编辑: 下面的答案是在 Lollipop 流行之前写的。现在有 a handy class 可以直接访问 Linux 系统调用,这使得使用 JNI 处理文件描述符成为可选的。
关于描述符的快速简报
文件描述符来自Linux open
系统调用和C库中相应的open()
函数。您无需访问文件即可对其描述符进行操作。大多数访问检查将被简单地跳过,但是一些关键信息,例如访问类型(read/write/read-and-write 等)是 "hardcoded" 到描述符中,并且在创建后不能更改。文件描述符由从 0 开始的非负整数表示。这些数字对于每个进程都是本地的,没有任何持久性或系统范围的意义,它们只是区分句柄
给定进程的文件(0、1 和 2 传统上引用 stdin
、stdout
和 stderr
)。
每个描述符都由对描述符 table 中条目的引用表示,存储在 OS 内核中。 table 中有每个进程和系统范围的 limits 到条目数,因此请快速关闭描述符,除非您希望打开事物并创建新描述符的尝试突然失败。
对描述符进行操作
在Linux中有两种C库函数和系统调用:使用名称(如readdir()
、stat()
、chdir()
、chown()
, open()
, link()
) 和操作描述符:getdents
, fstat()
, fchdir()
, fchown()
, fchownat()
, openat()
、linkat()
等。在阅读几个手册页并研究一些黑暗的 JNI 魔法后,您可以轻松调用这些函数和系统调用。这将通过屋顶提高您的软件质量! (以防万一:我说的是阅读和学习,而不是一直盲目使用JNI。
在 Java 中有一个 class 用于处理描述符:java.io.FileDescriptor
。它 can be used 具有 FileXXXStream
classes,因此间接具有所有框架 IO classes,包括内存映射和随机访问文件、通道和通道锁。这是一个棘手的class。由于需要与某些专有 OS 兼容,此跨平台 class 不公开底层整数。它甚至无法关闭!相反,您应该关闭相应的 IO classes,它们(再次出于兼容性原因)彼此共享相同的底层描述符:
FileInputStream fileStream1 = new FileInputStream("notes.db");
FileInputStream fileStream2 = new FileInputStream(fileStream1.getFD());
WritableByteChannel aChannel = fileStream1.getChannel();
// pass fileStream1 and aChannel to some methods, written by clueless people
...
// surprise them (or get surprised by them)
fileStream2.close();
没有支持的方法从 FileDescriptor
中获取整数值,但您可以(几乎)安全地假设,在旧的 OS 版本上有一个私有整数 descriptor
字段,可以通过反射访问。
搬起石头砸自己的脚
在 Android 框架中有一个专门的 class 用于处理 Linux 文件描述符:android.os.ParcelFileDescriptor
。不幸的是,它几乎和 FileDescriptor 一样糟糕。为什么?有两个原因:
1) 它有一个 finalize()
方法。阅读它的 javadoc 以了解这对您的性能意味着什么。如果你不想面对突然的 IO 错误,你仍然必须关闭它。
2) 由于可终结,一旦对 class 实例的引用超出范围,虚拟机将自动关闭它。这就是为什么在某些框架 classes 上使用 finalize()
, 尤其是 MemoryFile
是部分框架开发人员的错误:
public FileOutputStream giveMeAStream() {
ParcelFileDescriptor fd = ParcelFileDescriptor.open("myfile", MODE_READ_ONLY);
return new FileInputStream(fd.getDescriptor());
}
...
FileInputStream aStream = giveMeAStream();
// enjoy having aStream suddenly closed during garbage collection
幸运的是,有一种方法可以解决这种恐怖问题:神奇的 dup
系统调用:
public FileOutputStream giveMeAStream() {
ParcelFileDescriptor fd = ParcelFileDescriptor.open("myfile", MODE_READ_ONLY);
return new FileInputStream(fd.dup().getDescriptor());
}
...
FileInputStream aStream = giveMeAStream();
// you are perfectly safe now...
// Just kidding! Also close original ParcelFileDescriptor like this:
public FileOutputStream giveMeAStreamProperly() {
// Use try-with-resources block, because closing things in Java is hard.
// You can employ Retrolambda for backward compatibility,
// it can handle those too!
try (ParcelFileDescriptor fd = ParcelFileDescriptor.open("myfile", MODE_READ_ONLY)) {
return new FileInputStream(fd.dup().getDescriptor());
}
}
dup
系统调用克隆整数文件描述符,这使得相应的 FileDescriptor
独立于原始文件描述符。请注意,跨进程传递描述符不需要手动复制:已收到
描述符独立于源进程。传递 MemoryFile
的描述符(如果您通过反射获得它) 是否 需要调用 dup
:共享内存区域在发起时被破坏
过程将使每个人都无法访问它。此外,您必须在本机代码中执行 dup
或保留对创建的 ParcelFileDescriptor
的引用,直到接收器完成您的 MemoryFile
.
提供和接收描述符
有两种方法可以提供和接收文件描述符:让子进程继承创建者的描述符和通过进程间通信。
让进程的子进程继承由创建者打开的文件、管道和套接字是 Linux 中的常见做法,但需要在 Android – Runtime.exec()
上分叉本机代码并且ProcessBuilder
关闭所有多余的
创建子进程后的描述符。如果您选择,请确保也 close unnecessary descriptors
fork
你自己。
目前唯一支持在 Android 上传递文件描述符的 IPC 工具是 Binder 和 Linux 域套接字。
Binder 允许您将 ParcelFileDescriptor
提供给任何接受 parcelable 对象的东西,包括将它们放入 Bundle、从内容提供者返回以及通过 AIDL 调用传递给服务。
请注意,大多数尝试在进程外部传递带有描述符的 Bundle,包括调用 startActivityForResult
都会被系统拒绝,这可能是因为及时关闭这些描述符太难了。更好的选择是创建一个 ContentProvider(它为您管理描述符生命周期,并通过 ContentResolver
发布文件)或编写一个 AIDL 接口并在描述符传输后立即关闭它。另请注意,坚持 ParcelFileDescriptor
任何地方都没有多大意义:它只会在进程死亡之前有效,并且相应的整数很可能会指向其他东西,一旦你的进程被重新创建。
域套接字是低级的,用于描述符传输有点痛苦,尤其是与提供程序和 AIDL 相比。然而,它们是本机进程的一个很好的(也是唯一记录的)选项。如果你是 被迫打开文件 and/or 使用本机二进制文件移动数据(这通常是应用程序的情况,使用 root 权限),请考虑不要浪费你的努力和 CPU 与复杂通信的资源 这些二进制文件,而不是写一个 open helper。 [无耻的广告] 顺便说一下,你可以使用 the one I wrote,而不是创建你自己的。 [/无耻广告]
确切问题的答案
我希望这个答案给了你一个好主意,MediaStore.MediaColumns.DATA 有什么问题,以及为什么 Android 开发团队创建这个专栏用词不当。
就是说,如果您仍然不信服,请不惜一切代价想要那个文件名,或者只是没能阅读上面铺天盖地的文字墙,这里 – 准备好 –待办 JNI 功能;灵感来自 Getting Filename from file descriptor in C (EDIT: now has a
// src/main/jni/fdutil.c
JNIEXPORT jstring Java_com_example_FdUtil_getFdPathInternal(JNIEnv *env, jint descriptor)
{
// The filesystem name may not fit in PATH_MAX, but all workarounds
// (as well as resulting strings) are prone to OutOfMemoryError.
// The proper solution would, probably, include writing a specialized
// CharSequence. Too much pain, too little gain.
char buf[PATH_MAX + 1] = { 0 };
char procFile[25];
sprintf(procFile, "/proc/self/fd/%d", descriptor);
if (readlink(procFile, buf, sizeof(buf)) == -1) {
// the descriptor is no more, became inaccessible etc.
jclass exClass = (*env) -> FindClass(env, "java/io/IOException");
(*env) -> ThrowNew(env, exClass, "readlink() failed");
return NULL;
}
if (buf[PATH_MAX] != 0) {
// the name is over PATH_MAX bytes long, the caller is at fault
// for dealing with such tricky descriptors
jclass exClass = (*env) -> FindClass(env, "java/io/IOException");
(*env) -> ThrowNew(env, exClass, "The path is too long");
return NULL;
}
if (buf[0] != '/') {
// the name is not in filesystem namespace, e.g. a socket,
// pipe or something like that
jclass exClass = (*env) -> FindClass(env, "java/io/IOException");
(*env) -> ThrowNew(env, exClass, "The descriptor does not belong to file with name");
return NULL;
}
// doing stat on file does not give any guarantees, that it
// will remain valid, and on Android it likely to be
// inaccessible to us anyway let's just hope
return (*env) -> NewStringUTF(env, buf);
}
这是一个 class,与之相伴的是:
// com/example/FdUtil.java
public class FdUtil {
static {
System.loadLibrary(System.mapLibraryName("fdutil"));
}
public static String getFdPath(ParcelFileDescriptor fd) throws IOException {
int intFd = fd.getFd();
if (intFd <= 0)
throw new IOException("Invalid fd");
return getFdPathInternal(intFd);
}
private static native String getFdPathInternal(int fd) throws IOException;
}