现在的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: 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
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就讲这些了。
下篇接着讲调用设备摄像头硬件来拍照。