从不一致的 Android 本机文件中获取 URI 文件路径选择返回的 URI
Get URI file path from inconsistent Android native file pick returned URIs
从 Android N 起,我正面临这个问题。当我 select 来自左侧文件选择器抽屉中的任何应用程序(例如图库和文件管理器)的文件时,会收到 URI,我可以对文件执行我需要的操作。
此外,如果我 select 一个文件,而 Images/Videos/Audio 选项卡在文件选择器的抽屉中突出显示,我选择的任何文件都会成功返回其 URI。
在我的 onActivity 结果中,Log.i("File URI to String", data.getDataString());
输出
I/File URI to String: content://com.android.providers.media.documents/document/image%3A20399
我可以使用下面的 GetPath 方法获取文件路径。
但是当我只是从选择器中 select 突出显示内部或外部存储时,会发生注释,Log.i("File URI to String", data.getDataString());
输出
I/File URI to String: content://com.android.externalstorage.documents/document/CA8B-8BD2%3ADCIM%2F100ANDRO%2FDSC_0001.JPG
我的 getPath 方法无法为其获取路径,我认为这可能与提供者有关,查看字符串形式的 uris 之间的差异。
如何获取这些 selection 的文件路径似乎不受任何提供商的支持。任何帮助表示赞赏。
我启动文件选择器的代码:
Intent intent = new Intent();
intent.setType("*/*");
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(intent, DEFAULT_IMAGE_PICK);
当我启动文件选择器意图时
2018-10-23 21:51:26.345 1572-10747/? I/ActivityManager: START u0 {act=android.intent.action.OPEN_DOCUMENT cat=[android.intent.category.OPENABLE] typ=*/* cmp=com.android.documentsui/.picker.PickActivity (has extras)} from uid 10298
2018-10-23 21:51:26.352 1572-10747/? D/ActivityTrigger: activityStartTrigger: Activity is Triggerred in full screen ApplicationInfo{e1aa80b com.android.documentsui}
2018-10-23 21:51:26.352 1572-10747/? E/ActivityTrigger: activityStartTrigger: not whiteListedcom.android.documentsui/com.android.documentsui.picker.PickActivity/26
2018-10-23 21:51:26.353 1572-10747/? D/CompatibilityInfo: mCompatibilityFlags - 0
2018-10-23 21:51:26.353 1572-10747/? D/CompatibilityInfo: applicationDensity - 320
2018-10-23 21:51:26.353 1572-10747/? D/CompatibilityInfo: applicationScale - 1.0
2018-10-23 21:51:26.353 1572-10747/? D/ActivityTrigger: activityResumeTrigger: The activity in ApplicationInfo{e1aa80b com.android.documentsui} is now in focus and seems to be in full-screen mode
2018-10-23 21:51:26.353 1572-10747/? E/ActivityTrigger: activityResumeTrigger: not whiteListedcom.android.documentsui/com.android.documentsui.picker.PickActivity/26
2018-10-23 21:51:26.353 1572-10747/? D/ActivityTrigger: ActivityTrigger activityPauseTrigger
2018-10-23 21:51:26.376 1572-10747/? D/ActivityTrigger: activityResumeTrigger: The activity in ApplicationInfo{e1aa80b com.android.documentsui} is now in focus and seems to be in full-screen mode
2018-10-23 21:51:26.376 1572-10747/? E/ActivityTrigger: activityResumeTrigger: not whiteListedcom.android.documentsui/com.android.documentsui.picker.PickActivity/26
2018-10-23 21:51:26.380 1572-10747/? D/CompatibilityInfo: mCompatibilityFlags - 0
2018-10-23 21:51:26.380 1572-10747/? D/CompatibilityInfo: applicationDensity - 320
2018-10-23 21:51:26.380 1572-10747/? D/CompatibilityInfo: applicationScale - 1.0
2018-10-23 21:51:26.409 1572-2271/? I/InputDispatcher: Focus entered window: Window{9ac7c5c u0 com.android.documentsui/com.android.documentsui.picker.PickActivity}
2018-10-23 21:51:26.459 1572-1634/? I/ActivityManager: Displayed com.android.documentsui/.picker.PickActivity: +80ms
这是我的清单 file.I 已经实现了与其他应用程序共享文件 Uris 的提供商:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
在我的onActivityResult
if ((requestCode == SELECT_GALLERY_FILE || requestCode == DEFAULT_IMAGE_PICK)
&& resultCode == Activity.RESULT_OK)
{
Log.i("File URI to String", data.getDataString());
if(data.getData()!=null){//Receiving one file
filelist.add(getPath(getActivity(),data.getData()));
filelist = ListDeduplicator(filelist);
Log.i("File Added", getPath(getActivity(),data.getData()));
//Add Path of selected file to list of files to be loaded
}
if (data.getClipData() != null) {//Receiving several
ClipData mClipData = data.getClipData();
for (int i = 0; i < mClipData.getItemCount(); i++) {
//Add Paths of selected files to list of files to be loaded
ClipData.Item item = mClipData.getItemAt(i);
filelist.add(getPath(getActivity(),item.getUri()));
Log.i("File Added", getPath(getActivity(),item.getUri()));
}
filelist = ListDeduplicator(filelist);
}
CheckAndExecute();
}
我的GetPath方法在onActivityResult中实现
public static String getPath(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
//Log.i("CHECK", "1");
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
//Log.i("CHECK", split[1]);
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}else{
return Search_Dir(new File("/storage") ,split[1]);
/*for (String string : getStorageDirectories(context)) {
String TestPath = string+"/"+split[1];
if ((new File(TestPath)).exists()) {
return TestPath;
}
}*/
}
// TODO handle non-primary volumes
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
//Log.i("CHECK", "2");
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
//Log.i("CHECK", "3");
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[] {
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
//Log.i("CHECK", "4");
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
//Log.i("CHECK", "5");
return uri.getPath();
}
return null;
}
你检查过this了吗?
A FileProvider can only generate a content URI for files in
directories that you specify beforehand. To specify a directory,
specify the its storage area and path in XML, using child elements of
the element.
阅读以下路径,对您有帮助:
<files-path name="name" path="path" />
<cache-path name="name" path="path" />
<external-path name="name" path="path" />
<external-files-path name="name" path="path" />
<external-cache-path name="name" path="path" />
如果我没理解错的话,您正在尝试读取其他应用程序提供的数据,因此文件提供程序配置不相关。
我认为您的代码中的问题是您试图从收到的 URI 中提取文件路径。这不能可靠地工作。不保证文档 ID 包含路径或您可以查询包含路径的 _DATA
列。基本上,内容 URI 应该是一个不透明的句柄,只能通过 ContentResolver
的方法使用,例如 :
query() to get informations, like the size or a display name (the only guarantied columns)
Mark Murphy 有更详细的 explanation
你能在你的 Application.onCreate()
中试试这个代码吗?如果它对你有用,请告诉我?
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
您无法从 android 的 Intent.ACTION_GET_CONTENT 获取通用文件 URI。 URI 可能因文件提供程序的不同实现而异。如果你想要一个通用的 URI,你可以实现你自己的文件选择器或者你可以使用一些库。
如果您的要求只是将文件发送到服务器,那么您可以直接使用来自 URI 的输入流并将此输入流发送到服务器。
有关详细信息,请参阅 SO 。
在我的案例中,我处理场景的另一种方法是从您提到的 GetPath 方法获取路径,如果它无法提供来自 URI 的路径然后使用 URI 提供的输入流将文件保存在存储中的某个位置。下面是保存文件的代码:-
private void saveFileInStorage(final Uri uri) {
new Thread(new Runnable() {
@Override
public void run() {
File file = null;
try {
String mimeType = getActivity().getContentResolver().getType(uri);
if (mimeType != null) {
InputStream inputStream = getActivity().getContentResolver().openInputStream(uri);
String fileName = getFileName(uri);
if (!fileName.equals("")) {
file = new File(
getContext().getExternalFilesDir(
Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() + "/" + fileName);
OutputStream output = new FileOutputStream(file);
try {
byte[] buffer = new byte[inputStream.available()]; // or other buffer size
int read;
while ((read = inputStream.read(buffer)) != -1) {
output.write(buffer, 0, read);
}
output.flush();
String path=file.getAbsolutePath();//use this path
} finally {
output.close();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
public String getFileName(Uri uri) {
// The query, since it only applies to a single document, will only return
// one row. There's no need to filter, sort, or select fields, since we want
// all fields for one document.
String displayName = "";
Cursor cursor = null;
if (getActivity() != null)
cursor = getActivity().getContentResolver()
.query(uri, null, null, null, null, null);
try {
// moveToFirst() returns false if the cursor has 0 rows. Very handy for
// "if there's anything to look at, look at it" conditionals.
if (cursor != null && cursor.moveToFirst()) {
// Note it's called "Display Name". This is
// provider-specific, and might not necessarily be the file name.
displayName = cursor.getString(
cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
Log.i(TAG, "Display Name: " + displayName);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
cursor.close();
}
}
return displayName;
}
您可以使用 this class.
从 uri 获取文件: File file = FileUtils.getFile(this, fileUri);
希望对您有所帮助。
从 Android N 起,我正面临这个问题。当我 select 来自左侧文件选择器抽屉中的任何应用程序(例如图库和文件管理器)的文件时,会收到 URI,我可以对文件执行我需要的操作。
此外,如果我 select 一个文件,而 Images/Videos/Audio 选项卡在文件选择器的抽屉中突出显示,我选择的任何文件都会成功返回其 URI。
Log.i("File URI to String", data.getDataString());
输出
I/File URI to String: content://com.android.providers.media.documents/document/image%3A20399
我可以使用下面的 GetPath 方法获取文件路径。
但是当我只是从选择器中 select 突出显示内部或外部存储时,会发生注释,Log.i("File URI to String", data.getDataString());
输出
I/File URI to String: content://com.android.externalstorage.documents/document/CA8B-8BD2%3ADCIM%2F100ANDRO%2FDSC_0001.JPG
我的 getPath 方法无法为其获取路径,我认为这可能与提供者有关,查看字符串形式的 uris 之间的差异。
如何获取这些 selection 的文件路径似乎不受任何提供商的支持。任何帮助表示赞赏。
我启动文件选择器的代码:
Intent intent = new Intent();
intent.setType("*/*");
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(intent, DEFAULT_IMAGE_PICK);
当我启动文件选择器意图时
2018-10-23 21:51:26.345 1572-10747/? I/ActivityManager: START u0 {act=android.intent.action.OPEN_DOCUMENT cat=[android.intent.category.OPENABLE] typ=*/* cmp=com.android.documentsui/.picker.PickActivity (has extras)} from uid 10298
2018-10-23 21:51:26.352 1572-10747/? D/ActivityTrigger: activityStartTrigger: Activity is Triggerred in full screen ApplicationInfo{e1aa80b com.android.documentsui}
2018-10-23 21:51:26.352 1572-10747/? E/ActivityTrigger: activityStartTrigger: not whiteListedcom.android.documentsui/com.android.documentsui.picker.PickActivity/26
2018-10-23 21:51:26.353 1572-10747/? D/CompatibilityInfo: mCompatibilityFlags - 0
2018-10-23 21:51:26.353 1572-10747/? D/CompatibilityInfo: applicationDensity - 320
2018-10-23 21:51:26.353 1572-10747/? D/CompatibilityInfo: applicationScale - 1.0
2018-10-23 21:51:26.353 1572-10747/? D/ActivityTrigger: activityResumeTrigger: The activity in ApplicationInfo{e1aa80b com.android.documentsui} is now in focus and seems to be in full-screen mode
2018-10-23 21:51:26.353 1572-10747/? E/ActivityTrigger: activityResumeTrigger: not whiteListedcom.android.documentsui/com.android.documentsui.picker.PickActivity/26
2018-10-23 21:51:26.353 1572-10747/? D/ActivityTrigger: ActivityTrigger activityPauseTrigger
2018-10-23 21:51:26.376 1572-10747/? D/ActivityTrigger: activityResumeTrigger: The activity in ApplicationInfo{e1aa80b com.android.documentsui} is now in focus and seems to be in full-screen mode
2018-10-23 21:51:26.376 1572-10747/? E/ActivityTrigger: activityResumeTrigger: not whiteListedcom.android.documentsui/com.android.documentsui.picker.PickActivity/26
2018-10-23 21:51:26.380 1572-10747/? D/CompatibilityInfo: mCompatibilityFlags - 0
2018-10-23 21:51:26.380 1572-10747/? D/CompatibilityInfo: applicationDensity - 320
2018-10-23 21:51:26.380 1572-10747/? D/CompatibilityInfo: applicationScale - 1.0
2018-10-23 21:51:26.409 1572-2271/? I/InputDispatcher: Focus entered window: Window{9ac7c5c u0 com.android.documentsui/com.android.documentsui.picker.PickActivity}
2018-10-23 21:51:26.459 1572-1634/? I/ActivityManager: Displayed com.android.documentsui/.picker.PickActivity: +80ms
这是我的清单 file.I 已经实现了与其他应用程序共享文件 Uris 的提供商:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
在我的onActivityResult
if ((requestCode == SELECT_GALLERY_FILE || requestCode == DEFAULT_IMAGE_PICK)
&& resultCode == Activity.RESULT_OK)
{
Log.i("File URI to String", data.getDataString());
if(data.getData()!=null){//Receiving one file
filelist.add(getPath(getActivity(),data.getData()));
filelist = ListDeduplicator(filelist);
Log.i("File Added", getPath(getActivity(),data.getData()));
//Add Path of selected file to list of files to be loaded
}
if (data.getClipData() != null) {//Receiving several
ClipData mClipData = data.getClipData();
for (int i = 0; i < mClipData.getItemCount(); i++) {
//Add Paths of selected files to list of files to be loaded
ClipData.Item item = mClipData.getItemAt(i);
filelist.add(getPath(getActivity(),item.getUri()));
Log.i("File Added", getPath(getActivity(),item.getUri()));
}
filelist = ListDeduplicator(filelist);
}
CheckAndExecute();
}
我的GetPath方法在onActivityResult中实现
public static String getPath(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
//Log.i("CHECK", "1");
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
//Log.i("CHECK", split[1]);
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}else{
return Search_Dir(new File("/storage") ,split[1]);
/*for (String string : getStorageDirectories(context)) {
String TestPath = string+"/"+split[1];
if ((new File(TestPath)).exists()) {
return TestPath;
}
}*/
}
// TODO handle non-primary volumes
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
//Log.i("CHECK", "2");
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
//Log.i("CHECK", "3");
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[] {
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
//Log.i("CHECK", "4");
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
//Log.i("CHECK", "5");
return uri.getPath();
}
return null;
}
你检查过this了吗?
A FileProvider can only generate a content URI for files in directories that you specify beforehand. To specify a directory, specify the its storage area and path in XML, using child elements of the element.
阅读以下路径,对您有帮助:
<files-path name="name" path="path" />
<cache-path name="name" path="path" />
<external-path name="name" path="path" />
<external-files-path name="name" path="path" />
<external-cache-path name="name" path="path" />
如果我没理解错的话,您正在尝试读取其他应用程序提供的数据,因此文件提供程序配置不相关。
我认为您的代码中的问题是您试图从收到的 URI 中提取文件路径。这不能可靠地工作。不保证文档 ID 包含路径或您可以查询包含路径的 _DATA
列。基本上,内容 URI 应该是一个不透明的句柄,只能通过 ContentResolver
的方法使用,例如 :
query() to get informations, like the size or a display name (the only guarantied columns)
Mark Murphy 有更详细的 explanation
你能在你的 Application.onCreate()
中试试这个代码吗?如果它对你有用,请告诉我?
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
您无法从 android 的 Intent.ACTION_GET_CONTENT 获取通用文件 URI。 URI 可能因文件提供程序的不同实现而异。如果你想要一个通用的 URI,你可以实现你自己的文件选择器或者你可以使用一些库。
如果您的要求只是将文件发送到服务器,那么您可以直接使用来自 URI 的输入流并将此输入流发送到服务器。
有关详细信息,请参阅 SO
在我的案例中,我处理场景的另一种方法是从您提到的 GetPath 方法获取路径,如果它无法提供来自 URI 的路径然后使用 URI 提供的输入流将文件保存在存储中的某个位置。下面是保存文件的代码:-
private void saveFileInStorage(final Uri uri) {
new Thread(new Runnable() {
@Override
public void run() {
File file = null;
try {
String mimeType = getActivity().getContentResolver().getType(uri);
if (mimeType != null) {
InputStream inputStream = getActivity().getContentResolver().openInputStream(uri);
String fileName = getFileName(uri);
if (!fileName.equals("")) {
file = new File(
getContext().getExternalFilesDir(
Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() + "/" + fileName);
OutputStream output = new FileOutputStream(file);
try {
byte[] buffer = new byte[inputStream.available()]; // or other buffer size
int read;
while ((read = inputStream.read(buffer)) != -1) {
output.write(buffer, 0, read);
}
output.flush();
String path=file.getAbsolutePath();//use this path
} finally {
output.close();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
public String getFileName(Uri uri) {
// The query, since it only applies to a single document, will only return
// one row. There's no need to filter, sort, or select fields, since we want
// all fields for one document.
String displayName = "";
Cursor cursor = null;
if (getActivity() != null)
cursor = getActivity().getContentResolver()
.query(uri, null, null, null, null, null);
try {
// moveToFirst() returns false if the cursor has 0 rows. Very handy for
// "if there's anything to look at, look at it" conditionals.
if (cursor != null && cursor.moveToFirst()) {
// Note it's called "Display Name". This is
// provider-specific, and might not necessarily be the file name.
displayName = cursor.getString(
cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
Log.i(TAG, "Display Name: " + displayName);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
cursor.close();
}
}
return displayName;
}
您可以使用 this class.
从 uri 获取文件: File file = FileUtils.getFile(this, fileUri);
希望对您有所帮助。