Android调用相机应用拍照及FileProvider使用

时间:2021-11-01 20:28:34

现在的app基本上都需要用到拍照功能。

当需要拍照时,我们可以选择调用系统已有的相机应用拍照,然后获取相应的图片。另外我们也可以直接控制设备的相机硬件来拍照,因为google提供了camera相关的API。

这里首先来说一下如调用系统已有的相机应用拍照。

官方文档:

https://developer.android.com/training/camera/photobasics.html

首先在清单配置文件声明

<manifest ... >
    <uses-feature android:name="android.hardware.camera"
                  android:required="true" />
    ...
</manifest>

意思是只允许有摄像头的设备安装你的应用。

调用系统已有的相机应用拍照根据如何处理照片可以分两种情况:

1.拍完之后,图片数据通过onActivityResult(int requestCode, int resultCode, Intent data)回调函数里面的data参数带回来,当然前提是通过startActivityForResult()方法启动相机应用的。

2.启动相机应用的时候,将要存储图片数据的文件以uri形式传递给相机应用,这样相应拍完之后,图片就会存储到你指定的位置。

这两种方式拍照后的图片大小不一致,第1种返回是缩略图,第2种保存全尺寸的图片。

那先看第一种方式的相关处理过程:

private void dispatchTakePictureIntent() {
        Log.d(tag,"dispatchTakePictureIntent");
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
            startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
        }
    }
简简单单几句代码,

MediaStore.ACTION_IMAGE_CAPTURE
这个action代表有相机功能的应用,此时你发出这个intent,那么系统中能够接受该action的应用都能够启动,你选择其中一个。

takePictureIntent.resolveActivity(getPackageManager()
这个方法起保护作用,首先检查一下是否有相关应用能处理这个intent.

然后,就回调函数里面去获取图片:

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        Log.d(tag,"onActivityResult");
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
            if(data != null){
                Bundle extras = data.getExtras();
                if(extras != null){
                    Bitmap imageBitmap = (Bitmap) extras.get("data");
                    mImageView.setImageBitmap(imageBitmap);
                }else{
                    Log.d(tag,"no Bitmap return");
                }
            }else{
                Log.d(tag,"data is null");
            }
        }
    }

图片的在extras里 “data” key对应的值。

下面是我用真机测试的效果图:

Android调用相机应用拍照及FileProvider使用

Android调用相机应用拍照及FileProvider使用

手机有好几个相机应用,这里选择系统自带的相机应用完成拍照

Android调用相机应用拍照及FileProvider使用

这种方式拍摄的照片在图库也有保存。

好,接着看第二种方式的处理过程:

这种是指定图片数据存储的文件,首先创建存储文件

private File createImageFile() throws IOException {
        Log.d(tag,"createImageFile");
        // Create an image file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String imageFileName = "JPEG_" + timeStamp + "_";
        File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        File image = File.createTempFile(
                imageFileName,  /* prefix */
                ".jpg",         /* suffix */
                storageDir      /* directory */
        );
        Log.d(tag,"createImageFile  image="+image.getAbsolutePath());
        // Save a file: path for use with ACTION_VIEW intents
       // mCurrentPhotoPath = "file:" + image.getAbsolutePath();
        Log.d(tag,"createImageFile  mCurrentPhotoPath="+mCurrentPhotoPath);
        return image;
    }

如果存储在外部存储设备上,所以别忘了添加权限,
getExternalFilesDir(Environment.DIRECTORY_PICTURES)
这个返回的目录就是/storage/emulated/0/Android/data/应用包名/files/Pictures/,这是我真机对应目录,不同设备可能不一样有些可能是/storage/sdcard/Android/data/应用包名/files/Pictures/。这个目录就随意了
然后文件命名的方式JPEG_日期.jpg文件。
确认好存储图片数据的文件后,继续往下看:
 
 private void dispatchTakePictureIntent1() {
        Log.d(tag,"dispatchTakePictureIntent1");
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        // Ensure that there's a camera activity to handle the intent
        if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
            // Create the File where the photo should go
            File photoFile = null;
            try {
                photoFile = createImageFile();

            } catch (IOException ex) {
                // Error occurred while creating the File

            }
            // Continue only if the File was successfully created
            if (photoFile != null) {
                Uri photoURI = FileProvider.getUriForFile(this,
                        "cj.com.camerademo.fileprovider",
                        photoFile);
                Log.d(tag,"dispatchTakePictureIntent1  photoURI="+photoURI.toString());
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
                startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
            }
        }
    }

一般来说,将应用程序中的文件提供给另一个应用程序 是通过Uri的形式传递过去的。
Uri uri = Uri.parse("file://"+ photoFile.getAbsolutePath())。但是这里没有用file:// URI这个形式,而是使用 content:// Uri这种形式,这是因为如果你的app运行在Android N and higher, passing a file:// URI across a package boundary causes a FileUriExposedException. Therefore, we now present a more generic way of storing images using a FileProvider.这句英文不用翻译吧。总的来说使用 content:// Uri这种形式提供文件安全。
关于FileProvider后面再讲,先把上面内容讲完。
通过下面的key值
MediaStore.EXTRA_OUTPUT

将我们应用程序打算来存储图片的文件传递给了相机应用。

这样拍完照片之后,在onActivityResult(int requestCode, int resultCode, Intent data)回调函数中参数data不会有图片的信息了,等下看一下打印的log

 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        Log.d(tag,"onActivityResult");
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_TAKE_PHOTO && resultCode == RESULT_OK) {
            if(data != null){
                Bundle extras = data.getExtras();
                if(extras != null){
                    Bitmap imageBitmap = (Bitmap) extras.get("data");
                    mImageView.setImageBitmap(imageBitmap);
                }else{
                    Log.d(tag,"no Bitmap return");
                }
            }else{
                Log.d(tag,"data is null");
            }
        }
    }

看一下实机操作效果:

Android调用相机应用拍照及FileProvider使用


Android调用相机应用拍照及FileProvider使用

上面是存储的路径,没有错误,拍照也成功了

看一下log:

D/camerademo: dispatchTakePictureIntent1
D/camerademo: createImageFile
D/camerademo: createImageFile  image=/storage/emulated/0/Android/data/cj.com.camerademo/files/Pictures/JPEG_20161025_111559_-1787214180.jpg
D/camerademo: createImageFile  mCurrentPhotoPath=file:/storage/emulated/0/Android/data/cj.com.camerademo/files/Pictures/JPEG_20161025_111559_-1787214180.jpg
D/camerademo: dispatchTakePictureIntent1  photoURI=content://cj.com.camerademo.fileprovider/my_images/JPEG_20161025_111559_-1787214180.jpg
I/LBE-Sec: intent=Intent { act=android.media.action.IMAGE_CAPTURE flg=0x3 (has clip) (has extras) } result=false
D/AppTracker: App Event: stop
D/AbstractTracker: Event success
D/camerademo: onActivityResult
D/camerademo: no Bitmap return

回调里确实没有图片返回了,而且图像库也没有保存刚刚拍摄的照片。

前面用到了FileProvider,这里就简单说一下这个类:

官方文档:

https://developer.android.com/reference/android/support/v4/content/FileProvider.html

参考文章:

http://www.jianshu.com/p/3f9e3fc38eae

Android调用相机应用拍照及FileProvider使用

FileProvider是ContentPrivder的子类,ContentPrivder前面深入理解Android四大组件之一ContentProvider有讲过,它的作用是让不同应用之间共享数据,而这个FileProvider就是实现不同应用之间文件共享。

现在我这个应用要调用相机应用去拍照,相机应用拍完照之后,要把图片数据存储我这个应用的数据目录下的某个文件中去,这就涉及到了相机应用要共享我的应用文件。所以就可以通过FileProvider来实现。具体用法如下:

在我的应用需要有一个FileProvider

因为FileProvider这个类本身已经实现相应功能,所以直接使用这个类的对象即可,当然你也可以写一个Provider继承FileProvider.

在清单文件里声明。name 一项,如果直接用FileProvider,就如下写,如果自己定义的,就写入相应的全类名。

<provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="cj.com.camerademo.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"></meta-data>
        </provider>

authorities
不用说了,主机名,唯一标识

grantUriPermissions
授予共享权限,
<meta-data声明哪些文件可被共享,在res/xml/file_paths.xml中

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_images" path="Android/data/cj.com.camerademo/files/Pictures" />
</paths>

这里可以共享的文件:/storage/emulated/0/Android/data/cj.com.camerademo/files/Pictures/这个路径下的文件。

回头再看看前面的代码:

 Uri photoURI = FileProvider.getUriForFile(this,
                        "cj.com.camerademo.fileprovider",
                        photoFile);
                Log.d(tag,"dispatchTakePictureIntent1  photoURI="+photoURI.toString());
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
                startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
打印得到
 photoURI=content://cj.com.camerademo.fileprovider/my_images/JPEG_20161025_111559_-1787214180.jpg

因为这个 content://cj.com.camerademo.fileprovider/my_images/JPEG_20161025_111559_-1787214180.jpg Uri表示的文件已经被FileProvider允许共享了,所以,相机应用拍摄的照片可以存到这个文件里。其中原理可以这样理解,相机应用收到我们应用传过去的Uri后,就会解析Uri,得到主机名为“cj.com.camerademo.fileprovider”的内容提供者,通过这个内容提供者去往Uri指定的路径写入图片的数据。

cj.com.camerademo.fileprovider内容提供者的唯一身份,注意和清单文件里保持一致,my_images这个在file_paths.xml中映射为

Android/data/cj.com.camerademo/files/Pictures

关于FileProvider就讲这些了。

下篇接着讲调用设备摄像头硬件来拍照。