Android7.0 (N) 开始,将严格执行 StrictMode 模式,对文件系统将有更高的安全管理。从 Android N 开始,将不允许在 App 间,使用 file:// 的方式,传递一个 File ,否则会抛出 FileUriExposedException的错误,会直接引发 Crash。而是使用FileProvider,通过 content://的模式替换掉 file://。
以下就以具体的代码形式说明文件的选择与打开~~~
打开文件:
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
try {
startActivityForResult(Intent.createChooser(intent, "Select a File"), CODE_REQUEST_IMPORT_SMS);
} catch (android.content.ActivityNotFoundException ex) {
Toast.makeText(MainActivity.this, "Please install a File Manager.", Toast.LENGTH_SHORT).show();
return;
}
选择文件后在onActivityResult()中得到文件路径:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CODE_REQUEST_IMPORT_SMS) {
if(resultCode == Activity.RESULT_OK){
Uri uri = data.getData();
//content://com.speedsoftware.rootexplorer.content/sdcard/customfile.xml
String path = ContentUriUtil.getPath(MainActivity.this,uri);
}
}
}
其中ContentUriUtil中的代码如下:
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());
}
}
此时若要打开刚才选中的文件需要针对android 7.0以前和以后的版本做不同的处理,具体如下:
1.针对Android 7.0及以上的系统需要先在Manifest.xml中配置:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"//这里的authorities需要保持唯一,且与后面代码使用时需要保持一致
android:exported="false"//是否对外开放
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"//固定值
android:resource="@xml/provider_paths">//对应的xml资源文件,里面定义需要访问的文件路径,xml文件的文件名称可以自定义
</meta-data>
</provider>
2.然后在res/xml文件夹下创建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>
xml文件中的各个标签的含义:
root-path 对应DEVICE_ROOT,也就是File DEVICE_ROOT = new File("/"),即根目录,一般不需要配置
files-path对应 context.getFileDir() 获取到的目录
cache-path对应 context.getCacheDir() 获取到的目录
external-path对应 context.getExternalStorageDirectory() 指向的目录
external-files-path对应 context.getExternalFilesDirs() 获取到的目录
external-cache-path对应 context.getExternalCacheDirs() 获取到的目录
2.最后写代码打开文件(android 7.0之前的版本打开文件时不需要执行上面第一步和第二步,代码如下,这里不在赘述):
public static void openFile(Context context,String filePath){
if(context==null){
return;
}
if(TextUtils.isEmpty(filePath)){
return;
}
File file = new File(filePath);
if(file==null || !file.exists()){
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = null;
String extension = MimeTypeMap.getFileExtensionFromUrl(filePath);
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
//打开文件需要做版本适配 android 7.0需要用provider打开文件
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
//andorid 7.0及之后的版本需要通过FileProvider获取uri
uri = FileProvider.getUriForFile(context, context.getPackageName()+".provider", file);
intent.setDataAndType(uri,mimeType);
//intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//和grantUriPermission()选一个应该就可以了
grantUriPermission(context,uri,intent);
}else{
//andorid 7.0之前的版本直接获取uri即可
uri = Uri.fromFile(file);
intent.setDataAndType(uri,mimeType);
}
if(!(context instanceof Activity)){
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent);
}
//获得权限
private static void grantUriPermission (Context context, Uri fileUri, Intent intent) {
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
context.grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
到这里为止,打开文件的代码都已经完成~