url: http://blog.csdn.net/FromHJ/article/details/11383621
前言
接触Android开发有一段时间了。一开始时纯粹是出于自己的兴趣,空闲时写几个小软件自娱自乐。刚好暑假时老板布置的任务跟Android相关,所以这段时间又继续进行了Android的开发学习。现在的Android开发水平仅属于菜鸟级别,之所以写这系列博客,一来是对这段时间的学习做一些总结,二来是分享,希望能帮助到有需要的人。最近状态很差,干什么都不上劲,也希望能通过动手写这个博客来改变一下自己的状态,迎接后面的挑战。
好了,言归正传。Android要实现拍照功能,有两种方法。一是直接利用Intent调用系统自带的相机进行拍照,这适用于不想自己DIY相机,而仅仅是拍照分享或者拍照后进行处理的场合,简单方便。而是利用Camera相关的API自己从头实现一个相机,这个好处是可以自定义相机的行为,能最大程度满足自己的开发需求。这篇文章主要是介绍怎么实现预览和拍照保存的功能,其实这些基本功能的实现在Android文档中给出了很详细的介绍->http://developer.android.com/guide/topics/media/camera.html。
正文
一、声明权限
为了让手机能够正常使用相机功能,必须在AndroidManifest.xml文件中声明所需的权限。
<span class="tag" style="color: rgb(0, 0, 136);"><uses-permission</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="atn" style="color: rgb(136, 34, 136);">android:name</span><span class="pun" style="color: rgb(102, 102, 0);">=</span><span class="atv" style="color: rgb(0, 136, 0);">"android.permission.CAMERA"</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="tag" style="color: rgb(0, 0, 136);">/></span>另外,还需要加入写入存储卡的权限,否则无法保存图片。整个AndroidManifest.xml文件的内容如下:
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.supercamera"
- android:versionCode="1"
- android:versionName="1.0" >
- <uses-sdk
- android:minSdkVersion="8"
- android:targetSdkVersion="17" />
- <uses-permission android:name="android.permission.CAMERA" />
- <uses-feature android:name="android.hardware.camera" />
- <uses-feature android:name="android.hardware.camera" android:required="false" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- <application
- android:allowBackup="true"
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name"
- android:theme="@style/AppTheme" >
- <activity
- android:name="com.example.supercamera.MainActivity"
- android:label="@string/app_name" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
- </manifest>
二、检测并打开相机
在打开相机之前,先利用PackageManager.hasSystemFeature()方法检查相机是否可用。然后用 Camera.open()方法获取相机,注意一定要捕捉异常。Camera.open()方法还有一种重载形式Camera.open(int),参数为相机的id,这样就可以通过指定id来获取前摄像头或者后摄像头。
- // 检查设备是否提供摄像头
- private boolean checkCameraHardware(Context context) {
- if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
- // 摄像头存在
- return true;
- } else {
- // 摄像头不存在
- return false;
- }
- }
- // 安全获取Camera对象实例的方法*/
- public static Camera getCameraInstance(){
- Camera c = null;
- try {
- c = Camera.open(); // 试图获取Camera实例
- }
- catch (Exception e){
- // 摄像头不可用(正被占用或不存在)
- }
- return c; // 不可用则返回null
- }
三、预览
为了呈现要拍摄的内容,必须创建预览窗口,实时显示摄像头获取的内容。摄像头预览类需要继承 SurfaceView
类并且实现 SurfaceHolder.Callback接口,如下面的代码所示:
- // 基本的摄像头预览类
- public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
- private SurfaceHolder mHolder;
- private Camera mCamera;
- public CameraPreview(Context context, Camera camera) {
- super(context);
- mCamera = camera;
- // 安装一个SurfaceHolder.Callback,
- // 这样创建和销毁底层surface时能够获得通知。
- mHolder = getHolder();
- mHolder.addCallback(this);
- // 已过期的设置,但版本低于3.0的Android还需要
- mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
- }
- public void startPreview()
- {
- mCamera.startPreview();
- }
- public void surfaceCreated(SurfaceHolder holder) {
- // surface已被创建,现在把预览画面的位置通知摄像头
- try {
- mCamera.setPreviewDisplay(holder);
- mCamera.startPreview();
- } catch (IOException e) {
- Log.d(TAG, "Error setting camera preview: " + e.getMessage());
- }
- }
- public void surfaceDestroyed(SurfaceHolder holder) {
- // 空代码。注意在activity中释放摄像头预览对象
- }
- public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
- // 如果预览无法更改或旋转,注意此处的事件
- // 确保在缩放或重排时停止预览
- if (mHolder.getSurface() == null){
- // 预览surface不存在
- return;
- }
- // 更改时停止预览
- try {
- mCamera.stopPreview();
- } catch (Exception e){
- // 忽略:试图停止不存在的预览
- }
- // 在此进行缩放、旋转和重新组织格式
- // 以新的设置启动预
- try {
- mCamera.setPreviewDisplay(mHolder);
- mCamera.setDisplayOrientation(90);
- mCamera.startPreview();
- } catch (Exception e){
- Log.d(TAG, "Error starting camera preview: " + e.getMessage());
- }
- }
- public void setCamera(Camera camera)
- {
- try {
- mCamera = camera;
- camera.setPreviewDisplay(mHolder);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
布局的xml文件如下所示:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical" >
- <FrameLayout
- android:id="@+id/camera_preview"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1" >
- </FrameLayout>
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="#7f7f7f" >
- <Button
- android:id="@+id/button_capture"
- android:layout_width="98dp"
- android:layout_height="96dp"
- android:layout_alignParentTop="true"
- android:layout_centerHorizontal="true"
- android:background="@drawable/button_capture"
- android:maxHeight="32dp"
- android:minHeight="24dip"
- android:minWidth="24dip"
- android:width="32dp" />
- <ImageButton
- android:id="@+id/imgBtnOpenPic"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_centerVertical="true"
- android:layout_marginRight="28dp"
- android:layout_toLeftOf="@+id/button_capture"
- android:adjustViewBounds="false"
- android:background="#0000"
- android:maxHeight="48dp"
- android:maxWidth="48dp"
- android:minHeight="32dp"
- android:minWidth="32dp"
- android:scaleType="centerCrop"
- android:src="@drawable/gallery_pic" />
- </RelativeLayout>
- </LinearLayout>
其中,camera_preview是一个FrameLayout布局,把CameraPreview添加到这个布局中即可。
- // 创建Preview view并将其设为activity中的内容
- mPreview = new CameraPreview(this, mCamera);
- FrameLayout preview = (FrameLayout) findViewById(id.camera_preview);
- preview.addView(mPreview);
在预览时,可能会出现画面旋转了90度的情况,这时候camera.setDisplayOrientation(90);来设置一下角度即可。
调用void takePicture (Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg)方法即可实现拍照功能,其中shutter为拍照瞬间的回调函数,在该函数中可以用来播放快门声等,raw为原始图像数据的回调函数,jpeg为Jpeg格式数据的回调函数。要保存图像,重载 Camera.PictureCallback jpeg即可。
- private PictureCallback mPicture = new PictureCallback() {
- @Override
- public void onPictureTaken(byte[] data, Camera camera) {
- File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
- if (pictureFile == null){
- Log.d(TAG, "Error creating media file, check storage permissions: ");
- return;
- }
- try {
- FileOutputStream fos = new FileOutputStream(pictureFile);
- fos.write(data);
- fos.close();
- mPreview.startPreview();
- // 更新上一张图片路径,更新ImgBtnOpenPic缩略图
- lastPicPath = pictureFile.getAbsolutePath();
- updateOpenPicImgBtn(lastPicPath);
- } catch (FileNotFoundException e) {
- Log.d(TAG, "File not found: " + e.getMessage());
- } catch (IOException e) {
- Log.d(TAG, "Error accessing file: " + e.getMessage());
- }
- }
- };
在保存图像时,可能会出现保存的图像被旋转了90度的情况,这里可以利用以下方法解决:
- // 设置摄像头参数
- protected void setCameraParams(Camera camera)
- {
- camera.setDisplayOrientation(90);
- Camera.Parameters params = camera.getParameters();
- params.setRotation(90);
- camera.setParameters(params);
- }
五、打开图像
要实现这个功能,首先添加一个Button,点击这个Button后,通过图库打开上一次拍摄的照片,这就需要每次拍照时保存上一张图片的文件路径lastPicPath。给该按钮添加监听事件:
- public class OpenPictureListener implements View.OnClickListener
- {
- @Override
- public void onClick(View v) {
- File file = new File(lastPicPath);
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setDataAndType(Uri.fromFile(file), "image/*");
- startActivity(intent);
- }
- };
另一个很重要的问题是更新缩略图,这能让用户在打开图像之前能够大致知道图像是什么样子的。所以实际上打开图像按钮使用的imageButton类型。
- // 更新打开图像按钮的缩略图
- public void updateOpenPicImgBtn(String path)
- {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true; //表明只获取图像大小
- Bitmap bm = BitmapFactory.decodeFile(path, options); //由于inJustDecodeBounds为true,此时bm为null
- options.inSampleSize = options.outWidth/64;
- options.inJustDecodeBounds = false;
- bm = BitmapFactory.decodeFile(path, options);
- ImageButton openPicBtn = (ImageButton)findViewById(id.imgBtnOpenPic);
- openPicBtn.setImageBitmap(bm);
- }
还有一个需要注意的问题是,当程序第一次启动的时候,lastPicPath还没被赋予任何有意义的值。这样的话,就无法查看以前拍的照片了。这时候有两种解决思路,一是把lastPicPath保存到本地,然后程序启动的时候读取这个值,刷新缩略图;而是遍历保存目录,找出拍摄时间最晚的图像文件。这里采用的是第二种思路。
- // 读取保存目录中最新的图像文件
- String getLastCaptureFile()
- {
- File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
- Environment.DIRECTORY_PICTURES), "SuperCamera");
- if(mediaStorageDir.exists() == false)
- {
- return "";
- }
- File [] fs = mediaStorageDir.listFiles();
- if(fs.length <= 0)
- return "";
- Arrays.sort(fs, new MainActivity.CompratorByLastModified());
- return fs[fs.length-1].getPath();
- }
- // 排序器,按修改时间从新到旧排序
- static class CompratorByLastModified implements Comparator<File>
- {
- public int compare(File f1, File f2)
- {
- long diff = f1.lastModified()-f2.lastModified();
- if(diff>0)
- return 1;
- else if(diff==0)
- return 0;
- else
- return -1;
- }
- public boolean equals(Object obj)
- {
- return true;
- }
- }
注:在测试时发现了一个问题,系统并不会及时把所有包含图像的文件添加到图库中,我保存的目录是sdcard/Pictures/SuperCamera,但是在图库中有时候能看到这个文件夹,有时候看不到。当图库不包括这个目录的时候,用上述方法打开图像后,不能通过左右滑动查看上一张或者下一张图像。这个问题挺困惑的,但是没有找出原因和解决方法。
六、Pause and resume
在安卓程序中,这是一个很重要的问题。因为在使用你的程序时,用户随时可能切换到其他应用中,这时候如果程序不做相应处理,就会影响其他应用的使用(比如摄像头),或者导致程序崩溃。
- @Override
- protected void onResume() {
- super.onResume();
- if(mCamera == null)
- {
- mCamera = getCameraInstance();
- setCameraParams(mCamera);
- mPreview.setCamera(mCamera);
- mCamera.startPreview();
- }
- }<pre name="code" class="java"> </pre><pre name="code" class="java"> @Override
- protected void onPause() {
- super.onPause();
- releaseCamera();
- }</pre> private void releaseCamera(){ <br><span style="white-space:pre"> </span>if (mCamera != null){ <br><span style="white-space:pre"> </span> mCamera.release(); // 为其它应用释放摄像头<br><span style="white-space:pre"> </span> mCamera = null; <br><span style="white-space:pre"> </span> } <br><span style="white-space:pre"> </span>} }
参考
http://developer.android.com/guide/topics/media/camera.html
http://www.cnblogs.com/over140/archive/2011/11/16/2251344.html
注:在写程序的时候还参考了其他的博客,但是当时并没有一一记录下来。若本文的内容有侵犯您的利益,请跟我联系,我会更正。
附:源代码下载链接http://download.csdn.net/detail/fromhj/6321399