获取照片
这个课时将演示如何通过相机程序来拍摄照片。
有时候您的程序需要通过设备的摄像头来拍摄照片。比如发个图片微博来记录自己见到的事情 等等。这样拍照就是您程序中的一个小小的功能,对于这种需求您无需大刀阔斧的重新开发一个相机程序,只要使用设备上的相机程序即可完成您的需求。
添加摄像头权限
如果您的程序非常依赖拍照这个功能,那么您可能希望只让那些带有摄像头的设备能通过Android Market(电子市场)来安装您的程序。要实现这个需求,您需要在程序的manifest文件中添加一个 <uses-feature>
标签:
1
2
3
4
|
<
manifest
... >
<
uses-feature
android:name
=
"android.hardware.camera"
/>
...
</
manifest
... >
|
如果您的程序对摄像头的依赖不是必须的,没有摄像头也能使用,比如QQ没有摄像头也可以聊天。这样的话就在上面的标签中添加一个属性 android:required="false"
。这样的话没有摄像头的设备也可以通过电子市场安装您的程序。然后您就需要在程序中通过 hasSystemFeature(PackageManager.FEATURE_CAMERA)
来检测设备是否具有摄像头。如果没有就把摄像头相关的功能禁用。
使用相机程序拍照
在Android里面请求其他程序来完成您的操作是通过 Intent
来完成的。拍照这个任务需要分三个步骤:描述拍照功能的 Intent
、一个发起该功能的 Activity
、和处理接收到的照片的代码。
下面的函数用来启动相机来拍照。
1
2
3
4
|
private
void
dispatchTakePictureIntent(
int
actionCode) {
Intent takePictureIntent =
new
Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(takePictureIntent, actionCode);
}
|
这样通过几行代码您的程序就可以通过相机来获取图片了!当然了, 如果这个设备没有程序能处理这个Intent,您的程序就会失败咯。下面是一个用来检测设备上是否有程序能处理这个Intent的代码:
1
2
3
4
5
6
7
|
public
static
boolean
isIntentAvailable(Context context, String action) {
final
PackageManager packageManager = context.getPackageManager();
final
Intent intent =
new
Intent(action);
List<ResolveInfo> list =
packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
return
list.size() >
0
;
}
|
浏览照片
如果只是获取到图片您还不过瘾, 还可以对该图片做一些操作。
在onActivityResult()
函数中的 Intent
参数包含了 Android 相机程序返回的图片数据,使用"data"
作为键值来获取这个图片的 Bitmap
对象。下面的代码演示了如何获取这个图片并且在一个 ImageView
中来显示图片。
1
2
3
4
5
|
private
void
handleSmallCameraPhoto(Intent intent) {
Bundle extras = intent.getExtras();
mImageBitmap = (Bitmap) extras.get(
"data"
);
mImageView.setImageBitmap(mImageBitmap);
}
|
注意: 上面从"data"
得到的缩略图适合用作一个图标,如果想获取一个分辨率大的图片,还需要额外的工作。
保存图片
如果您提供了一个文件地址给Android相机程序,那么相机程序会把拍摄的大分辨率照片保存到这个文件中。您需要提供一个文件路径,包含存储的位置、存储的目录以及文件名。
在 Android 2.2 (API level 8)以后的版本中有个简易的方法来获取这个图片路径:
1
2
3
4
5
6
|
storageDir =
new
File(
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES
),
getAlbumName()
);
|
对于之前的版本您需要自己提供这个保存文件的路径信息:
1
2
3
4
5
|
storageDir =
new
File (
Environment.getExternalStorageDirectory()
+ PICTURES_DIR
+ getAlbumName()
);
|
注意:这里的变量 PICTURES_DIR
只是一个字符串 Pictures/
,标准的可分享图片目录位于外部存储空间的分享目录中。
设置文件名字
就如上面所示,一个图片的保存目录是和设备环境相关的。您需要做的就是选择一个用做图片名称的模式,这样您保存图片的名称就是同一风格的,下面是一个示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private
File createImageFile()
throws
IOException {
// Create an image file name
String timeStamp =
new
SimpleDateFormat(
"yyyyMMdd_HHmmss"
).format(
new
Date());
String imageFileName = JPEG_FILE_PREFIX + timeStamp +
"_"
;
File image = File.createTempFile(
imageFileName,
JPEG_FILE_SUFFIX,
getAlbumDir()
);
mCurrentPhotoPath = image.getAbsolutePath();
return
image;
}
|
在 Intent 中设置文件名字
选择好了保存图片的路径和名字就可以通过Intent
来告诉相机程序。
1
2
|
File f = createImageFile();
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f));
|
添加图片到图库
当您通过Intent来创建一个图片的时候,您应该知道这个图片被保存到哪里了,毕竟是您告诉相机程序把图片保存到哪里的。对于用户来说,访问您的图片的最简单的方式就是通过系统提供的 Media Provider。图库程序会把Media Provider中的内容显示给用户。
下面的代码演示了如何使用系统的多媒体扫描器来添加图片到Media Provider的数据库中,这样Android系统的图库程序就可以显示您保存的图片了。
1
2
3
4
5
6
7
|
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);
}
|
解析一个缩放过的图片
当同时管理多个大尺寸的图片的时候会消耗很多内存,如果设备的内存不够大的话可能就会出现问题。如果您发现在显示几个图片后您的程序出现了out of memory 异常,您可以把通过相机获取到的图片缩小为和需要显示图片的窗口一样大小,这样可以节省很多内存。下面是一个示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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);
}
|
添加摄像头权限
如果您的程序非常依赖录像这个功能,那么您可能希望只让那些带有摄像头的设备能通过Android Market(电子市场)来安装您的程序。要实现这个需求,您需要在程序的manifest文件中添加一个 href="http://developer.android.com/guide/topics/manifest/uses-feature-element.html"> <uses-feature>
标签:
1
2
3
4
|
<
manifest
... >
<
uses-feature
android:name
=
"android.hardware.camera"
/>
...
</
manifest
... >
|
如果您的程序对摄像头的依赖不是必须的,没有摄像头也能使用,比如QQ没有摄像头也可以聊天。这样的话就在上面的标签中添加一个属性 android:required="false"
。这样的话没有摄像头的设备也可以通过电子市场安装您的程序。然后您就需要在程序中通过 hasSystemFeature(PackageManager.FEATURE_CAMERA)
来检测设备是否具有摄像头。如果没有就把摄像头相关的功能禁用。
使用相机程序录制视频
在Android里面请求其他程序来完成您的操作是通过 Intent
来完成的。录制视频这个任务需要分三个步骤:描述录像功能的 Intent
、一个发起该功能的 Activity
、和处理接收到的录像数据代码。
下面的函数用来启动相机来录制视频。
1
2
3
4
|
private
void
dispatchTakeVideoIntent() {
Intent takeVideoIntent =
new
Intent(MediaStore.ACTION_VIDEO_CAPTURE);
startActivityForResult(takeVideoIntent, ACTION_TAKE_VIDEO);
}
|
在调用前面的函数之前,先检测下是否有第三方程序可以处理您的Intent是个不错的选择:
1
2
3
4
5
6
7
8
|
public
static
boolean
isIntentAvailable(Context context, String action) {
final
PackageManager packageManager = context.getPackageManager();
final
Intent intent =
new
Intent(action);
List<ResolveInfo> list =
packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
return
list.size() >
0
;
}
|
查看视频
和拍照一样,在返回的Intent中可以获取相机程序保存视频的Uri
对象,这里面包含了视频保存的位置。下面的代码把视频获取出来并显示在 VideoView
中。
1
2
3
4
|
private
void
handleCameraVideo(Intent intent) {
mVideoUri = intent.getData();
mVideoView.setVideoURI(mVideoUri);
}
|
打开摄像头
在控制摄像头之前,需要先获取到 Camera
对象。和Android自带的相机程序一样,推荐在另外一个线程中打开摄像头。 打开摄像头可能需要消耗一些时间,放到非UI线程中就不会出现ANR了。一般可以在onResume()
函数中启动打开摄像头的线程。
如果摄像头正在被其他程序使用,那么调用 Camera.open()
会抛出一个异常,所以要用try
来处理这个异常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
private
boolean
safeCameraOpen(
int
id) {
boolean
qOpened =
false
;
try
{
releaseCameraAndPreview();
mCamera = Camera.open(id);
qOpened = (mCamera !=
null
);
}
catch
(Exception e) {
Log.e(getString(R.string.app_name),
"failed to open Camera"
);
e.printStackTrace();
}
return
qOpened;
}
private
void
releaseCameraAndPreview() {
mPreview.setCamera(
null
);
if
(mCamera !=
null
) {
mCamera.release();
mCamera =
null
;
}
}
|
从 API level 9 开始,框架支持多个摄像头了。如果使用旧的API, 不设置任何参数调用函数 open()
将会打开第一个背面的摄像头。
创建 Camera Preview
拍照程序通常在用户按下快门之前显示一个预览窗口。要实现这个预览窗口需要使用 SurfaceView
对象。
Preview Class
要显示一个预览图像就需要一个预览的类。该类需要实现 android.view.SurfaceHolder.Callback
接口,该接口用来把摄像头获取的数据传递给预览窗口显示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class
Preview
extends
ViewGroup
implements
SurfaceHolder.Callback {
SurfaceView mSurfaceView;
SurfaceHolder mHolder;
Preview(Context context) {
super
(context);
mSurfaceView =
new
SurfaceView(context);
addView(mSurfaceView);
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = mSurfaceView.getHolder();
mHolder.addCallback(
this
);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
...
}
|
在显示预览图像之前,需要把这个预览类设置到 Camera
对象中,下面会介绍如何设置。
设置预览类开始显示预览窗口
摄像头对象和预览类必须按照特定的顺序来创建,要首先创建摄像头对象。下面的代码片段中,初始化摄像头的工作封装起来了,用户每次修改了摄像头的设置,然后调用setCamera()
函数时都会调用函数Camera.startPreview()
。每次在预览类的surfaceChanged()
回调函数中都需要重新启动预览。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public
void
setCamera(Camera camera) {
if
(mCamera == camera) {
return
; }
stopPreviewAndFreeCamera();
mCamera = camera;
if
(mCamera !=
null
) {
List<Size> localSizes = mCamera.getParameters().getSupportedPreviewSizes();
mSupportedPreviewSizes = localSizes;
requestLayout();
try
{
mCamera.setPreviewDisplay(mHolder);
}
catch
(IOException e) {
e.printStackTrace();
}
/*
Important: Call startPreview() to start updating the preview surface. Preview must
be started before you can take a picture.
*/
mCamera.startPreview();
}
}
|
修改摄像头配置信息
通过配置可以修改摄像头的一些参数,比如聚焦和曝光补偿等。下面的示例代码只是修改了预览的大小,更多详细控制请参考相机程序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public
void
surfaceChanged(SurfaceHolder holder,
int
format,
int
w,
int
h) {
// Now that the size is known, set up the camera parameters and begin
// the preview.
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
requestLayout();
mCamera.setParameters(parameters);
/*
Important: Call startPreview() to start updating the preview surface. Preview must be
started before you can take a picture.
*/
mCamera.startPreview();
}
|
设置预览窗口的方向
大多数的相机程序都使用横向拍照,这也是摄像头传感器的自然方向。但是这并不影响您在竖屏的时候拍照,设备的方向信息会存储到图片的EXIF信息中。可以通过函数 setCameraDisplayOrientation()
来改变预览的显示方向而不影响图片的保存数据。然而,在API level 14之前的版本中,在修改预览方向之前需要先停止预览窗口然后重新启动预览。
拍摄照片
在预览启动以后就可以通过函数 Camera.takePicture()
来拍摄照片了。可以创建 Camera.PictureCallback
和Camera.ShutterCallback
对象作为参数来调用函数Camera.takePicture()
。
如果您想连续不断的拍照,可以创建一个实现 Camera.PreviewCallback
接口的类并且实现onPreviewFrame()
函数。 For
something in between, you can capture only selected preview frames, or set up a
delayed action to call takePicture()
.
重启预览窗口
在拍摄一张照片后,需要重启预览来能继续拍摄下一张。下面的示例在处理快门事件的时候重启了预览。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@Override
public
void
onClick(View v) {
switch
(mPreviewState) {
case
K_STATE_FROZEN:
mCamera.startPreview();
mPreviewState = K_STATE_PREVIEW;
break
;
default
:
mCamera.takePicture(
null
, rawCallback,
null
);
mPreviewState = K_STATE_BUSY;
}
// switch
shutterBtnConfig();
}
|
停止预览并释放资源
一旦使用完了摄像头就可以清理资源了。为了不引起其他程序崩溃,您必需要释放 Camera
对象。
何时停止预览并释放摄像头呢? 当预览surface被销毁的时候去释放资源是个不错的地方,下面是在 Preview
类中的一个示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public
void
surfaceDestroyed(SurfaceHolder holder) {
// Surface will be destroyed when we return, so stop the preview.
if
(mCamera !=
null
) {
/*
Call stopPreview() to stop updating the preview surface.
*/
mCamera.stopPreview();
}
}
/**
* When this function returns, mCamera will be null.
*/
private void stopPreviewAndFreeCamera() {
if (mCamera != null) {
/*
Call stopPreview() to stop updating the preview surface.
*/
mCamera.stopPreview();
/*
Important: Call release() to release the camera for use by other applications.
Applications should release the camera immediately in onPause() (and re-open() it in
onResume()).
*/
mCamera.release();
mCamera =
null
;
}
}
|
Earlier in the lesson, this procedure was also part of the setCamera()
method, so initializing a camera always begins with stopping the preview.
原文:http://yunfeng.sinaapp.com/?p=323