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


    http://www.cnblogs.com/dazhao/p/6547811.html

    摘要:

    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 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 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 Studio新建xml目录

    然后,在xml文件夹下新建一个xml文件,文件命名file_provider.xml,指定根标签为paths,如下图:

    xml新建file_provider.xml

    在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

    2. <external-path>,表示外部内存卡根目录,对应根目录等价于
      Environment.getExternalStorageDirectory()
      查看完整路径:/storage/emulated/0

    3. <external-files-path>,表示外部内存卡根目录下的APP公共目录,对应根目录等价于
      Context#getExternalFilesDir(String) Context.getExternalFilesDir(null)
      查看完整路径:
      /storage/emulated/0/Android/data/cn.teachcourse.demos/files/Download

    4. <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+"
    "+mimeType);
                }
    
            }
        }

    运行Demo,控制台打印效果图:

    Android 7.0访问sdcard

    更多说明,可以参考Google提供的例子

    二、深入理解DownloadManager

    同样,为了方便理解DownloadManager的用法,首先以一个简单例子开始:从指定的url下载资源,然后显示下载资源的相关信息,运行Demo的效果图:

    DownloadManager详解

    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 + "
    ");
            mStringBuffer.append("fileTitle:" + fileTitle + "
    ");
            mStringBuffer.append("description:" + description + "
    ");
            mStringBuffer.append("type:" + type + "
    ");
            mStringBuffer.append("length:" + length + "
    ");
            mStringBuffer.append("fileName:" + fileName + "
    ");
            mStringBuffer.append("fileUri:" + fileUri + "
    ");
            mStringBuffer.append("statusCode:" + statusCode + "
    ");
            mStringBuffer.append("statusReason:" + statusReason + "
    ");
            mStringBuffer.append("downloadSize:" + downloadSize + "
    ");
            mStringBuffer.append("modifiedTime:" + modifiedTime + "
    ");
            mStringBuffer.append("mediaUri:" + mediaUri + "
    ");
            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源码
    4. 参考资料:https://developer.android.google.cn/about/versions/nougat/android-7.0-changes.html

  • 相关阅读:
    Calibrating & Undistorting with OpenCV in C++ (Oh yeah)
    opencv中标定函数calibrateCamera
    3D 障碍物感知
    在ubuntu18.04一键安装opencv3.4.1的脚本
    How to run yolov5 model using TensorRT.
    花费 3 天时间整理的代码规范示例代码,你确定不进来看看吗?
    企业级自定义表单引擎解决方案(六)--工作流挂接表单
    企业级自定义表单引擎解决方案(五)--自定义表单典型业务案例
    企业级自定义表单引擎解决方案(四)--实体对象模型实现
    企业级自定义表单引擎解决方案(三)--实体对象模型设计
  • 原文地址:https://www.cnblogs.com/dhcn/p/7131091.html
Copyright © 2020-2023  润新知