Android调取系统相机拍照获取到拍摄照片或从相册中直接选取照片后展示上传是Android开发中很常见的一个功能,实现的思路主要是:
* 自Android 6.0以后对某些涉及用户隐私权限的获取需要动态获取,所以首先是检查权限,如没有权限则动态申请权限,这里我们需要用到的权限是WRITE_EXTERNAL_STORAGE和CAMERA。

  • 自Android 7.0后系统禁止应用向外部公开file://URI ,因此需要FileProvider来向外界传递URI。

  • 获取到拍照后的照片,按照现在的手机拍照文件大小来说不做处理直接展示很容易发生OOM,因此这一步需要对图片做压缩处理。


一、动态申请权限

首先在Mainfest.xml文件中声明权限

<uses-permission android:name="android.permission.CAMERA"/>
<!--  因为拍照需要写入文件 所以需要申请读取内存的权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

接下来点击Button按钮模拟调取拍照

 private static final int REQUEST_PERMISSION_CODE = 101;mButtonTakePhoto.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//大于Android 6.0if (!checkPermission()) { //没有或没有全部授权requestPermissions(); //请求权限}} else {takePhoto();//拍照逻辑}}});//检查权限private boolean checkPermission() {//是否有权限boolean haveCameraPermission = ContextCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;boolean haveWritePermission = ContextCompat.checkSelfPermission(mContext,Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;return haveCameraPermission && haveWritePermission;}// 请求所需权限@RequiresApi(api = Build.VERSION_CODES.M)private void requestPermissions() {requestPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_PERMISSION_CODE);}// 请求权限后会在这里回调@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);switch (requestCode) {case REQUEST_PERMISSION_CODE:boolean allowAllPermission = false;for (int i = 0; i < grantResults.length; i++) {if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {//被拒绝授权allowAllPermission = false;break;}allowAllPermission = true;}if (allowAllPermission) {takePhotoOrPickPhoto();//开始拍照或从相册选取照片} else {Toast.makeText(mContext, "该功能需要授权方可使用", Toast.LENGTH_SHORT).show();}break;}}

在点击拍照按钮后,调用 ContextCompat.checkSelfPermission( )方法检查是否有权限,方法返回值为0说明已经授权。没授权的情况下,调用requestPermissions( )方法,该方法的第一个参数为一个数组,数组中的值为你要申请的一个或多个权限的值,第二个参数为请求码。

调用requestPermission( )方法后我们需要在Activity中重写onRequestPermissionsResult()方法,在该方法中会得到回调结果,方法中第一个参数是请求码,第二个参数是我们申请的权限数组,第三个参数数组中每一个值对应申请的每一个权限的返回值,值为0或-1,0代表授权,-1代表拒绝授权。源码如下

 /*** Permission check result: this is returned by {@link #checkPermission}* if the permission has been granted to the given package.*/public static final int PERMISSION_GRANTED = 0;//授权成功/*** Permission check result: this is returned by {@link #checkPermission}* if the permission has not been granted to the given package.*/public static final int PERMISSION_DENIED = -1;//拒绝授权
二、FileProvider

在获取所有所需的权限后,我们调取系统相机拍照


private void takePhoto() {// 步骤一:创建存储照片的文件String path = getFilesDir() + File.separator + "images" + File.separator;File file = new File(path, "test.jpg");if(!file.getParentFile().exists())file.getParentFile().mkdirs();if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//步骤二:Android 7.0及以上获取文件 Uri mUri = FileProvider.getUriForFile(PickPicActivity.this, "com.example.admin.custmerviewapplication", file);} else {//步骤三:获取文件UrimUri = Uri.fromFile(file);}//步骤四:调取系统拍照Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");intent.putExtra(MediaStore.EXTRA_OUTPUT, mUri);startActivityForResult(intent, REQUEST_TAKE_PHOTO_CODE);}

在Android 7.0之前我们只需要步骤一、三、四即可调取系统相机拍照,在此之后的话直接这么调取会报android.os.FileUriExposedException异常。所以我们需要对Android 7.0及以后的机型适配,采用FileProvider方式。

1. FileProvider是什么

FileProvider是ContentProvider的一个子类,用于应用程序之间私有文件的传递。自Android 7.0后系统禁止应用向外部公开file://URI ,因此需要FileProvider来向外界传递URI,传递的形式是content : //Uri,使用时需要在清单文件中注册。

2.注册清单文件
<manifest>...<application>...<providerandroid:name="android.support.v4.content.FileProvider"android:authorities="com.example.admin.custmerviewapplication"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/file_paths" /></provider>...</application>
</manifest>

解释上面provider标签的意思:

name 因为我们使用的是V4包下的FileProvider ,所以name的值就是V4包下FileProvider的相对路径值。当然我们也可以自定义类继承于FileProvider,这时候name的值就是我们自定义类的相对路径了

authorities 可以理解为标识符,是我们自己自定义的。我们代码中调用getUriForFile方法获取Uri时第二个参数就是这里我们定义的值。

exported 代表是否可以输出被外部程序使用,填false就行。

android:grantUriPermissions 是否允许为文件授予临时权限,必须为true

标签里配置的内容是用来指定那个文件夹下的文件是可被共享的。
name 为固定的值android.support.FILE_PROVIDER_PATHS。
path 是对应的xml文件路径,@xml/file_paths代表在xml文件下的file_paths文件。

3.指定可共享的文件路径

我们在res目录下新建一个xml文件夹,在文件夹下创建一个名为file_paths的xml文件

<paths xmlns:android="http://schemas.android.com/apk/res/android"><!--files-path  相当于 getFilesDir()--><files-path name="my_images" path="images"/><!--cache-path  相当于 getCacheDir()--><cache-path name="lalala" path="cache_image"/><!--external-path  相当于 Environment.getExternalStorageDirectory()-->< external-path  name="hahaha" path="comeOn"/><!--external-files-path  相当于 getExternalFilesDir("") --><external-files-path name="paly" path="freeSoft"/><!--external-cache-path  相当于 getExternalCacheDir() --> <external-cache-path  name="lei" path="."/>...
</paths>

files-path所代表的路径等于getFilesDir(),打印getFileDir( )它的路径是 /data/user/0/包名/files。什么意思呢,<files-path name="my_images" path="images"/>的意思就是/data/user/0/包名/files + "/files-path标签中path的值/"路径下的文件是可共享的,在生成Uri时name的值my_images会替代上面的路径/data/user/0/包名/files / images /向外暴露。最终的Uri会是content : //com.example.admin.custmerviewapplication / my_images / test.jpg

我们在代码中获取Uri的方法就是FileProvider.getUriForFile(“上下文”,”清单文件中authorities的值”,”共享的文件”);

三、图片获取并压缩

我们调用startActivityForResult(intent, REQUEST_TAKE_PHOTO_CODE);进行拍照,拍照结束后会回调onActivityResult( )方法。

@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (resultCode == RESULT_OK && requestCode == REQUEST_TAKE_PHOTO_CODE) {//获取系统照片上传Bitmap bm = null;try {bm = getBitmapFormUri(mUri);} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}mImageView.setImageBitmap(bm);}}

通过Uri直接获取图片加载到内存然后显示在ImageView很容易发生OOM,所以还需做进一步的图片压缩。

public Bitmap getBitmapFormUri(Uri uri) throws FileNotFoundException, IOException {InputStream input = getContentResolver().openInputStream(uri);//这一段代码是不加载文件到内存中也得到bitmap的真是宽高,主要是设置inJustDecodeBounds为trueBitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();onlyBoundsOptions.inJustDecodeBounds = true;//不加载到内存onlyBoundsOptions.inDither = true;//optionalonlyBoundsOptions.inPreferredConfig = Bitmap.Config.RGB_565;//optionalBitmapFactory.decodeStream(input, null, onlyBoundsOptions);input.close();int originalWidth = onlyBoundsOptions.outWidth;int originalHeight = onlyBoundsOptions.outHeight;if ((originalWidth == -1) || (originalHeight == -1))return null;//图片分辨率以480x800为标准float hh = 800f;//这里设置高度为800ffloat ww = 480f;//这里设置宽度为480f//缩放比,由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可int be = 1;//be=1表示不缩放if (originalWidth > originalHeight && originalWidth > ww) {//如果宽度大的话根据宽度固定大小缩放be = (int) (originalWidth / ww);} else if (originalWidth < originalHeight && originalHeight > hh) {//如果高度高的话根据宽度固定大小缩放be = (int) (originalHeight / hh);}if (be <= 0)be = 1;//比例压缩BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();bitmapOptions.inSampleSize = be;//设置缩放比例bitmapOptions.inDither = true;bitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;input = getContentResolver().openInputStream(uri);Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);input.close();return compressImage(bitmap);//再进行质量压缩}public Bitmap compressImage(Bitmap image) {ByteArrayOutputStream baos = new ByteArrayOutputStream();image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中int options = 100;while (baos.toByteArray().length / 1024 > 100) {  //循环判断如果压缩后图片是否大于100kb,大于继续压缩baos.reset();//重置baos即清空baos//第一个参数 :图片格式 ,第二个参数: 图片质量,100为最高,0为最差  ,第三个参数:保存压缩后的数据的流image.compress(Bitmap.CompressFormat.JPEG, options, baos);//这里压缩options,把压缩后的数据存放到baos中options -= 10;//每次都减少10if (options<=0)break;}ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把压缩后的数据baos存放到ByteArrayInputStream中Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStream数据生成图片return bitmap;}

压缩的步骤分为两步,第一步是先得到bitmap的真实宽高计算压缩比例,得到压缩比例后进行初步压缩。第二步将初步压缩的bitmap进行质量压缩得到最终的图片。

从相册中选取图片步骤和调取相机拍照的步骤一致,只是创建的intent和在onActivtyResult回调时获取的Uri不同。

//调用相册
Intent intent = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_UR);
startActivityForResult(intent, PICK_IMAGE_CODE);@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);//获取图片路径if (requestCode == PICK_IMAGE_CODE && resultCode == Activity.RESULT_OK && data != null) {mUri = data.getData();//通过getData获取到Uri...}}

END:以上如有错误欢迎大家批评指正,你的留言和点赞对我是莫大的鼓励。欢迎关注我的个人微信公众号「 未远可追」,更多的技术分享和人生感悟。

适配Android7.0调取相机拍照并返回照片相关推荐

  1. 安卓调用系统相机拍照并返回,实现图片预览

    安卓调用相机拍照并返回预览及相关类型换(略缩图,画质糊)原图预览参考传送门 一.demo预览 二.xml代码和activity简单代码描述. 三.顺便写几个转换工具方法吧 今年主要在忙新项目,安卓也有 ...

  2. Android 7.0 获取相机拍照图片,适配三星手机拍照,解决三星手机拍照屏幕旋转,判断设备是否有摄像头

    方法1 新建/res/xml/file_paths: <?xml version="1.0" encoding="utf-8"?> <path ...

  3. Android - 更换头像及图片裁剪(适配Android7.0)

    我的CSDN: ListerCi 我的简书: 东方未曦 一.概述 相信大家都用过 Android 应用中更换头像的功能,在这个功能中,用户可以拍照或者选择相册图片,然后裁剪出头像所需要的图案. 那么你 ...

  4. 微信chooseImage,getLocalImgData调取相机拍照,获取图片base64

    <a href="javascript:void(0)" onclick="use_wx_camera()">调取微信拍照</a> &l ...

  5. Android 11适配指南之系统相机拍照、打开相册,安卓app开发教程

    Android 6 权限适配 Android 7 文件适配 Android 10/11 存储适配 ok,接下来以一个更换头像的小例子来讲解一下. 示例 ======================== ...

  6. Android踩坑日记:android7.0动态相机权限

    前提: 项目中使用的动态权限开源库github:https://github.com/yanzhenjie/AndPermission. 转载必须注明本文转自严振杰的博客:http://blog.cs ...

  7. android相机保存文件为空,android 调用系统相机拍照,返回的data为null

    最近做项目,需要拍照功能,于是就想简单的调用系统相机来完成这一需求(当然,如果想要个性化一点的,也可以自定义camera去实现,这里暂时不做). if(Environment.getExternalS ...

  8. Android实现更换头像功能(适配Android7.0版本)

    只要涉及到用户的功能,基本都会使用到用户头像功能.那么切换用户头像,就是一个必做的功能.切换头像的图片源,一般有两个:一个是拍照然后裁剪图片,另一种是从图库中选择图片,然后裁剪图片.所以这里就来实现这 ...

  9. Android系统相机拍照与选择照片

    拍照与选择照片 // 拍摄照片private val takePicturePreview =registerForActivityResult(ActivityResultContracts.Tak ...

最新文章

  1. 宏基因组实战3. MEGAHIT组装拼接及quast评估
  2. linux bash shell 常用快捷键
  3. java的知识点32——多线程 并发同步的 性能分析、快乐影院  订票操作
  4. Php无刷新修改url,history 实现无刷新更改url和页面内容
  5. LBaaS 实现机制 - 每天5分钟玩转 OpenStack(125)
  6. CCF OJ 1113-括号匹配[栈]
  7. 控制文件中的 MAXDATAFILES 参数
  8. 鞭策你一辈子的好文章
  9. linux callback函数,C++回调函数(callback)的使用
  10. .NET Core 3.0 Preview 3中关于ASP.NET Core的更新内容
  11. excel中如何动态地创建控件以显示查询结果_Excel催化剂开源第23波-VSTO开发辅助录入功能...
  12. 运用孤立森林异常检测算法,过滤异常数据
  13. 问题五十九:怎么求一元六次方程在区间内的所有不相等的实根(1)
  14. Spring Boot各种日志记录方式详解
  15. java ee中如何实现数据库中数据柱状图,#java 将echarts生成的图表导出到excle表格中,后台是javaee,求大神解决,谢谢#excel输入数值e...
  16. PPT设置密码和加水印的方法
  17. Tecplot新手进阶--使用tecplot宏操作批量处理数据输出图片(详细步骤)
  18. 2011年中国科学院院士增选初步候选…
  19. 面向初学者的 MQL4 语言系列之4——自定义指标
  20. 企业信息系统架构要点

热门文章

  1. 【阿里云镜像】OpenSUSE全新安装并更改阿里OpenSUSE镜像源
  2. 互联网为何“杀死”中年人?人人皆笑余欢水,人人皆是余欢水!
  3. ubuntu16.04/18.04安装卸载cuda10.0/10.1和cudnn图文说明
  4. 不平凡的2020遇见充满期待的2021
  5. java swing 购物管理系统 java swing mysql实现的购物管理系统源码(1028)
  6. 作为一个安全措施,windows 不允许对这台计算机进行远程访问,打印机共享设置及常见问题...
  7. python3安装PCV包
  8. ORA-00257: archiver error. Connect internal only, until freed 与“对归档日志的验证失败”错误的处理方法...
  9. storage路径问题
  10. XC6SLX100-3FGG484C规格、XC7A15T-2CPG236I产品概述及应用