注:
安卓6.0是权限管理的变更,要想使用危险权限,必须代码中主动请求相关权限;到了安卓7.0中,即使你主动请求了读写权限,你不一定能够读取你想要的文件,因为7.0的文件系统加密了,使用方式和以前不同了,如果app不能即使的兼容7.0的新特性,很可能会导致应用崩溃。
安卓7.0文件系统
应用程序通常需要将其一个或多个文件提供给其他应用程序。例如,图片库可能希望为图像编辑人员提供文件,或者文件管理应用可能希望允许用户在外部存储区域之间复制和粘贴文件。发送应用程序可以共享文件的一种方式是响应来自接收应用程序的请求。
在所有情况下,从您的应用程序向另一个应用程序提供文件的唯一安全方式是向接收应用程序发送文件的内容URI并授予对该URI的临时访问权限。具有临时URI访问权限的内容URI是安全的,因为它们仅适用于接收URI的应用程序,并且它们会自动过期。Android FileProvider组件提供了 getUriForFile()生成文件内容URI的方法。
7.0如何正确的使用文件系统呢?
正确的使用方法,必须遵从以下这几个步骤:
第一:在manifest.xml文件中加入以下的<Provider>
标签
<application
...>
<!-- 定义FileProvider -->
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.myapp.fileprovider" <!-- 这个是唯一权限符,相当于网络地址中的ip一样-->
android:grantUriPermissions="true"
android:exported="false"> <!-- 是否提供给外部使用-->
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
...
</application>
第二:提供给外部使用的可访问文件目录
也是说,想要使用文件系统的中的文件,还必须要提供哪些路径上面的文件可以访问,那么其余的都不可以访问。
在res文件夹下新建xml文件夹,新建一个resource的xml文件,将如下内容复制到文件中。
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!--
1、name对应的属性值,开发者可以自由定义;
2、path对应的属性值,当前external-path标签下的相对路径
比如:/storage/emulated/0/s-release.apk
sdcard路径:/storage/emulated/0(WriteToReadActivity.java:176)
at cn.teachcourse.nougat.WriteToReadActivity.onClick(WriteToReadActivity.java:97)
at android.view.View.performClick(View.java:5610)
at android.view.View$PerformClick.run(View.java:22265)
相对路径:/
-->
<!--1、对应内部内存卡根目录:Context.getFileDir()-->
<files-path
name="int_root"
path="/" />
<!--2、对应应用默认缓存根目录:Context.getCacheDir()-->
<cache-path
name="app_cache"
path="/" />
<!--3、对应外部内存卡根目录:Environment.getExternalStorageDirectory()-->
<external-path
name="ext_root"
path="/" />
<!--4、对应外部内存卡根目录下的APP公共目录:Context.getExternalFileDir(String)-->
<external-files-path
name="ext_pub"
path="/" />
<!--5、对应外部内存卡根目录下的APP缓存目录:Context.getExternalCacheDir()-->
<external-cache-path
name="ext_cache"
path="/" />
</paths>
注释里面已经写的很清楚了,每一个标签对应一个文件系统的目录,比如说<files-path>
标签,就是代表系统内部根目录的意思,和Context.getFileDir()
返回的路径是等同的,接着看下<files-path>
标签内部的两个属性,name="int_root" path="/"
,name的意思就是7.0文件系统中provider所识别的文件路径,而path是name所对应的真实的文件路径,那么这个标签:
<files-path name="int_root" path="/images" />
的意思就是:
provider所接受的uri路径是:content://com.example.myapp.fileprovider/int_root
真实的绝对路径是:Content.getFilesDir() + "/images/" (比如:/data/app/com.example.myapp/images/)
上面还可以配置很多路径,一般为了省事,都是直接根目录就行了,不用麻烦让应用只访问仅限的几个文件夹。
配置完成之后,就是使用了
1.调取摄像头,将拍摄的图片保存起来,然后回调路径使用图片
Intent intentFromCapture = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (hasSdcard()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//7.0及以上
Uri uriForFile = FileProvider.getUriForFile(getActivity(), "com.renwohua.conch.fileprovider", mCameraFile);
intentFromCapture.putExtra(MediaStore.EXTRA_OUTPUT, uriForFile);
intentFromCapture.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intentFromCapture.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
} else {
intentFromCapture.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(mCameraFile));
}
}
startActivityForResult(intentFromCapture, CAMERA_REQUEST_CODE);
然后就是在activity中接收从摄像机拍摄出来的图片的路径信息,如下代码:
case CAMERA_REQUEST_CODE: //照相后返回
if (hasSdcard()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri inputUri = FileProvider.getUriForFile(getActivity(), "com.renwohua.conch.fileprovider", mCameraFile);//通过FileProvider创建一个content类型的Uri
startPhotoZoom(inputUri);//设置输入类型
} else {
Uri inputUri = Uri.fromFile(mCameraFile);
startPhotoZoom(inputUri);
}
} else {
showToast("未找到存储卡,无法存储照片!");
}
break;
在版本高于N的情况下使用provider来进行文件的读写操作,低版本直接可以使用路径进行uri转化。
当然还有其它的应用,比如说从相册取图片,回调结果。
再比如从外部传递过来一个真实路径,需要将该路径的图片读取到imageview中,在安卓7.0系统中,就需要把真实路径转化成provider识别的content类型的uri,然后再读取bitmap,代码如下:
File newFile = new File(path);
Uri inputUri = FileProvider.getUriForFile( context, "com.example.waImageClip.activity", newFile);
InputStream input = context.getContentResolver().openInputStream(inputUri);
Bitmap bitmap = BitmapFactory.decodeStream(input);
只需要保证配置的有权限访问的文件路径包含path路径,如果不包含那么会报找不到failed to find config root that XXX路径。
在调用系统相册或者相机的时候,通常回调结果是content路径的格式,并不是file文件路径格式,那么就需要一个格式转化工具类将其转化成路径,代码如下:
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static String getpath(Context context, Uri uri){
if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
if (DocumentsContract.isDocumentUri(context, uri)) {
if (isExternalStorageDocument(uri)) {
// ExternalStorageProvider
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
String path = Environment.getExternalStorageDirectory() + "/" + split[1];
return path;
}
} 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));
String path = getDataColumn(context, contentUri, null, null);
return path;
} else if (isMediaDocument(uri)) {
// MediaProvider
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]};
String path = getDataColumn(context, contentUri, selection, selectionArgs);
return path;
}
}
}
return null;
}
private 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 column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
private static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
private static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
private static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
使用的方法大致如此。有补充的欢迎留言。