targetSdk27 FileProvider 摄像和照相[通俗易懂]

targetSdk27 FileProvider 摄像和照相[通俗易懂]推荐Github开源项目SelectImgAsWechath:https://github.com/SCCXYS/SelectImgAsWechat参考地址:AndroidFileProvider详细解析和踩坑指南开始以下,调用相机的代码出自开源项目SelectImgAsWechath。权限<!–拍照–><uses-permissionandroid:name=”android.permission.CAMERA”/><!

大家好,又见面了,我是你们的朋友全栈君。

相机

以下,调用相机的代码出自开源项目 SelectImgAsWechath 。

添加权限:

    <!--拍照-->
    <uses-permission android:name="android.permission.CAMERA" />
    <!--写入SD卡的权限:如果你希望保存相机拍照后的照片-->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!--读取SD卡的权限:打开相册选取图片所必须的权限-->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <!--网络-->
    <uses-permission android:name="android.permission.INTERNET"/>
    <!-- 在SDCard中创建与删除文件权限 -->
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>

    <uses-permission android:name="android.permission.RECORD_AUDIO" />

调用:

    public void takePicture(Activity activity, int requestCode) { 
   
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        if (intent.resolveActivity(activity.getPackageManager()) != null) { 
   
            if (existSDCard()) { 
   
                takeImageFile = new File(Environment.getExternalStorageDirectory(), "/DCIM/camera/");
            } else { 
   
                takeImageFile = Environment.getDataDirectory();
            }
            Log.i(TAG, "takeImageFile ===" + takeImageFile);
            takeImageFile = createFile(takeImageFile, "IMG_", ".jpg");
            Log.i(TAG, "createFile ===" + takeImageFile);
            if (takeImageFile != null) { 
   
                Uri uri = Uri.parse("file://" + takeImageFile.getAbsolutePath());
                Log.i(TAG, "uri ===" + uri);
                intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
            }
        }
        activity.startActivityForResult(intent, requestCode);
    }

发生异常:
在使用照相机或者摄像机的时候出现异常(targetSdkVersion 27):

    io.reactivex.exceptions.OnErrorNotImplementedException: 
    file:///storage/emulated/0/DCIM/camera/IMG_20200526_110146.jpg exposed beyond app through ClipData.Item.getUri()
        at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:704)
        at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:701)
        at io.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:77)
        at io.reactivex.internal.observers.LambdaObserver.onNext(LambdaObserver.java:67)

经百度:
参考地址:解决exposed beyond app through ClipData.Item.getUri() 错误

7.0后的版本:
不再允许在app之间,使用file://的方式传递File,否则会抛出FileUriExposedException异常

FileUriExposedException:
应用程序将file://Uri 暴露给另一个应用程序时引发的异常。不鼓励这种曝光,因为接收应用可能无法访问共享路径。

严格模式解决办法

项目中的解决办法是
在onCreate的时候,添加如下代码:

        // android 7.0系统解决拍照的问题
        StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
        StrictMode.setVmPolicy(builder.build());
        builder.detectFileUriExposure();

StrictMode 严格模式应用:https://www.jianshu.com/p/b80c58677892

Android 2.3提供一个称为严苛模式(StrictMode)的调试特性,Google称该特性已经使数百个Android上的Google应用程序受益。那它都做什么呢?它将报告与线程及虚拟机相关的策略违例。一旦检测到策略违例(policy violation),你将获得警告,其包含了一个栈trace显示你的应用在何处发生违例。你可以强制用警告代替崩溃(crash),也可以仅将警告计入日志,让你的应用继续执行。

虽然官方官方不是这么推荐滴,但是存在即合理?

摄像

通过这段代码,摄像机也可以这么搞:

    private void takenVedio() { 
   
        // 表示跳转至相机的录视频界面
        Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
        // MediaStore.EXTRA_VIDEO_QUALITY 表示录制视频的质量,从 0-1,越大表示质量越好,同时视频也越大
        intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
        intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 30);
        // 调用前置摄像头
        intent.putExtra("android.intent.extras.CAMERA_FACING", 1);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        if (intent.resolveActivity(getPackageManager()) != null) { 
   
            File file;
            if (existSDCard()) { 
   
                file = new File(Environment.getExternalStorageDirectory(), "/DCIM/audio/");
            } else { 
   
                file = Environment.getDataDirectory();
            }
            Log.i(TAG, "file ===" + file);
            file = createFile(file, "VIDEO_", ".mp4");
            Log.i(TAG, "file ===" + file);
            if (file != null) { 
   
                Uri uri = Uri.parse("file://" + file.getAbsolutePath());
                Log.i(TAG, "uri ===" + uri);
                intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
            }
        }
        startActivityForResult(intent, REQUEST_CODE_RECORD_VIDEO);
    }

官方推荐 – FileProvider

1. 缘由(转载)

Android7.0 (N) 开始,将严格执行 StrictMode 模式,也就是说,将对安全做更严格的校验。而从 Android N 开始,将不允许在 App 间,使用 file:// 的方式,传递一个 File ,否者会抛出 FileUriExposedException的错误,会直接引发 Crash。

但是,既然官方对文件的分享做了一个这么强硬的修改(直接抛出异常),实际上也提供了解决方案,那就是 FileProvider,通过 content://的模式替换掉 file://,同时,需要开发者主动升级 targetSdkVersion 到 24 才会执行此策略。

FileProvider是android support v4包提供的,是ContentProvider的子类,便于将自己app的数据提供给其他app访问。
在app开发过程中需要用到FileProvider的主要有

  1. 相机拍照以及图片裁剪
  2. 调用系统应用安装器安装apk(应用升级)

2. 配置 FileProvider

2.1 AndroidManifest内配置FileProvider

注意:provider是4大组件,需要放置application中哦

        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/install" />
        </provider>

2.2 可访问路径配置

位置:
在这里插入图片描述
install.mxl :

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">

    <external-path
        name="DCIM"
        path="DCIM/camerademo" />

    <!--代表外部存储区域的根目录下的文件 Environment.getExternalStorageDirectory()/目录-->
    <external-path
        name="external_storage_root"
        path="." />

    <external-path
        name="Pictures"
        path="Pictures/camerademo" />

    <!--代表app 私有的存储区域 Context.getFilesDir()目录下的images目录 /data/user/0/com.hm.camerademo/files/images-->
    <files-path
        name="private_files"
        path="images" />

    <!--代表app 私有的存储区域 Context.getCacheDir()目录下的images目录 /data/user/0/com.hm.camerademo/cache/images-->
    <cache-path
        name="private_cache"
        path="images" />

    <!--代表app 外部存储区域根目录下的文件 Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)目录下的Pictures目录-->
    <!--/storage/emulated/0/Android/data/com.xx.xxxxxx/files/Pictures-->
    <external-files-path
        name="external_files"
        path="Pictures" />

    <!--代表app 外部存储区域根目录下的文件 Context.getExternalCacheDir目录下的images目录-->
    <!--/storage/emulated/0/Android/data/com.xx.xxxxxx/cache/images-->
    <external-cache-path
        name="external_cache"
        path="." />

</paths>

分析路径什么用途?

当调用Environment.getExternalStorageDirectory()获取,外部存储根目录时,获取的路径是:

/storage/emulated/0

打印2.3中

        photoUri = FileProvider.getUriForFile(
                this,
                getPackageName() + ".fileprovider",
                takeImageFile);

photoUri 的值:

content://com.yoshin.company.blogdemo.fileprovider/external_storage_root/DCIM/camera/IMG_20200526_203123.jpg

此时外部存储根目录在Uri中显示为 “external_storage_root”

所以:

    <external-path
        name="external_storage_root"
        path="." />

中,name代表uri中显示名称,path是绝对路径。

其他路径

    <external-path
        name="DCIM"
        path="DCIM/camerademo" />

我们知道获取DCIM的路径代码是:

Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsoluteFile()

打印出的路径是:

/storage/emulated/0/DCIM

所以,我们可以知道替换

            takeImageFile = new File(Environment.getExternalStorageDirectory(), "/DCIM/camera/");

takeImageFile = new File(
	Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsoluteFile(), "/DCIM/camera/");

时,会打印出:

content://com.yoshin.company.blogdemo.fileprovider/external_storage_root/DCIM/DCIM/camera/IMG_20200526_203123.jpg

通过:

    <external-path
        name="DCIM"
        path="DCIM/camerademo" />

我们知道,Uri中要显示”DCIM”,绝对路径必须是 path=“DCIM/camerademo”,修改代码:

takeImageFile = new File(
	Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsoluteFile(), "/camerademo/");

此时,打印出来的Uri即是:

content://com.yoshin.company.blogdemo.fileprovider/DCIM/IMG_20200526_204125.jpg

懂了吗?

当我们代码中使用的绝对路径,(如 “DCIM/camerademo”)匹配到了install.mxl中设置到的path字段的内容(path=“DCIM/camerademo”),Uri中显示路径就会被对应的name字段内容替代了。

配置的标签参照FileProvider里面的TAG配置

在这里插入图片描述

3. 访问 FileProvider

    public void takePicture(Activity activity, int requestCode) { 
   
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                | Intent.FLAG_ACTIVITY_CLEAR_TOP);

        if (existSDCard()) { 
   
            takeImageFile = new File(Environment.getExternalStorageDirectory(), "/DCIM/camera/");
        } else { 
   
            takeImageFile = Environment.getDataDirectory();
        }
        takeImageFile = createFile(takeImageFile, "IMG_", ".jpg");

        photoUri = FileProvider.getUriForFile(
                this,
                getPackageName() + ".fileprovider",
                takeImageFile);

        intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
        activity.startActivityForResult(intent, requestCode);
    }

4. 拓展:获取图片方式

4.1 通过绝对路径获取图片

image.setImageBitmap(BitmapFactory.decodeFile(takeImageFile.getAbsolutePath()));

4.2 通过Uri获取资源流

            Bitmap bitmap = null;
            try { 
   
                bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(photoUri));
            } catch (FileNotFoundException e) { 
   
                e.printStackTrace();
            }
            image2.setImageBitmap(bitmap);

4.3 通过cursor获取图片

嘿嘿嘿,这是错误示范,引以为戒

            Cursor cursor = getContentResolver().query(photoUri, new String[]{ 
   MediaStore.Images.Media.DATA}, null, null, null);
            if (cursor != null) { 
   
                if (cursor.getColumnCount() > 0) { 
   
                    if (cursor.moveToFirst()) { 
   
                        int index = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
                        if (index > -1) { 
   
                            path = cursor.getString(index);
                        } else { 
   
                            LogUtils.e(TAG, "cursor index -1");
                        }
                    }
                }
                cursor.close();
            }

通过Uri和selection来获取真实路径
Android系统提供了MediaScanner,MediaProvider,MediaStore等接口,并且提供了一套数据库表格,通过Content Provider的方式提供给用户。当手机开机或者有SD卡插拔等事件发生时,系统将会自动扫描SD卡和手机内存上的媒体文件,如audio,video,图片等,将相应的信息放到定义好的数据库表格中。
在这个程序中,我们不需要关心如何去扫描手机中的文件,只要了解如何查询和使用这些信息就可以了。

MediaStore中定义了一系列的数据表格,通过ContentResolver提供的查询接口,我们可以得到各种需要的信息。

EXTERNAL_CONTENT_URI 为查询外置内存卡的,INTERNAL_CONTENT_URI为内置内存卡。
MediaStore.Audio获取音频信息的类
MediaStore.Images获取图片信息
MediaStore.Video获取视频信息

为什么是错误示范呢?

因为 getContentResolver().query(photoUri, new String[]{MediaStore.Images.Media.DATA}, null, null, null); 查询不到任何图片信息,这和Uri有关系,因为我们的Uri不是目标文件的绝对路径,所以查询不到任何东西。

至于Uri怎么设置成绝对路径(试了试,都没有成功),

我自己拼了个Uri

            Uri uri = new Uri.Builder()
                .scheme("content")
                .authority(BuildConfig.APPLICATION_ID+".fileprovider")
                .path("/DCIM/camerademo/IMG_20200527_093044.jpg")
                .build();

打印:

uri ===content://com.yoshin.company.blogdemo.fileprovider/DCIM/camerademo/IMG_20200527_093044.jpg

替换photoUri去查询,并不好用,查询出0条信息。

再试下,还是不行~~~~

            Uri uri = new Uri.Builder()
                .scheme("content")
                .authority(BuildConfig.APPLICATION_ID+".fileprovider")
                .path("/storage/emulated/0/DCIM/camerademo/IMG_20200527_093044.jpg")
                .build();

崩溃了。

通过cursor获取图片,不太懂怎么弄的,记录一下。欢迎大神留言。感谢。

推荐Github开源项目 SelectImgAsWechath :https://github.com/SCCXYS/SelectImgAsWechat
参考地址:Android FileProvider详细解析和踩坑指南

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/162732.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • hive的元数据存储在derby和mysql_桌面云必须部署的组件包括

    hive的元数据存储在derby和mysql_桌面云必须部署的组件包括搭建hive的环境需要hadoop的dfs和yarn可以正常运行的情况下。准备好apache-hive-1.2.1-bin.tar.gz和mysql-libs.zip两个包hive安装步骤:解压apache-hive-1.2.1-bin.tar.gz到/usr/local/src下,并且将其重命名为hivetar-zxvfapache-hive-1.2.1-bin.tar.gzmvapache-hive-1.2.1-binhive复制/hive/conf下的hive-en.

    2025年6月19日
    0
  • 设计模式(一)工厂模式Factory(创建型)

    设计模式(一)工厂模式Factory(创建型)设计模式一工厂模式Factory在面向对象编程中,最通常的方法是一个new操作符产生一个对象实例,new操作符就是用来构造对象实例的。但是在一些情况下,new操作符直接生成对象会带来一些问题。举例来说,许多类型对象的创造需要一系列的步骤:你可能需要计算或取得对象的初始设置;选择生成哪个子对象实例;或在生成你需要的对象之前必须先生成一些辅助功能的对象。在这些………

    2022年7月20日
    16
  • vue-axios使用_vue接口请求放在哪里写

    vue-axios使用_vue接口请求放在哪里写什么是axiosAxios是一个基于promise的HTTP库,可以用在浏览器和node.js中。主要的作用:axios主要是用于向后台发起请求的,还有在请求中做更多是可控功能。a

    2022年7月29日
    24
  • 离散傅里叶变换和离散时间傅里叶变换_离散傅里叶变换求相位

    离散傅里叶变换和离散时间傅里叶变换_离散傅里叶变换求相位1.离散时间傅里叶变换的导出针对离散时间非周期序列,为了建立它的傅里叶变换表示,我们将采用与连续情况下完全类似的步骤进行。考虑某一序列$x[n]$,它具有有限持续期;也就是说,对于某个整数$

    2022年8月4日
    4
  • 网络爬虫原理解析「建议收藏」

    1、网络爬虫原理网络爬虫指按照一定的规则(模拟人工登录网页的方式),自动抓取网络上的程序。简单的说,就是讲你上网所看到页面上的内容获取下来,并进行存储。网络爬虫的爬行策略分为深度优先和广度优先。如下图是深度优先的一种遍历方式是A到B到D到E到C到F(ABDECF)而宽度优先的遍历方式ABCDEF。2、写网络爬虫的原因(1)互联网中的数据量大,我们不能人工的去收集数据,这样会很浪费时间与金钱…

    2022年4月10日
    61
  • leetcode 接雨水2_雨水口连接管

    leetcode 接雨水2_雨水口连接管题目链接给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。示例 1:输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]输出:6解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。示例 2:输入:height = [4,2,0,3,2,5]输出:9 提示:n == height.length0 <= n &lt

    2022年8月8日
    6

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号