Android开发之深入理解Android 7.0系统权限更改相关文档

古之立大事者,不惟有超世之才,亦必有坚忍不拔之志。这篇文章主要讲述Android开发之深入理解Android 7.0系统权限更改相关文档相关的知识,希望能为你提供帮助。
摘要: android 6.0之后的版本增加了运行时权限, 应用程序在执行每个需要系统权限的功能时, 需要添加权限请求代码( 默认权限禁止) , 否则应用程序无法响应; Android 7.0在Android 6.0的基础上, 对系统权限进一步更改, 这次的权限更改包括三个方面:

  1. APP应用程序的私有文件不再向使用者放宽
  2. Intent组件传递file://URI的方式可能给接收器留下无法访问的路径, 触发FileUriExposedException异常, 推荐使用FileProvider
  3. DownloadManager不再按文件名分享私人存储的文件。旧版应用在访问COLUMN_LOCAL_FILENAME时可能出现无法访问的路径。面向 Android 7.0 或更高版本的应用在尝试访问 COLUMN_LOCAL_FILENAME 时会触发 SecurityException
简单的三句话, 无法让TeachCourse真正理解Android 7.0系统权限更改的含义, 如果不按照文档的方式去做, API 24开发的应用程序是否就用不了?
Android开发之深入理解Android 7.0系统权限更改相关文档

文章图片

一、深入理解FileProvider FileProvider属于Android 7.0新增的一个类, 该类位于v4包下, 详情可见android.support.v4.content.FileProvider, 使用方法类似与ContentProvider, 简单概括为三个步骤, 这里先以调用系统相机拍照并保存sdcard公共目录为例, 演示使用过程:
  • 在资源文件夹res/xml下新建file_provider.xml文件, 文件声明权限请求的路径, 代码如下:
< ?xml version= " 1.0" encoding= " utf-8" ?> < paths xmlns:android= " http://schemas.android.com/apk/res/android" > < !--3、对应外部内存卡根目录: Environment.getExternalStorageDirectory()--> < external-path name= " ext_root" path= " /" /> < /paths>

  • AndroidManifest.xml添加组件provider相关信息, 类似组件activity, 指定resource属性引用上一步创建的xml文件( 后面会详细介绍各个属性的用法) , 代码如下:
< !-- 定义FileProvider --> < provider android:name= " android.support.v4.content.FileProvider" android:authorities= " @ string/install_apk_path" android:exported= " false" android:grantUriPermissions= " true" > < meta-data android:name= " android.support.FILE_PROVIDER_PATHS" android:resource= " @ xml/file_provider" /> < /provider>

  • 最后一步, java代码申请权限, 使用新增的方法getUriForFile()grantUriPermission(), 代码如下( 后面会详细介绍方法对应参数的使用) :
if (Build.VERSION.SDK_INT > 23) { /**Android 7.0以上的方式**/ Uri contentUri = getUriForFile(this, getString(R.string.install_apk_path), file); grantUriPermission(getPackageName(), contentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION); intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri); }

  • 修改build.gradle文件compileSdkVersion大于或等于24, targetSdkVersion等于24, 使用Android 7.0模拟器运行Demo, 效果图:
Android开发之深入理解Android 7.0系统权限更改相关文档

文章图片

那么, 我们已经了解Android 7.0系统权限申请的步骤, 接下来说明每一个步骤需要注意的事项、相关方法参数的说明、属性的含义以及可以的申请权限目录( 最后下载相关Demo) 。
1.1 定义一个FileProvider
直接使用FileProvider本身或者它的子类, 需要在AndroidManifest.xml文件中声明组件的相关属性, 包括:
  • android:name, 对应属性值: android.support.v4.content.FileProvider或者子类完整路径
  • android:authorities, 对应属性值是一个常量, 通常定义的方式packagename.fileprovider, 例如: cn.teachcourse.fileprovider
  • android:exported, 对应属性值是一个boolean变量, 设置为false
  • android:grantUriPermissions, 对应属性值也是一个boolean变量, 设置为true, 允许获得文件临时的访问权限
< manifest> ... < application> ... < provider android:name= " android.support.v4.content.FileProvider" android:authorities= " com.mydomain.fileprovider" android:exported= " false" android:grantUriPermissions= " true" > ... < /provider> ... < /application> < /manifest>

想要关联res/xml文件夹下创建的file_provider.xml文件, 需要在< provider> 标签内, 添加< meta-data> 子标签, 设置< meta-data> 标签的属性值, 包括:
  • android:name, 对应属性值是一个固定的系统常量android.support.FILE_PROVIDER_PATHS
  • android:resource, 对应属性值指向我们的xml文件@ xml/file_provider
< provider android:name= " android.support.v4.content.FileProvider" android:authorities= " com.mydomain.fileprovider" android:exported= " false" android:grantUriPermissions= " true" > < meta-data android:name= " android.support.FILE_PROVIDER_PATHS" android:resource= " @ xml/file_provider" /> < /provider>

1.2 指定授予临时访问权限的文件目录
上一步说明了怎么定义一个FileProvider, 这一步主要说明怎么定义一个@ xml/file_provider文件。Android Studio或Eclipse开发工具创建Android项目的时候默认不会创建res/xml文件夹, 需要开发者手动创建, 点击res文件夹新建目录, 命名xml, 如下图:
Android开发之深入理解Android 7.0系统权限更改相关文档

文章图片

然后, 在xml文件夹下新建一个xml文件, 文件命名file_provider.xml, 指定根标签为paths, 如下图:
Android开发之深入理解Android 7.0系统权限更改相关文档

文章图片

在xml文件中指定文件存储的区块和区块的相对路径, 在< paths> 根标签中添加< files-path> 子标签( 稍后详细列出所有子标签) , 设置子标签的属性值, 包括:
- name, 是一个虚设的文件名( 可以自由命名) , 对外可见路径的一部分, 隐藏真实文件目录
- path, 是一个相对目录, 相对于当前的子标签< files-path> 根目录
- < files-path> , 表示内部内存卡根目录, 对应根目录等价于Context.getFilesDir(), 查看完整路径:
/data/user/0/cn.teachcourse.demos/files
- 代码如下:
< paths xmlns:android= " http://schemas.android.com/apk/res/android" > < files-path name= " my_images" path= " images/" /> ... < /paths>

< paths> 根标签下可以添加的子标签也是有限的, 参考官网的开发文档, 除了上述的提到的< files-path> 这个子标签外, 还包括下面几个:
1. < cache-path> , 表示应用默认缓存根目录, 对应根目录等价于getCacheDir(), 查看完整路径: /data/user/0/cn.teachcourse.demos/cache
  1. < external-path> , 表示外部内存卡根目录, 对应根目录等价于
    Environment.getExternalStorageDirectory()
    查看完整路径: /storage/emulated/0
  2. < external-files-path> , 表示外部内存卡根目录下的APP公共目录, 对应根目录等价于
    Context#getExternalFilesDir(String) Context.getExternalFilesDir(null)
    查看完整路径:
    /storage/emulated/0/Android/data/cn.teachcourse.demos/files/Download
  3. < external-cache-path> , 表示外部内存卡根目录下的APP缓存目录, 对应根目录等价于Context.getExternalCacheDir(), 查看完整路径:
    /storage/emulated/0/Android/data/cn.teachcourse.demos/cache
最终, 在file_provider.xml文件中, 添加上述5种类型的临时访问权限的文件目录, 代码如下:
< ?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/92Recycle-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= " pictures/" /> < !--4、对应外部内存卡根目录下的APP公共目录: Context.getExternalFileDir(String)--> < external-files-path name= " ext_pub" path= " /" /> < !--5、对应外部内存卡根目录下的APP缓存目录: Context.getExternalCacheDir()--> < external-cache-path name= " ext_cache" path= " /" /> < /paths>

1.3 生成指定文件的Content URI
Content URI方便与另一个APP应用程序共享同一个文件, 共享的方式通过ContentResolver.openFileDescriptor获得一个ParcelFileDescriptor对象, 读取文件内容。那么, 如何生成一条完整的Content URI呢? TeachCourse总结后, 概括为三个步骤, 第一步: 明确上述5种类型中的哪一种, 第二步: 明确指定文件的完整路径( 包括目录、文件名) , 第三步: 调用getUriForFile()方法生成URI
File imagePath = new File(Environment.getExternalStorageDirectory(), " download" ); File newFile = new File(imagePath, " default_image.jpg" ); Uri contentUri = getUriForFile(getContext(), " cn.teachcourse.fileprovider" , newFile);

1.4 授予Content URI临时访问权限
上一步获得的Content URI, 并没有获得指定文件的读写权限, 想要获得文件的读写权限需要调用Context.grantUriPermission(package, Uri, mode_flags)方法, 该方法向指定包名的应用程序申请获得读取或者写入文件的权限, 参数说明如下:
  • package, 指定应用程序的包名, Android Studio真正的包名指build.gradle声明的applicationId属性值; getPackageName()AndroidManifest.xml文件声明的package属性值, 如果两者不一致, 就不能提供getPackageName()获取包名, 否则报错!
  • Uri, 指定请求授予临时权限的URI, 例如: contentUri
  • mode_flags, 指定授予临时权限的类型, 选择其中一个常量或两个: Intent.FLAG_GRANT_READ_URI_PERMISSIONIntent.FLAG_GRANT_WRITE_URI_PERMISSION
授予文件的临时读取或写入权限, 如果不再需要了, TeachCourse该如何撤销授予呢? 撤销权限有两种方式: 第一种: 通过调用revokeUriPermission()撤销, 第二种: 重启系统后自动撤销
1.5 对外提供可访问的Content URI
有多种方式可以向客户端APP提供可访问文件的Content URI, 其中一种常用的方式是通过发送Intent给需要启动的Activity, 在重写的startActivityResult()方法中获取授予临时权限的Content URI或向用户提供可访问的接口来获取文件, 后面的这种方式获取文件后转换成Content URI, 以文章开头拍照的功能为例, TeachCourse想要在sdcard的公共目录pictures/查看已保存的照片, 实现过程:
  • 请求授予访问公共目录的权限, 代码如下:
if (Build.VERSION.SDK_INT > 23) { /**Android 7.0以上的方式**/ mStorageManager = this.getSystemService(StorageManager.class); StorageVolume storageVolume = mStorageManager.getPrimaryStorageVolume(); Intent intent = storageVolume.createAccessIntent(Environment.DIRECTORY_PICTURES); startActivityForResult(intent, REQUEST_CODE_GRAINT_URI); }

  • 在重写的startActivityResult()方法中获取授予临时权限的Content URI, 代码如下:
@ Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case REQUEST_CODE_GRAINT_URI: updateDirectoryEntries(data.getData()); Log.d(TAG, " onActivityResult:Uri= " + data.getData()); break; } }

  • 查询Environment.DIRECTORY_PICTURES目录, 返回的Content URI包含的文件和文件类型相关信息, 代码如下:
private static final String[] DIRECTORY_SELECTION = new String[]{ DocumentsContract.Document.COLUMN_DISPLAY_NAME, DocumentsContract.Document.COLUMN_MIME_TYPE, DocumentsContract.Document.COLUMN_DOCUMENT_ID, }; @ TargetApi(Build.VERSION_CODES.LOLLIPOP) private void updateDirectoryEntries(Uri uri) { ContentResolver contentResolver = this.getContentResolver(); Uri docUri = DocumentsContract.buildDocumentUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri)); Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri)); try (Cursor docCursor = contentResolver .query(docUri, DIRECTORY_SELECTION, null, null, null)) { while (docCursor != null & & docCursor.moveToNext()) { mPath_tv.setText(docCursor.getString(docCursor.getColumnIndex( DocumentsContract.Document.COLUMN_DISPLAY_NAME))); } }try (Cursor childCursor = contentResolver .query(childrenUri, DIRECTORY_SELECTION, null, null, null)) { while (childCursor != null & & childCursor.moveToNext()) { String fileName = childCursor.getString(childCursor.getColumnIndex( DocumentsContract.Document.COLUMN_DISPLAY_NAME)); String mimeType = childCursor.getString(childCursor.getColumnIndex( DocumentsContract.Document.COLUMN_MIME_TYPE)); Log.e(TAG, " updateDirectoryEntries: " + fileName+ " \\n" + mimeType); }} }

运行Demo, 控制台打印效果图:
Android开发之深入理解Android 7.0系统权限更改相关文档

文章图片

更多说明, 可以参考Google提供的例子
二、深入理解DownloadManager 同样, 为了方便理解DownloadManager的用法, 首先以一个简单例子开始: 从指定的url下载资源, 然后显示下载资源的相关信息, 运行Demo的效果图:
Android开发之深入理解Android 7.0系统权限更改相关文档

文章图片

Android 7.0系统权限更改的第三点, 简单的说: 通过访问COLUMN_LOCAL_FILENAME, 在Android 7.0系统上可能无法获取Demo效果图fileName对应的文件路径, 这时候可能触发异常SecurityException, 打印的log信息, 如下:
Caused by: java.lang.SecurityException: COLUMN_LOCAL_FILENAME is deprecated; use ContentResolver.openFileDescriptor() instead at android.app.DownloadManager$CursorTranslator.getString(DownloadManager.java:1499) at cn.teachcourse.download.DownloadManagerActivity.query(DownloadManagerActivity.java:244) at cn.teachcourse.download.DownloadManagerActivity.access$100(DownloadManagerActivity.java:34) at cn.teachcourse.download.DownloadManagerActivity$1.onReceive(DownloadManagerActivity.java:186) at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:1122) at android.os.Handler.handleCallback(Handler.java:751) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6077) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)

2.1 关于DownloadManager
DownloadManager是一个用于处理长时间HTTP请求的系统服务, 客户端请求的URI可能是将要下载的指定的文件, 处于后台的下载管理器将控制着下载的任务, 并监测下载的状态, 在下载失败或连接改变以及系统重启后尝试重新下载。
  • 如何初始化DownloadManager实例? 首先调用getSystemService(String)方法, 传入DOWNLOAD_SERVICE常量, 来初始化DownloadManager实例, 代码如下:
mDownloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);

  • 如何配置请求参数? 首先需要使用到内部类DownloadManager.Request, 查看源码学习该类的各个方法的使用, TeachCourse简单总结: 该类主要用于配置一条新下载任务相关内容, 这些内容包括下载任务的保存路径, 下载任务所处的网络状态( WiFi或流量状态) 和下载任务通知栏显示样式等等, 代码如下:
/** * 设置请求下载的数据 */ private void initData() { //Request内部类配置新下载任务相关内容, 比如: 保存路径, WiFi或流量状态, 下载通知栏样式 request = new DownloadManager.Request(Uri.parse(mUrl + mFileName)); request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, mFileName); request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE); request.setTitle(" 正在下载应用程序" ); request.setDescription(" 92回收, 就爱回收APP" ); request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE); }

  • 如何开启下载任务? 下载任务参数配置完成后, 就可以开启后台服务下载, 同一个DownloadManager实例, 可以开启多个下载任务, 需要上一步中配置多条URI, 每个下载任务分配唯一的id, 代码如下:
/** * 下载任务的唯一标识ID, 用于查询下载文件的相关信息 */ private void start() { mDownloadUniqueId = mDownloadManager.enqueue(request); mDownloadManager_btn.setText(" 正在下载。。。" ); mDownloadManager_btn.setClickable(false); }

  • DownloadManager通过两种状态的广播, 第一种: 任务下载完成后发送, 广播拦截器过滤action是DownloadManager.ACTION_DOWNLOAD_COMPLETE( 关于广播的知识, 不懂的可以参考TeachCourse博客另外的几篇文章) ; 第二种: 点击通知栏进度条后发送, 广播拦截器过滤action是DownloadManager.ACTION_NOTIFICATION_CLICKED, 代码如下:
/** * 注册下载完成广播接收器, 还可以注册其它监听器, 比如: DownloadManager.ACTION_NOTIFICATION_CLICKED */ private void registerReceiverCompleted() { IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); registerReceiver(mBroadcastReceiver, intentFilter); } /** * 接收下载完成广播 */ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @ Override public void onReceive(Context context, Intent intent) { long reference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); if (mDownloadUniqueId = = reference) { query(reference); mShowInformation_tv.setText(information); mDownloadManager_btn.setText(" 点击下载" ); mDownloadManager_btn.setClickable(true); } } };

  • 如何查询下载任务的相关信息? 首先需要使用到内部类DownloadManager.Query, 查看源码学习该类各个方法的使用, TeachCourse简单总结: 该类正如文章开头样式的例子, 通过分配的id查询下载任务相关的信息, 这些信息包括文件类型、文件的Uri和文件的长度等, 代码如下:
/** * 查询下载任务相关的信息, 比如: 文件名、文件大小、文件类型等 * * @ param reference */ private void query(long reference) { DownloadManager.Query query = new DownloadManager.Query(); /**指定查询条件**/ query.setFilterById(reference); /**查询正在等待、运行、暂停、成功、失败状态的下载任务**/ query.setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL); Cursor cursor = mDownloadManager.query(query); if (cursor.moveToFirst()) { int fileId = cursor.getColumnIndex(DownloadManager.COLUMN_ID); int fileTitleId = cursor.getColumnIndex(DownloadManager.COLUMN_TITLE); int fileDescriptionId = cursor.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION); int fileTypeId = cursor.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE); int fileLengthId = cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES); int fileUriId = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI); /**过时的方式: DownloadManager.COLUMN_LOCAL_FILENAME**/ int fileNameId = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME); int statusCodeId = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS); int statusReasonId = cursor.getColumnIndex(DownloadManager.COLUMN_REASON); int downloadSizeId = cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR); int lastModifiedTimeId = cursor.getColumnIndex(DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP); int mediaUriId = cursor.getColumnIndex(DownloadManager.COLUMN_MEDIAPROVIDER_URI); String id = cursor.getString(fileId); String fileTitle = cursor.getString(fileTitleId); String description = cursor.getString(fileDescriptionId); String type = cursor.getString(fileTypeId); String length = cursor.getString(fileLengthId); String statusCode = cursor.getString(statusCodeId); String statusReason = cursor.getString(statusReasonId); String downloadSize = cursor.getString(downloadSizeId); String modifiedTime = cursor.getString(lastModifiedTimeId); String mediaUri = cursor.getString(mediaUriId); String fileUri = cursor.getString(fileUriId); String fileName = null; if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { openFile(type, Uri.parse(fileUri)); fileName = Uri.parse(fileUri).getPath(); } else { /**Android 7.0以上的方式: 请求获取写入权限, 这一步报错**/ fileName = cursor.getString(fileNameId); openFile(type, Uri.parse(fileUri)); }/**清空StringBuffer存储的数据**/ mStringBuffer.delete(0, mStringBuffer.length()); mStringBuffer.append(" id: " + id + " \\n" ); mStringBuffer.append(" fileTitle: " + fileTitle + " \\n" ); mStringBuffer.append(" description: " + description + " \\n" ); mStringBuffer.append(" type: " + type + " \\n" ); mStringBuffer.append(" length: " + length + " \\n" ); mStringBuffer.append(" fileName: " + fileName + " \\n" ); mStringBuffer.append(" fileUri: " + fileUri + " \\n" ); mStringBuffer.append(" statusCode: " + statusCode + " \\n" ); mStringBuffer.append(" statusReason: " + statusReason + " \\n" ); mStringBuffer.append(" downloadSize: " + downloadSize + " \\n" ); mStringBuffer.append(" modifiedTime: " + modifiedTime + " \\n" ); mStringBuffer.append(" mediaUri: " + mediaUri + " \\n" ); information = mStringBuffer.toString(); } cursor.close(); }

  • 代码加入判断语句, 如果非Android 7.0系统继续访问COLUMN_LOCAL_FILENAME获得文件存储的绝对路径( 上面中间部分代码) , openFile()方法代码如下:
/** * 根据文件的类型, 指定可以打开的应用程序 * * @ param type * @ param uri */ private void openFile(String type, Uri uri) { if (type.contains(" image/" )) { try { ParcelFileDescriptor descriptor = getContentResolver().openFileDescriptor(uri, " r" ); FileDescriptor fileDescriptor = descriptor.getFileDescriptor(); Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor); mShowPic_iv.setVisibility(View.VISIBLE); mShowPic_iv.setImageBitmap(bitmap); } catch (FileNotFoundException e) { e.printStackTrace(); } } }

现在, 我们已经掌握了DownloadManager怎么实例化、怎么配置下载任务、怎么开启后台服务以及如何查询任务相关信息, 想要实现一个应用程序版本更新就变得很简单, 实现多任务下载也不是难事, 完整源码参考文章后台通过的Demo。
三、关于ParcelFileDescriptor和FileDescriptor总结 官网的文档推荐我们使用ContentResolver.openFileDescriptor()方法, 获得一个ParcelFileDescriptor对象, 再通过getFileDescriptor()方法返回一个FileDescriptor, 它们之间的关系参考上面的代码。
FileDescriptor通常被称为文件描述符, 可以理解成本地的一个文件, 通过流的方式读取文件内容以及通过流的方式写入数据到文件, 这里是读取或写入数据到FileDescriptor中, 假如我们的Uri表示的是一个txt文件, 获取FileDescriptor对象后, 通过下面的代码读取txt文件的内容:
FileInputStream fis = new FileInputStream(fd);

同理, 写入数据到txt文件, 代码如下:
FileOutputStream out = new FileOutputStream(fd); out.write(' 写入数据到txt文件中' ); out.close();

获取到输入流或输出流后, 剩下的就是关于流的操作了, 划分为: 文件字节流文件字符流缓冲流数组流
3.1 改写上面的例子
openFile()方法使用封装好的decodeFileDescriptor(), 查看BitmapFactory.decodeFileDescriptor()相关源码, 学习如何读取文件描述符中的内容, 这里TeachCourse根据读取流的方式, 改写如下:
... Bitmap bitmap = BitmapFactory.decodeStream(getStreamByFileDescriptor(fileDescriptor)); ... /** * 通过流的方式读取内容 * * @ param fileDescriptor * @ return */ private InputStream getStreamByFileDescriptor(FileDescriptor fileDescriptor) { return new FileInputStream(fileDescriptor); }

于是, 可以对FileDescriptor进行简单的封装成writeData()readData(), 代码如下:
/**往FileDescriptor中写入数据 * @ param fileDescriptor * @ param content */ private void writeData(FileDescriptor fileDescriptor, String content) { FileOutputStream fos = new FileOutputStream(fileDescriptor); try { fos.write(content.getBytes()); } catch (IOException e) { e.printStackTrace(); } finally { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } }/**从FileDescriptor中读取数据 * @ param fileDescriptor * @ return */ private String readData(FileDescriptor fileDescriptor) { FileInputStream fis = new FileInputStream(fileDescriptor); byte[] b = new byte[1024]; int read; String content= null; try { while ((read = fis.read(b)) != -1) { content = new String(b, 0, read); } } catch (IOException e) { e.printStackTrace(); } finally { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } return content; }

总结: Android 7.0系统的权限更改, 包括三个方面, 文章从第二方面开始讲解, 着重介绍了FileProviderDownloadManager两个类的使用, 花了好长时间整理、测试和编辑, 如果对你有帮忙, 别忘了收藏和分享咯!
  1. FileProvider源码路径: nougat/WriteToReadActivity.java
  2. DownloadManager源码路径: download/DownloadActivity.java
  3. Demo源码
参考资料: https://developer.android.google.cn/about/versions/nougat/android-7.0-changes.html
【Android开发之深入理解Android 7.0系统权限更改相关文档】版权声明: 本文著作权归TeachCourse所有, 未经许可禁止转载, 谢谢支持!
转载请注明出处: http://teachcourse.cn/2379.html

    推荐阅读