Android将"content://"类型的uri转为文件路径

本文介绍了一个用于从Android应用中获取文件真实路径的工具类,包括如何处理不同类型的URI,并提供了将内容URI转换为实际路径的方法。在获取路径后,演示了如何将其转换为“file:///”类型的URI。适用于Android混合开发中的文件选择和上传功能。

uri有多种类型的的scheme,例如content、file等,有些时候我们需要文件存储的真实路径例如Android混合开发中使用<input type="file/>与openFileChooser完成文件选择上传的功能,本文提供了一个使用的工具类,可以根据"content://"类型或者其他类型的uri获取该文件的真实路径。

<span style="font-size:14px;">public class ContentUriUtil {
	/**
	 * Get a file path from a Uri. This will get the the path for Storage Access
	 * Framework Documents, as well as the _data field for the MediaStore and
	 * other file-based ContentProviders.
	 *
	 * @param context The context.
	 * @param uri The Uri to query.
	 * @author paulburke
	 */
	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)) {
	            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];
	            }

	            // TODO handle non-primary volumes
	        }
	        // DownloadsProvider
	        else if (isDownloadsDocument(uri)) {

	            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)) {
	            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())) {

	        // Return the remote address
	        if (isGooglePhotosUri(uri))
	            return uri.getLastPathSegment();

	        return getDataColumn(context, uri, null, null);
	    }
	    // File
	    else if ("file".equalsIgnoreCase(uri.getScheme())) {
	        return uri.getPath();
	    }

	    return null;
	}
	/**
	**
	 * Get the value of the data column for this Uri. This is useful for
	 * MediaStore Uris, and other file-based ContentProviders.
	 *
	 * @param context The context.
	 * @param uri The Uri to query.
	 * @param selection (Optional) Filter used in the query.
	 * @param selectionArgs (Optional) Selection arguments used in the query.
	 * @return The value of the _data column, which is typically a file path.
	 */
	public static String getDataColumn(Context context, Uri uri, String selection,
	        String[] selectionArgs) {

	    Cursor cursor = null;
	    final String column = "_data";
	    final String[] projection = {
	            column
	    };

	    try {
	        cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
	                null);
	        if (cursor != null && cursor.moveToFirst()) {
	            final int index = cursor.getColumnIndexOrThrow(column);
	            return cursor.getString(index);
	        }
	    } finally {
	        if (cursor != null)
	            cursor.close();
	    }
	    return null;
	}


	/**
	 * @param uri The Uri to check.
	 * @return Whether the Uri authority is ExternalStorageProvider.
	 */
	public static boolean isExternalStorageDocument(Uri uri) {
	    return "com.android.externalstorage.documents".equals(uri.getAuthority());
	}

	/**
	 * @param uri The Uri to check.
	 * @return Whether the Uri authority is DownloadsProvider.
	 */
	public static boolean isDownloadsDocument(Uri uri) {
	    return "com.android.providers.downloads.documents".equals(uri.getAuthority());
	}

	/**
	 * @param uri The Uri to check.
	 * @return Whether the Uri authority is MediaProvider.
	 */
	public static boolean isMediaDocument(Uri uri) {
	    return "com.android.providers.media.documents".equals(uri.getAuthority());
	}

	/**
	 * @param uri The Uri to check.
	 * @return Whether the Uri authority is Google Photos.
	 */
	public static boolean isGooglePhotosUri(Uri uri) {
	    return "com.google.android.apps.photos.content".equals(uri.getAuthority());
	}
}</span>
代码中的注释比较详细,在此不多做解释,如有什么疑问可以留言。

在使用上面给出的工具类获取到路径后,还需要在做一步处理使其转为“file:///”类型的uri

Uri.fromFile(new File(ContentUriUtil.getPath(mActivity, resultDataUri)));

参考资源:http://stackoverflow.com/questions/19985286/convert-content-uri-to-actual-path-in-android-4-4



<think>我们正在处理一个SecurityException,具体错误信息是: "No persistable permission grants found for UID 10011 and Uri content://com.android.providers.downloads.documents/..." 这个错误发生在尝试访问通过`content://com.android.providers.downloads.documents/`提供的URI时,系统检查发现没有为调用者UID(10011)持久化授权(persistable permission grants)。 在Android中,当我们使用`ACTION_OPEN_DOCUMENT`或`ACTION_GET_CONTENT`等Intent选择文件后,会返回一个内容URIcontent URI)。为了在后续访问中(例如,应用重启后)仍然能够访问该文件,我们需要获取持久化的URI权限。否则,当应用尝试再次访问该URI时,可能会遇到权限错误。 解决方案的核心是:在获取到URI后,立即通过`ContentResolver.takePersistableUriPermission()`方法获取持久化的URI访问权限。 具体步骤如下: 1. 在启动`ACTION_OPEN_DOCUMENT`或`ACTION_GET_CONTENT`时,添加`Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION`标志,以表明我们想要获取持久化权限(注意:该标志仅适用于API 19+)。 2. 在`onActivityResult`中,当我们成功获取到文件URI后,调用`takePersistableUriPermission`来获取持久化权限。 但是,请注意,并非所有内容提供者都支持持久化权限。因此,在调用`takePersistableUriPermission`之前,最好检查一下该URI是否支持持久化权限(通过`ContentResolver.getPersistedUriPermissions()`可以查看已持久化的权限,但这并不是检查是否支持,而是检查已有权限)。实际上,我们可以直接尝试获取,如果失败则捕获异常。 另外,错误信息中提到的UID 10011是应用的UID,URI是`content://com.android.providers.downloads.documents/...`,这是通过系统的下载存储提供的文件。 下面是一个示例代码,展示如何正确获取持久化权限: ```java private static final int REQUEST_CODE_OPEN_DOC = 42; private void openDocument() { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("*/*"); // 添加标志以请求持久化权限 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); startActivityForResult(intent, REQUEST_CODE_OPEN_DOC); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_OPEN_DOC && resultCode == RESULT_OK) { if (data != null) { Uri uri = data.getData(); if (uri != null) { // 尝试获取持久化权限 try { getContentResolver().takePersistableUriPermission( uri, Intent.FLAG_GRANT_READ_URI_PERMISSION ); } catch (SecurityException e) { // 处理异常:该URI可能不支持持久化权限 Log.e("TAG", "无法获取持久化权限", e); } // 现在可以安全地使用该URI,并保存它以便以后使用 // 例如,将URI保存到SharedPreferences中 saveUriToPrefs(uri); } } } } private void saveUriToPrefs(Uri uri) { // 将URI的字符串形式保存到SharedPreferences SharedPreferences prefs = getSharedPreferences("my_prefs", MODE_PRIVATE); prefs.edit().putString("file_uri", uri.toString()).apply(); } // 当需要再次使用该文件时 private void openFileFromSavedUri() { SharedPreferences prefs = getSharedPreferences("my_prefs", MODE_PRIVATE); String uriString = prefs.getString("file_uri", null); if (uriString != null) { Uri uri = Uri.parse(uriString); // 在访问之前,检查是否还有权限(或者直接尝试访问并捕获异常) try { ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r"); // 使用文件描述符读取文件 // ... 然后关闭 pfd.close(); } catch (Exception e) { Log.e("TAG", "文件访问失败", e); } } } ``` 注意: - 在保存URI之前,我们获取了持久化权限,这样即使应用重启,我们仍然可以访问该文件。 - 如果内容提供者不支持持久化权限,那么`takePersistableUriPermission`会抛出`SecurityException`。在这种情况下,我们无法持久化访问,必须每次重新获取文件(即重新启动文件选择器)。 - 对于下载存储提供者(`com.android.providers.downloads.documents`),通常支持持久化权限。 另外,在Android 10(API 29)及以上版本,由于Scoped Storage,应用访问外部文件必须使用内容URI和存储访问框架(SAF),因此正确处理URI权限尤为重要。 如果问题仍然存在,请检查: 1. 是否在Intent中正确添加了`FLAG_GRANT_PERSISTABLE_URI_PERMISSION`标志。 2. 是否在`onActivityResult`中立即调用了`takePersistableUriPermission`。 3. 是否保存了URI,并在后续使用该URI(而不是文件路径)。 通过以上步骤,应该可以解决SecurityException问题。 引用说明: - 引用[1]中提到的错误与权限问题相关,我们的解决方案正是针对此类权限问题。 - 引用[2]中关于抽象类的描述,虽然与权限问题不直接相关,但我们在代码中使用的`ContentResolver`和`Intent`等就是Android框架提供的抽象,具体实现由系统提供。 如果问题仍未解决,可能需要检查系统权限管理是否存在异常,或者考虑使用其他方式(如`ACTION_GET_CONTENT`)获取文件。 §§相关问题§§ 1. 如何检查一个URI是否支持持久化权限? 2. 在Android中,除了使用`takePersistableUriPermission`,还有哪些方法可以长期访问文件? 3. 如何处理内容提供者不支持持久化权限的情况?</think>### 解决Android SecurityException: No persistable permission grants问题 当Android应用访问文件时出现以下错误: ```java SecurityException: No persistable permission grants found for UID 10011 and Uri content://com.android.providers.downloads.documents/... ``` 这表示应用(UID 10011)尝试访问通过文档提供者(`downloads.documents`)获取的URI时,系统找不到持久化的访问权限授权。以下是系统化的解决方案: --- #### 1. **确保正确获取持久化权限** 在通过`ACTION_OPEN_DOCUMENT`获取URI后,必须显式获取持久化权限: ```java // 在onActivityResult中处理返回的URI @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) { Uri uri = data.getData(); // 关键步骤:获取持久化权限 try { getContentResolver().takePersistableUriPermission( uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION // 按需添加写权限 ); } catch (SecurityException e) { // 处理权限获取失败 } // 保存URI以便后续使用 saveUriForFutureUse(uri); } } ``` - **`takePersistableUriPermission`** 是核心方法,它将临时权限转为持久化权限 - 必须添加`FLAG_GRANT_PERSISTABLE_URI_PERMISSION`标志到启动Intent: ```java Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); // 关键标志 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); ``` --- #### 2. **正确处理URI存储和恢复** 持久化权限与应用生命周期绑定,需正确存储URI: ```java // 存储URI到SharedPreferences void saveUriForFutureUse(Uri uri) { getSharedPreferences("uri_prefs", MODE_PRIVATE) .edit() .putString("saved_uri", uri.toString()) .apply(); } // 使用时恢复URI并验证权限 void useSavedUri() { String uriString = getSharedPreferences("uri_prefs", MODE_PRIVATE) .getString("saved_uri", null); if (uriString != null) { Uri uri = Uri.parse(uriString); // 检查是否仍有权限 List<UriPermission> permissions = getContentResolver() .getPersistedUriPermissions(); for (UriPermission perm : permissions) { if (perm.getUri().equals(uri) && perm.isReadPermission()) { // 安全访问文件 try (InputStream is = getContentResolver().openInputStream(uri)) { // 处理文件 } return; } } // 无权限时重新请求 requestFilePermission(); } } ``` --- #### 3. **处理系统级权限问题** 对于系统应用(如`com.android.certinstaller`),可能需要额外处理: 1. **在AndroidManifest.xml声明权限** ```xml <uses-permission android:name="android.permission.INSTALL_CERTIFICATE" /> <uses-permission android:name="android.permission.MANAGE_DOCUMENTS" /> ``` 2. **检查系统权限配置** - 确认在`/etc/permissions`目录中存在相关权限配置 - 验证应用签名是否匹配系统签名 3. **处理ContentProvider权限** 如果是自定义ContentProvider,需在`AndroidManifest.xml`声明权限: ```xml <provider android:name=".MyProvider" android:authorities="com.example.provider" android:grantUriPermissions="true" android:permission="android.permission.MANAGE_DOCUMENTS" /> ``` --- #### 4. **兼容性处理** 针对不同Android版本的特殊处理: ```java if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // 仅Android 4.4+支持持久化URI权限 intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Android 8.0+需额外检查 if (!getPackageManager().isPermissionRevokedByPolicy( "android.permission.READ_EXTERNAL_STORAGE", getPackageName())) { // 执行安全操作 } } ``` --- #### 5. **错误处理最佳实践** ```java try { // 文件访问操作 } catch (SecurityException e) { if (e.getMessage().contains("persistable permission grants")) { // 1. 清除无效的URI缓存 clearInvalidUri(); // 2. 重新请求用户选择文件 openFilePicker(); // 3. 记录错误日志 Log.w(TAG, "Persistable permission missing, re-requesting file", e); } } ``` **关键点总结**: 1. 使用`takePersistableUriPermission()`转换临时权限为持久权限 2. 正确存储和恢复URI时验证权限状态 3. 系统应用需检查签名权限配置 4. 针对不同Android版本进行兼容处理 5. 实现健壮的错误处理逻辑 通过以上步骤,可解决`No persistable permission grants`导致的SecurityException问题,确保文件访问的稳定性[^1][^2]。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值