从 sdcard marshmallow 文件的 URI 获取真实路径
Get real path from URI of file in sdcard marshmallow
我想选择存在于 sdcard 而不是内部存储 中的文件并将其上传到服务器,但我无法获取其大小的路径。我已经开始使用以下代码选择文件的意图:
intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
String[] mimetypes = {"application/*"};
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimetypes);
startActivityForResult(
Intent.createChooser(intent, "Select a File to Upload"), FILE_SELECT_CODE);
为了获取文件路径,我正在使用 this answer 及其工作,除非用户 select 来自 sdcard(可移动)的任何文件。当我调试代码时发现 type 不是 primary 所以它不会进入这个条件:
if("primary".equalsIgnoreCase(type)){
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
所以我的问题是它还会是什么?即如果类型不是主要的怎么办?在那种情况下我们如何获得文件路径? 我已经搜索了很多问题和教程,其中没有其他问题。我也尝试了 this answer 的其他部分,但它不起作用,因为 System.getenv()
return null for "SECONDARY_STORAGE ”和 sdcard 用于“EXTERNAL_STORAGE”。我在尝试时遇到找不到文件异常:
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}else{
return System.getenv("EXTERNAL_STORAGE") + "/" + split[1];
}
文件的 Uri 和文档 ID 如下所示:
Uri: content://com.android.externalstorage.documents/document/0EF9-3110%3Adevice-2016-12-02-130553.png
docId: 0EF9-3110:device-2016-12-02-130553.png
有什么帮助吗??
在您的应用程序上实现 FileProvider 非常容易。首先,您需要在 AndroidManifest.xml 标签下添加一个 FileProvider 标签,如下所示:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...
<application
...
<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>
</application>
</manifest>
然后在res文件夹下的xml文件夹中创建一个provider_paths.xml文件。如果文件夹不存在,可能需要创建。
res/xml/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>
完成! FileProvider 现已声明并可以使用。
最后一步是更改 MainActivity.java
中下面的代码行
Uri photoURI = Uri.fromFile(createImageFile());
to
Uri photoURI = FileProvider.getUriForFile(MainActivity.this,
BuildConfig.APPLICATION_ID + ".provider",
createImageFile());
然后....完成!您的应用程序现在应该可以在任何 Android 版本(包括 Android Nougat)上正常运行。耶!
更改您的上传代码。某个地方你会有
FileInputStream fis = new FileInputStream(path);
改为
InputStream is = getContentResolver().openInputStream(uri);
所以直接使用uri。不需要文件路径。
在 Android 设备管理器上花费时间后,我找到了解决方案,这里是:
如果文档类型 id 不是主要的,那么我使用创建路径:
filePath = "/storage/" + type + "/" + split[1];
EDIT1: DocumentUri select contentUri 基于文件类型
这里是完整的函数:
public static String getRealPathFromURI_API19(Context context, Uri uri) {
String filePath = "";
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
} else {
if (Build.VERSION.SDK_INT > 20) {
//getExternalMediaDirs() added in API 21
File extenal[] = context.getExternalMediaDirs();
for (File f : extenal) {
filePath = f.getAbsolutePath();
if (filePath.contains(type)) {
int endIndex = filePath.indexOf("Android");
filePath = filePath.substring(0, endIndex) + split[1];
}
}
}else{
filePath = "/storage/" + type + "/" + split[1];
}
return filePath;
}
} else if (isDownloadsDocument(uri)) {
// DownloadsProvider
final String id = DocumentsContract.getDocumentId(uri);
//final Uri contentUri = ContentUris.withAppendedId(
// Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
Cursor cursor = null;
final String column = "_data";
final String[] projection = {column};
try {
cursor = context.getContentResolver().query(uri, projection, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(column);
String result = cursor.getString(index);
cursor.close();
return result;
}
} finally {
if (cursor != null)
cursor.close();
}
} else if (DocumentsContract.isDocumentUri(context, uri)) {
// MediaProvider
String wholeID = DocumentsContract.getDocumentId(uri);
// Split at colon, use second item in the array
String[] ids = wholeID.split(":");
String id;
String type;
if (ids.length > 1) {
id = ids[1];
type = ids[0];
} else {
id = ids[0];
type = ids[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[]{id};
final String column = "_data";
final String[] projection = {column};
Cursor cursor = context.getContentResolver().query(contentUri,
projection, selection, selectionArgs, null);
if (cursor != null) {
int columnIndex = cursor.getColumnIndex(column);
if (cursor.moveToFirst()) {
filePath = cursor.getString(columnIndex);
}
cursor.close();
}
return filePath;
} else {
String[] proj = {MediaStore.Audio.Media.DATA};
Cursor cursor = context.getContentResolver().query(uri, proj, null, null, null);
if (cursor != null) {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA);
if (cursor.moveToFirst())
filePath = cursor.getString(column_index);
cursor.close();
}
return filePath;
}
return null;
}
EDIT2 用于像 content://com.adobe.scan.android.documents/document/
一样处理主机检查代码 here
我想选择存在于 sdcard 而不是内部存储 中的文件并将其上传到服务器,但我无法获取其大小的路径。我已经开始使用以下代码选择文件的意图:
intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
String[] mimetypes = {"application/*"};
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimetypes);
startActivityForResult(
Intent.createChooser(intent, "Select a File to Upload"), FILE_SELECT_CODE);
为了获取文件路径,我正在使用 this answer 及其工作,除非用户 select 来自 sdcard(可移动)的任何文件。当我调试代码时发现 type 不是 primary 所以它不会进入这个条件:
if("primary".equalsIgnoreCase(type)){
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
所以我的问题是它还会是什么?即如果类型不是主要的怎么办?在那种情况下我们如何获得文件路径? 我已经搜索了很多问题和教程,其中没有其他问题。我也尝试了 this answer 的其他部分,但它不起作用,因为 System.getenv()
return null for "SECONDARY_STORAGE ”和 sdcard 用于“EXTERNAL_STORAGE”。我在尝试时遇到找不到文件异常:
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}else{
return System.getenv("EXTERNAL_STORAGE") + "/" + split[1];
}
文件的 Uri 和文档 ID 如下所示:
Uri: content://com.android.externalstorage.documents/document/0EF9-3110%3Adevice-2016-12-02-130553.png
docId: 0EF9-3110:device-2016-12-02-130553.png
有什么帮助吗??
在您的应用程序上实现 FileProvider 非常容易。首先,您需要在 AndroidManifest.xml 标签下添加一个 FileProvider 标签,如下所示: AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...
<application
...
<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>
</application>
</manifest>
然后在res文件夹下的xml文件夹中创建一个provider_paths.xml文件。如果文件夹不存在,可能需要创建。
res/xml/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>
完成! FileProvider 现已声明并可以使用。
最后一步是更改 MainActivity.java
中下面的代码行Uri photoURI = Uri.fromFile(createImageFile());
to
Uri photoURI = FileProvider.getUriForFile(MainActivity.this,
BuildConfig.APPLICATION_ID + ".provider",
createImageFile());
然后....完成!您的应用程序现在应该可以在任何 Android 版本(包括 Android Nougat)上正常运行。耶!
更改您的上传代码。某个地方你会有
FileInputStream fis = new FileInputStream(path);
改为
InputStream is = getContentResolver().openInputStream(uri);
所以直接使用uri。不需要文件路径。
在 Android 设备管理器上花费时间后,我找到了解决方案,这里是:
如果文档类型 id 不是主要的,那么我使用创建路径:
filePath = "/storage/" + type + "/" + split[1];
EDIT1: DocumentUri select contentUri 基于文件类型
这里是完整的函数:
public static String getRealPathFromURI_API19(Context context, Uri uri) {
String filePath = "";
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
} else {
if (Build.VERSION.SDK_INT > 20) {
//getExternalMediaDirs() added in API 21
File extenal[] = context.getExternalMediaDirs();
for (File f : extenal) {
filePath = f.getAbsolutePath();
if (filePath.contains(type)) {
int endIndex = filePath.indexOf("Android");
filePath = filePath.substring(0, endIndex) + split[1];
}
}
}else{
filePath = "/storage/" + type + "/" + split[1];
}
return filePath;
}
} else if (isDownloadsDocument(uri)) {
// DownloadsProvider
final String id = DocumentsContract.getDocumentId(uri);
//final Uri contentUri = ContentUris.withAppendedId(
// Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
Cursor cursor = null;
final String column = "_data";
final String[] projection = {column};
try {
cursor = context.getContentResolver().query(uri, projection, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(column);
String result = cursor.getString(index);
cursor.close();
return result;
}
} finally {
if (cursor != null)
cursor.close();
}
} else if (DocumentsContract.isDocumentUri(context, uri)) {
// MediaProvider
String wholeID = DocumentsContract.getDocumentId(uri);
// Split at colon, use second item in the array
String[] ids = wholeID.split(":");
String id;
String type;
if (ids.length > 1) {
id = ids[1];
type = ids[0];
} else {
id = ids[0];
type = ids[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[]{id};
final String column = "_data";
final String[] projection = {column};
Cursor cursor = context.getContentResolver().query(contentUri,
projection, selection, selectionArgs, null);
if (cursor != null) {
int columnIndex = cursor.getColumnIndex(column);
if (cursor.moveToFirst()) {
filePath = cursor.getString(columnIndex);
}
cursor.close();
}
return filePath;
} else {
String[] proj = {MediaStore.Audio.Media.DATA};
Cursor cursor = context.getContentResolver().query(uri, proj, null, null, null);
if (cursor != null) {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA);
if (cursor.moveToFirst())
filePath = cursor.getString(column_index);
cursor.close();
}
return filePath;
}
return null;
}
EDIT2 用于像 content://com.adobe.scan.android.documents/document/
一样处理主机检查代码 here