【Android Developers Training】 48. 轻松拍摄照片
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好。
原文链接:http://developer.android.com/training/camera/photobasics.html
这节课将展示如何使用已经存在的相机应用拍摄相机。
假设你现在在实现一个基于人群的气象服务,它构建一个全球的气象地图,通过将运行了你的应用色设备所拍摄的天空照片拼接起来来实现这个气象地图。整合照片只是你的应用的一小部分。你希望通过最简单地方式拍摄照片,而不是需要重新构造一个相机。大多数Android设备其实已经至少有了一个相机应用。在这节课中,你将学习如何利用它来为您拍摄一个照片。
一). 请求相机权限
如果你的应用中一个重要的函数会拍摄照片,同时限制只有那些拥有相机的设备可以在Google Play上下载。为了声明你的应用依赖于一个相机,在你的清单文件中放置一个<uses-feature>标签:
<manifest ... > <uses-feature android:name="android.hardware.camera" android:required="true" /> ... </manifest>
如果你的应用使用,但并不依赖一个相机来执行功能,那么将“android:required”设置为“false”。这样的话,那么Google Play将会允许没有相机的设备下载你的应用。那么接下来就是你的责任在运行时如果调用了需要用相机的函数时,通过调用hasSystemFeature(PackageManager.FEATURE_CAMERA)检查是否可以获取相机。如果相机无法获取,那么你就应该禁止你的相关功能特性。
二). 使用相机应用拍摄照片
在Android中向其它应用分发意图是通过激活一个描述你的意图的Intent。这一过程分为三步:Intent自身,调用外部Activity,当焦点回到你的activity中处理图像数据的一些代码。
下面的代码是构造一个intent来获取一张照片。
static final int REQUEST_IMAGE_CAPTURE = 1; private void dispatchTakePictureIntent() { Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (takePictureIntent.resolveActivity(getPackageManager()) != null) { startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE); } }
注意这里startActivityForResult()方法被一个前提所保护,那就是通过调用resolveActivity(),返回第一个可以处理该intent的组件。执行这个检查时很重要的因为如果你调用了startActivityForResult()并使用一个没有一个应用可以处理的intent,你的应用将会崩溃。所以只要结果不是null,那么使用这个intent是安全的。
三). 获取缩略图
如果简单地获取照片不是你的应用的终极目标,那么你可能希望从相机应用收回图像并做一些事情。
Android相机应用在onActivityResult()中传递的Intent内,将照片编码并作为一个小的位图(Bitmap)在“extras”,在键“data”下。下面的代码将会获得一个图像并在ImageView中显示。
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) { Bundle extras = data.getExtras(); Bitmap imageBitmap = (Bitmap) extras.get("data"); mImageView.setImageBitmap(imageBitmap); } }
Note:
这个来自“data”的缩略图用作图标hi非常好的,但是如果用作更大的图片就不行了。处理全尺寸的图片需要更多操作。
四). 保存全尺寸照片
如果你提供了一个存储文件的地方,Android相机应用就可以存储全尺寸的照片。你必须提供一个完整的文件名来指定相机应用应该把照片保存在哪里。
一般来说,任何用户通过相机拍摄的照片都应该存储在设备的公共外部存储区域,这样他们就能被所有应用访问。一个合适的共享照片存储目录可以通过带有DIRECTORY_PICTURES参数的getExternalStoragePublicDirectory()函数获得。因为由该方法提供的目录是被所有应用所共享的,在目录内读或写分别要READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE的权限声明。写权限隐含了读权限,所以如果你需要写入外部目录,那么你只需要声明一个权限:
<manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ... </manifest>
然而,如果你希望保持这些照片是你应用私有的,那么你可以使用由getExternalFilesDir()提供的目录。在Android 4.3或更低版本的系统中,写入该目录也需要WRITE_EXTERNAL_STORAGE权限。从Android 4.4以后,这个权限就不在需要了。因为这个目录对其他应用来说是访问不到的,所以你可以通过使用maxSdkVersion字段来表明该权限声明只对低版本有效:
<manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" /> ... </manifest>
Note:
你存储在由getExternalFilesDir()所提供的目录内的文件,将会在用户删除你的应用时一起被删除。
一旦你决定了文件存储的目录,你需要创建一个不容易重名的文件名。你可能也希望在成员变量中存储路径名,以备今后使用。这里是一个方法的例子,它通过时间戳为一个新照片返回一个唯一的文件名:
String mCurrentPhotoPath; private File createImageFile() throws IOException { // Create an image file name String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); String imageFileName = "JPEG_" + timeStamp + "_"; File storageDir = Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES); File image = File.createTempFile( imageFileName, /* prefix */ ".jpg", /* suffix */ storageDir /* directory */ ); // Save a file: path for use with ACTION_VIEW intents mCurrentPhotoPath = "file:" + image.getAbsolutePath(); return image; }
通过这种方法为照片创建了文件,现在你可以像下面这样创建并激活Intent:
static final in REQUEST_TAKE_PHOTO = 1; private void dispatchTakePictureIntent() { 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) { takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile)); startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO); } } }
五). 将照片添加至图库:
当你通过一个intent创建了一个照片,你应该知道照片放置在哪里,因为你指定了它需要存储在哪里。对其他任何应用来说,可能最简单的让你照片可访问的方法就是让它可被系统的媒体提供程序(Media Provider)可被访问。
Note:
如果你把文件存储在了由getExternalFilesDir()提供的路径,那么此时媒体扫描器
下面的方法证明了如何激活系统的媒体扫描器将你的照片添加至照片提供程序的数据库,使它对Android图库应用和其它应用来说是可以访问的。
private void galleryAddPic() { Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); File f = new File(mCurrentPhotoPath); Uri contentUri = Uri.fromFile(f); mediaScanIntent.setData(contentUri); this.sendBroadcast(mediaScanIntent); }
六). 解码缩放图像
管理多个全尺寸图像对于有限的存储空间来说是很复杂的。如果你发现你的应用在显示了几幅图片后就用尽了存储,那么你可以通过将JPEG延展到一个记忆数组里面,它将图片缩放至目标View的尺寸,这样可以大幅减小使用的动态堆内存。下面的代码展示了这一技术:
private void setPic() { // Get the dimensions of the View int targetW = mImageView.getWidth(); int targetH = mImageView.getHeight(); // Get the dimensions of the bitmap BitmapFactory.Options bmOptions = new BitmapFactory.Options(); bmOptions.inJustDecodeBounds = true; BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions); int photoW = bmOptions.outWidth; int photoH = bmOptions.outHeight; // Determine how much to scale down the image int scaleFactor = Math.min(photoW/targetW, photoH/targetH); // Decode the image file into a Bitmap sized to fill the View bmOptions.inJustDecodeBounds = false; bmOptions.inSampleSize = scaleFactor; bmOptions.inPurgeable = true; Bitmap bitmap = BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions); mImageView.setImageBitmap(bitmap); }