概述

最近在看 nanChen 写的图片选择器 ImagePicker,感觉写得很不错,也打算把从中学到的东西写下来。很多时候,遇到一个好的框架能够降低开发成本这是好事。但是也要去了解其内部具体实现逻辑,说不定哪天你需要完成一个类似的小功能,你知道原理就能快速写出来,而不是引入整个框架。

本文讲其中的第一个功能:如何调起手机的相机拍照?

系统现有相机应用

对于如何调用系统现有应用,这里简单再说一下。在开发的应用中调用系统现有应用,需要使用 Intent 指定开启的应用的 Action 和 Category,然后通过 startActivity(Intent) 或者 startActivityForResult(Intent, int) 开启指定的 Activity,如果使用 startActivityForResult() 方法开启并需要返回值,再重写 onActivityResult(int, int, Intent) 即可。

先来看看系统现有相机应用的 AndroidManifest.xml 清单文件定义的 Activity:

        <activity
android:name="com.android.camera.Camera"
android:clearTaskOnLaunch="true"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="landscape"
android:taskAffinity="android.task.camera"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<categroy android:name="android.intent.category.DEFAULT" />
<categroy android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.media.action.IMAGE_CAPTURE" />
<categroy android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.media.action.STILL_IMAGE_CAMERA" />
<categroy android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name="com.android.camera.VideoCamera"
android:clearTaskOnLaunch="true"
android:configChanges="origientation|keyboardHidden"
android:label="@string/video_camera_label"
android:screenOrientation="landscape"
android:taskAffinity="android.task.camcorder"
android:theme="@android:style/theme.Black.NoTitleBar.Fullscreen" >
<intent-filter>
<action android:name="android.media.action.VIDEO_CAMERA" />
<categroy android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.media.action.VIDEO_CAPTURE" />
<categroy android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

它定义了两个 Activity,com.android.camera.Camera 表示照相机,com.android.camera.VideoCamera 表示摄像机。从字面意思可以看出,为了捕获系统相机返回的数据,一般需要使用一下两个 Action 即可开启照相机与摄像机:

  • android.media.action.IMAGE_CAPTURE:Intent 的 Action 类型,从现有的相机应用中请求一张图片。

  • android.media.action.VIDEO_CAPTURE:Intent 的 Action 类型,从现有的相机应用中请求一段视频。

上面两个参数,均在 MediaStore 类中以静态常量的形式定义好了,分别是:MediaStore.ACTION_IMAGE_CAPTURE (相机) 和 MediaStore.ACTION_VIDEO_CAPTURE (摄像机)。

获取系统现有相机拍摄的图片

在新开启的 Activity 中,如果需要获取它的返回值,则需要使用 startActivityForResult(Intent,int) 方法打开 Activity,并重写 onActivityResult(int, int, Intent) 获取系统相机的返回数据,那么我们只需要在 onActivityResult() 中获取到返回值即可。

系统相机拍摄的照片,如果不指定路径,会保存在系统默认文件夹下,可以使用 Intent.getExtra() 方法得到,得到的是一个 Uri 地址,表示了一个内容提供者的地址。如果通过MediaStore.EXTRA_OUTPUT 指定了保存路径,那么通过 Intent.getExtra() 得到的将是一个空地址,但是既然是我们指定的地址,那么也不愁找不到它了。

但是如果是7.0以上,需要使用 FileProvider 来得到这个 uri 地址。

实现方案

说清楚流程之后,下面就是具体代码实现:

首先是在 AndroidManiFest.xml 下声明拍照权限:

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

声明权限后,在开始拍照前,还是需要判断用户是否给了我们拍照的权限:

if (( mActivity).checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
  ActivityCompat.requestPermissions(mActivity, new String[]{Manifest.permission.CAMERA}, GalleryActivity.REQUEST_PERMISSION_CAMERA);
} else {
  imagePicker.takePicture(mActivity, GalleryActivity.REQUEST_CODE_TAKE);
}

如果用户没有给权限,那么需要申请权限,权限申请以后,会有一个回调通知开发者是否允许了,具体见下发的代码:

    @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_PERMISSION_CAMERA) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
imagePicker.takePicture(this, REQUEST_CODE_TAKE);
} else {
showToast("权限被禁止,无法打开相机");
}
}
}

权限允许之后,通过 imagePicker.takePicture 去拍照。下面看下拍照的具体代码逻辑:

    /**
* 拍照的方法
*/
public void takePicture(Activity activity, int requestCode) {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
takePictureIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
if (takePictureIntent.resolveActivity(activity.getPackageManager()) != null) {
if (Utils.existSDCard()) takeImageFile = new File(Environment.getExternalStorageDirectory(), "/DCIM/camera/");
else takeImageFile = Environment.getDataDirectory();
takeImageFile = createFile(takeImageFile, "IMG_", ".jpg");
if (takeImageFile != null) {
// 默认情况下,即不需要指定intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
// 照相机有自己默认的存储路径,拍摄的照片将返回一个缩略图。如果想访问原始图片,
// 可以通过dat extra能够得到原始图片位置。即,如果指定了目标uri,data就没有数据,
// 如果没有指定uri,则data就返回有数据! Uri uri;
if (VERSION.SDK_INT <= VERSION_CODES.M) {
uri = Uri.fromFile(takeImageFile);
} else { /**
* 7.0 调用系统相机拍照不再允许使用Uri方式,应该替换为FileProvider
* 并且这样可以解决MIUI系统上拍照返回size为0的情况
*/
uri = FileProvider.getUriForFile(activity, "com.example.myapplication.provider", takeImageFile);
//加入uri权限 要不三星手机不能拍照
List<ResolveInfo> resInfoList = activity.getPackageManager().queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
activity.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
} // Log.e("nanchen", ProviderUtil.getFileProviderName(activity));
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
}
}
activity.startActivityForResult(takePictureIntent, requestCode);
}

关于 Intent.resolveActivity 作用,简单来说就是当你在调用第三方软件或者系统 Activity,类似打开相机,发送图片等隐式 Intent,是并不一定能够在所有的 Android 设备上都正常运行。通过该方法判断这个 intent 对应的 activity 是否存在,确保不会出现崩溃。

takeImageFile 是图片存储地址,在7.0以前,需要用 Uri.fromFile 进行处理。7.0之后的采用 FileProvider。下面介绍下 FileProvider 的使用方法:

注册

使用 FileProvider 需要在 AndroidManiFest.xml 里声明:

        <provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.myapplication.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path" />
</provider> 

这里是直接使用的 v4 包中的 FileProvider,我们也可以直接继承 FileProvider 类,适当重写重载函数,但不建议如此做。下面来介绍上面的几个设置:

  • name: provider 的类名,若使用默认的 v4 的 FileProvider 可使用 "android.support.v4.content.FileProvider",也可以设置为自定义的继承 FileProvider 的 provider 类;

  • authorities: 一个签名认证,可以自定义,但在获取 uri 的时候需要保持一致;

  • grantUriPermissions: 使用 FileProvider 的使用需要我们给流出的URI 赋予临时访问权限(READ 和 WRITE),该设置是允许我们行使该项权力;

  • meta-data: meta-data 配置的是我们可以访问的文件的路径配置信息,需要使用 xml 文件进行配置,FileProvider 会通过解析 xml 文件获取配置项,其中 name 名字不可改变为: android.support.FILE_PROVIDER_PATHS,resource 为配置路径信息的配置项目。

路径配置

可访问的路径配置可以在 res 中建立一个 xml 文件下面建立一个配置文件,格式如下:

<?xml version="1.0" encoding="utf-8"?>
<paths>
  <!--path:需要临时授权访问的路径(.代表所有路径)-->
  <!--name:就是你给这个访问路径起个名字-->
  <external-path name="cam" path="." />
</paths>

下面解释下:

  • <files-path/>   代表的根目录: Context.getFilesDir()

  • <external-path/>   代表的根目录: Environment.getExternalStorageDirectory()
  • <cache-path/>  代表的根目录: getCacheDir()

最后,将 uri 放到 MediaStore.EXTRA_OUTPUT 中。

takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);

拍照完成后,会回调 onActivityResult,在这里我们可以根据先前传的值将图片展示到 ImageView 中:

    @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.i(TAG, "系统相机拍照完成,resultCode=" + resultCode + " " + requestCode);
if (requestCode == REQUEST_CODE_TAKE) {
Uri uri = Uri.fromFile(takeImageFile);
iv_CameraImg.setImageURI(uri);
}
}

到此,调用系统相机拍照的过程到此就结束了。

Android 调起系统相机拍照的更多相关文章

  1. Android下载图片/调用系统相机拍照、显示并保存到本地

    package com.example.testhttpget; import java.io.BufferedReader; import java.io.FileNotFoundException ...

  2. Android 调用系统相机拍照保存以及调用系统相册的方法

    系统已经有的东西,如果我们没有新的需求的话,直接调用是最直接的.下面讲讲调用系统相机拍照并保存图片和如何调用系统相册的方法. 首先看看调用系统相机的核心方法: Intent camera = new ...

  3. 【踩坑速记】MIUI系统BUG,调用系统相机拍照可能会带给你的一系列坑,将拍照适配方案进行到底!

    一.写在前面 前几天也是分享了一些学习必备干货(还没关注的,赶紧入坑:传送门),也好久没有与大家探讨技术方案了,心里也是挺痒痒的,这不,一有点闲暇之时,就迫不及待把最近测出来的坑分享给大家. 提起An ...

  4. Android6.0机型上调用系统相机拍照返回的resultCode值始终等于0的问题

    版权声明:本文为博主原创文章,未经博主允许不得转载. 正常情况下调用系统相机拍照: 如果拍照后点击的是“确定”图标,返回的resultCode = -1(Activity.RESULT_OK): 如果 ...

  5. Android7.0调用系统相机拍照、读取系统相册照片+CropImageView剪裁照片

    Android手机拍照.剪裁,并非那么简单 简书地址:[我的简书–T9的第三个三角] 前言 项目中,基本都有用户自定义头像或自定义背景的功能,实现方法一般都是调用系统相机–拍照,或者系统相册–选择照片 ...

  6. android 调用系统相机拍照 获取原图

      好吧,为了这个问题又折腾了一整天.之前在网上找来的方法,如果在onActivityResult中直接用data.getData()的方式来生成bitmap,其实获取的是拍照生成的缩略图!看看尺寸就 ...

  7. Android笔记之调用系统相机拍照

    参考链接: 拍照  |  Android Developers, Android相机拍照方向旋转的解决方案:ExifInterface - 简书 Demo链接:https://pan.baidu.co ...

  8. Android调用系统相机拍照保存照片很小解决方案

    保存图片小的一般操作步骤: 1. 调用系统相机 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); startActivityFo ...

  9. [Android Pro] 调用系统相机和图库,裁剪图片

    private static final int PHOTO_REQUEST_TAKEPHOTO = 1;// 拍照 private static final int PHOTO_REQUEST_GA ...

随机推荐

  1. 配置Mongodb

    MS Windows: 常用命令: 查看帮助 mongod help/mongod -h 查看版本 mongod --version 启动/停止/重启服务 net start/stop/restart ...

  2. Python_包

    包 包是一种通过使用‘.模块名’来组织python模块名称空间的方式. 1. 无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,都要第一时间提高警 ...

  3. String.valueOf()和toString()的区别

    1.String.valueOf(): Object obj=null; String str=""; str=String.valueOf(obj); //str=obj.toS ...

  4. 第三十七篇 入门机器学习——Numpy基础

    No.1. 查看numpy版本 No.2. 为了方便使用numpy,在导入时顺便起个别名 No.3. numpy.array的基本操作:创建.查询.修改 No.4. 用dtype查看当前元素的数据类型 ...

  5. 7.log4j

    Log4j:日志工厂的一部分(使用起来比较麻烦) 1.要想使用外部类,得先导包 pom.xml <dependency> <groupId>log4j</groupId& ...

  6. Django_视图

    1. 视图 1.1 返回json数据 2. url配置 url组成 3. 获取 url参数 别名 4. url反向解析 接收参数 reverse 5. 视图总结 5.1 自定义错误页面 6. Http ...

  7. vue 使用 jsonp 请求数据

    vue 使用 jsonp 请求数据 vue请求数据的时候,会遇到跨域问题,服务器为了保证信息的安全,对跨域请求进行拦截,因此,为了解决vue跨域请求问题,需要使用jsonp. 安装jsonp npm ...

  8. 【做题笔记】洛谷P1002过河卒

    虽说是 dp 入门题,但还是有很多细节需要注意 如果设 \(f_{x,y}\) 为目标地点为 \((x,y)\) 时走的种数,那么答案就是 \(f_{n,m}\) 在不考虑那只讨厌的马的情况下,对于任 ...

  9. python正则元字符的含义

    练习的时候使用linux+ipython,ipython安装 python的元字符 # 元字符 :#  .   ^   $   *   +   ?   {}  []   \   |   () 注:\w ...

  10. SQL Server 用户定义表类型

    用户定义表类型: CREATE TYPE [dbo].[TVP_Location] AS TABLE( [Location] [varchar](50) NOT NULL, [Address] [va ...