概述

最近在看 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. BeautifulSoup的基本使用

    一.将一段文档传入BeautifulSoup的构造方法,得到一个文档的对象: from bs4 import BeautifulSoup Soup = BeautifulSoup(html_doc) ...

  2. 关于jquery绑定事件执行两次

    经常会出现jquery绑定事件执行两次的情况,如: $("a[tag=slide]").click(function () { alert(1); $(this).parent() ...

  3. OpenCV图像载入、显示和输出到文件以及滑块的使用

    图像载入 imread()函数 Mat imread(const string& filename, int flags = 1); 第一个参数为文件名 第二个参数为载入标识 flags &g ...

  4. docker映射

    端口映射 大-P对容器暴露的所有端口进行映射 小-p可以指定对哪些端口进行映射 第一种,只指定容器的端口,宿主机的端口是随机映射的 第二种,宿主机的端口和容器的端口一一对应, 第三种,只配置容器的ip ...

  5. CAD

    文件另存为——Autocad.doc.SaveAs   一.前言 使用pyautocad编辑好cad图纸后,往往涉及到一个保存的问题,但是官方文档并未提及,所以只能自己来了,测试了好久,终于是找到了保 ...

  6. php:对象(object)数据类型实例详解

    什么是对象? 对象是存储数据和有关如何处理数据的信息的数据类型.是系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位.一个对象由一组属性和对这组属性进行操作的一组服务组成. 语法 在 PHP ...

  7. poj 2195 Going Home(最小费用流)

    题目链接:http://poj.org/problem?id=2195 题目大意是给一张网格,网格中m代表人,h代表房子,网格中的房子和人数量相等,人可以向上向下走,每走1步花费加1,每个房子只能住一 ...

  8. 一起了解 .Net Foundation 项目 No.4

    .Net 基金会中包含有很多优秀的项目,今天就和笔者一起了解一下其中的一些优秀作品吧. 中文介绍 中文介绍内容翻译自英文介绍,主要采用意译.如与原文存在出入,请以原文为准. BenchmarkDotN ...

  9. C语言程序设计100例之(28):直线蛇形阵

    例28        直线蛇形阵 问题描述 编写程序,将自然数1.2.….N2按蛇形方式逐个顺序存入N阶方阵.例如,当N=3和N=4时的直线蛇形阵如下图1所示. 图1  直线蛇形阵 输入格式 一个正整 ...

  10. markdown pic

    Markdown 图片 Markdown 图片语法格式如下: ![alt 属性文本](图片地址) ![alt 属性文本](图片地址 "可选标题") 开头一个感叹号 ! 接着一个方括 ...