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

来源:互联网 发布:上海奥特莱斯淘宝真假 编辑:程序博客网 时间:2024/06/18 04:46

现在的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对应的值。

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



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


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

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

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

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");            }        }    }

看一下实机操作效果:




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

看一下log:

D/camerademo: dispatchTakePictureIntent1D/camerademo: createImageFileD/camerademo: createImageFile  image=/storage/emulated/0/Android/data/cj.com.camerademo/files/Pictures/JPEG_20161025_111559_-1787214180.jpgD/camerademo: createImageFile  mCurrentPhotoPath=file:/storage/emulated/0/Android/data/cj.com.camerademo/files/Pictures/JPEG_20161025_111559_-1787214180.jpgD/camerademo: dispatchTakePictureIntent1  photoURI=content://cj.com.camerademo.fileprovider/my_images/JPEG_20161025_111559_-1787214180.jpgI/LBE-Sec: intent=Intent { act=android.media.action.IMAGE_CAPTURE flg=0x3 (has clip) (has extras) } result=falseD/AppTracker: App Event: stopD/AbstractTracker: Event successD/camerademo: onActivityResultD/camerademo: no Bitmap return

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

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

官方文档:

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

参考文章:

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


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>
  • <files-path/>代表的根目录: Context.getFilesDir()
  • <external-path/>代表的根目录: Environment.getExternalStorageDirectory()

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


这里可以共享的文件:/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就讲这些了。

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









1 0
原创粉丝点击