今天無意當中發現在《Android開發學習之基於ZBar實現掃一掃》中的一部分代碼可以用來以硬體方式實現一個照相機的功能,在《Android開發學習之調用系統相機完成拍照的實現》中我們是以Intent方式調用系統內建的相機來完成拍照的,今天呢,就讓我們來以Camera類為核心來開發自己的相機應用吧。在Android的官方文檔中,以硬體方式定製相機應用的步驟如下:
1. 檢查和訪問Camera
建立代碼來檢查Camera和所申請訪問的存在性;
2. 建立一個預覽類
繼承SurfaceView來建立一個Camera的預覽類,並實現SurfaceHolder介面。這個類用來預覽來自Camera的實施映像。
3. 構建一個預覽布局
一旦有了Camera預覽類,就可以把這個預覽類和你想要的使用者介面控制結合在一起來建立一個視圖布局。
4. 針對採集建立監聽
把監聽器與響應使用者動作(如按下按鈕)的介面控制串連到一起來啟動映像或視頻的採集。
5. 採集和儲存檔案
針對真正採集的圖片或視頻,以及輸出的儲存來編寫代碼。
6. 釋放Camera
使用Camera之後,你的應用程式必須釋放Camera,以便其他應用程式能夠使用。
Camera硬體是一個必須要認真管理的共用資源,因此你的應用程式在使用它時,不能跟其他應用程式發生衝突。下文將討論如何檢查Camera硬體、如何申請對Camera的訪問,如何採集圖片和視頻,以及在應用使用完成後如何釋放Camera。
警告:在應用程式使用完Camera時,要記住通過調用Camera.release()方法來釋放Camera對象。如果你的應用程式沒有正確的釋放Camera,所有的後續的視圖對Camera的訪問,包括你自己的應用程式,都會失敗,並可能到你的或其他的應用程式關閉。
下面我們就來按照上面的步驟來一步步的製作一個屬於自己的相機應用吧!
第一步:檢查和訪問相機
/** 官方建議的安全地訪問網路攝影機的方法 **/public static Camera getCameraInstance(){ Camera c = null; try { c = Camera.open(); } catch (Exception e){ Log.d("TAG", "Error is "+e.getMessage()); } return c;}/** 檢查裝置是否支援網路攝影機 **/private boolean CheckCameraHardware(Context mContext){if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){ // 網路攝影機存在 return true; } else { // 網路攝影機不存在 return false; } }
第二步:建立一個預覽類。這裡我們使用的是官方API中提供的一個基本的預覽類:
package com.android.OpenCamera;import java.io.IOException;import android.annotation.SuppressLint;import android.content.Context;import android.hardware.Camera;import android.util.Log;import android.view.SurfaceHolder;import android.view.SurfaceView;/** 一個基本的相機預覽介面類 **/@SuppressLint("ViewConstructor")public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {/** Camera **/private Camera mCamera;/** SurfaceHolder **/private SurfaceHolder mHolder;/** CamreaPreview建構函式 **/@SuppressWarnings("deprecation")public CameraPreview(Context mContext,Camera mCamera) {super(mContext);this.mCamera=mCamera; // 安裝一個SurfaceHolder.Callback, // 這樣建立和銷毀底層surface時能夠獲得通知。 mHolder = getHolder(); mHolder.addCallback(this); // 已到期的設定,但版本低於3.0的Android還需要 mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); }@Overridepublic void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {}@Overridepublic void surfaceCreated(SurfaceHolder holder) { try {mCamera.setPreviewDisplay(holder);mCamera.startPreview();mCamera.setDisplayOrientation(90); } catch (IOException e) {Log.d("TAG", "Error is "+e.getMessage()); }}@Overridepublic void surfaceDestroyed(SurfaceHolder arg0) {// 如果預覽無法更改或旋轉,注意此處的事件 // 確保在縮放或重排時停止預覽 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 is " + e.getMessage()); } }}
第三步:構建預覽布局。這裡我們使用FrameLayout來載入第二步建立的預覽類,使用一個按鈕進行拍照並完成儲存,使用一個ImageView顯示拍照的縮圖,當我們點擊這個縮圖時時,系統將會調用相應的程式來開啟這個圖片。
<frameLayout android:id="@+id/PreviewView" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="0.8" > </frameLayout>
第四步:針對採集監聽。其中PictureCallback介面用於處理拍照的結果,這裡我們做三件事情,第一,儲存圖片;第二,顯示縮圖;第三、儲存當前圖片的Uri,以便於系統的訪問。ShutterCallback介面用於處理按下快門的事件,這裡我們使用MediaPlayer類來播放快門按下時的音效。
/** 拍照回調介面 **/private PictureCallback mPicureCallback=new PictureCallback(){@Overridepublic void onPictureTaken(byte[] mData, Camera camera) {File mPictureFile = StorageHelper.getOutputFile(StorageHelper.MEDIA_TYPE_IMAGE); if (mPictureFile == null){ return; } try { /** 儲存照片 **/ FileOutputStream fos = new FileOutputStream(mPictureFile); fos.write(mData); fos.close(); /** 設定縮圖 **/ Bitmap mBitmap=BitmapFactory.decodeByteArray(mData, 0, mData.length); ThumbsView.setImageBitmap(mBitmap); /** 擷取縮圖Uri **/ mUri=StorageHelper.getOutputUri(mPictureFile); /**停止預覽**/ mCamera.stopPreview(); /**開始預覽**/ mCamera.startPreview(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) {e.printStackTrace();}}};
/** 快門回調介面 **/ private ShutterCallback mShutterCallback=new ShutterCallback() {@Overridepublic void onShutter() {mPlayer=new MediaPlayer();mPlayer=MediaPlayer.create(MainActivity.this,R.raw.shutter);try {mPlayer.prepare();} catch (IllegalStateException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}mPlayer.start();} };
第五步:採集與儲存。採集與儲存在第四步已經給出了,這裡給出一個用於儲存檔案的輔助類StorageHelper類:
package com.android.OpenCamera;import java.io.File;import java.text.SimpleDateFormat;import java.util.Date;import android.annotation.SuppressLint;import android.net.Uri;import android.os.Environment;@SuppressLint("SimpleDateFormat")public final class StorageHelper { public static final int MEDIA_TYPE_IMAGE=0; public static final int MEDIA_TYPE_VIDEO=1; public static Uri getOutputUri(File mFile) { return Uri.fromFile(mFile); } public static File getOutputFile(int mType) { File mMediaFileDir=new File(Environment.getExternalStorageDirectory(),"OpenCamera"); if(!mMediaFileDir.exists()) { if(!mMediaFileDir.mkdir()){ return null;} } File mMediaFile=null; /** 建立檔案名稱 **/ String mFileName=new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); switch(mType) { case MEDIA_TYPE_IMAGE: mMediaFile=new File(mMediaFileDir.getPath()+File.separator+"IMG_"+mFileName+".jpg"); break; case MEDIA_TYPE_VIDEO: mMediaFile=new File(mMediaFileDir.getPath()+File.separator+"VID_"+mFileName+".mp4"); break; default: mMediaFile=null; } return mMediaFile; }}
第六步:相機的釋放
if (mCamera != null){ mCamera.release(); mCamera = null; }
通過以上步驟,我們就完成了一個簡單的相機,最後給出主要的邏輯代碼:
package com.android.OpenCamera;import java.io.File;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.hardware.Camera;import android.hardware.Camera.AutoFocusCallback;import android.hardware.Camera.PictureCallback;import android.hardware.Camera.ShutterCallback;import android.media.MediaPlayer;import android.net.Uri;import android.os.Bundle;import android.app.Activity;import android.content.Context;import android.content.Intent;import android.content.pm.PackageManager;import android.util.Log;import android.view.Menu;import android.view.View;import android.view.View.OnClickListener;import android.view.Window;import android.view.WindowManager;import android.widget.Button;import android.widget.FrameLayout;import android.widget.ImageView;import android.widget.Toast;public class MainActivity extends Activity {/** 相機 **/private Camera mCamera; /** 預覽介面 **/private CameraPreview mPreview;/** 縮圖 **/ImageView ThumbsView;/** 當前縮圖Uri **/private Uri mUri;/** MediaPlayer **/private MediaPlayer mPlayer;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);/** 隱藏標題列 **/requestWindowFeature(Window.FEATURE_NO_TITLE);/** 隱藏狀態列 **/ getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN); /** 禁用鎖屏,因為再次喚醒螢幕程式就會終止,暫時不知道怎麼解決 **/ getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);setContentView(R.layout.activity_main);/** 硬體檢查 **/if(CheckCameraHardware(this)==false){Toast.makeText(this, "很抱歉,您的裝置可能不支援網路攝影機功能!", Toast.LENGTH_SHORT).show();return;}/** 擷取相機 **/mCamera=getCameraInstance();/** 擷取預覽介面 **/ mPreview = new CameraPreview(this, mCamera); FrameLayout mFrameLayout = (FrameLayout)findViewById(R.id.PreviewView); mFrameLayout.addView(mPreview); mCamera.startPreview(); /** 拍照按鈕 **/ Button BtnCapture = (Button) findViewById(R.id.BtnCapture); BtnCapture.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {/** 使用takePicture()方法完成拍照 **/mCamera.autoFocus(new AutoFocusCallback(){/** 自動聚焦聚焦後完成拍照 **/@Overridepublic void onAutoFocus(boolean isSuccess, Camera camera) {if(isSuccess&&camera!=null){mCamera.takePicture(mShutterCallback, null, mPicureCallback);}}});} }); /** 相機縮圖 **/ ThumbsView = (ImageView)findViewById(R.id.ThumbsView); ThumbsView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {/** 使用Uri訪問當前縮圖 **/Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(mUri, "image/*"); startActivity(intent);} });}/** 快門回調介面 **/ private ShutterCallback mShutterCallback=new ShutterCallback() {@Overridepublic void onShutter() {mPlayer=new MediaPlayer();mPlayer=MediaPlayer.create(MainActivity.this,R.raw.shutter);try {mPlayer.prepare();} catch (IllegalStateException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}mPlayer.start();} }; /** 拍照回調介面 **/private PictureCallback mPicureCallback=new PictureCallback(){@Overridepublic void onPictureTaken(byte[] mData, Camera camera) {File mPictureFile = StorageHelper.getOutputFile(StorageHelper.MEDIA_TYPE_IMAGE); if (mPictureFile == null){ return; } try { /** 儲存照片 **/ FileOutputStream fos = new FileOutputStream(mPictureFile); fos.write(mData); fos.close(); /** 設定縮圖 **/ Bitmap mBitmap=BitmapFactory.decodeByteArray(mData, 0, mData.length); ThumbsView.setImageBitmap(mBitmap); /** 擷取縮圖Uri **/ mUri=StorageHelper.getOutputUri(mPictureFile); /**停止預覽**/ mCamera.stopPreview(); /**開始預覽**/ mCamera.startPreview(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) {e.printStackTrace();}}};/** 官方建議的安全地訪問網路攝影機的方法 **/public static Camera getCameraInstance(){ Camera c = null; try { c = Camera.open(); } catch (Exception e){ Log.d("TAG", "Error is "+e.getMessage()); } return c;}/** 檢查裝置是否支援網路攝影機 **/private boolean CheckCameraHardware(Context mContext){if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){ // 網路攝影機存在 return true; } else { // 網路攝影機不存在 return false; } }@Overrideprotected void onPause() {super.onPause();if (mCamera != null){ mCamera.release(); mCamera = null; } }@Overrideprotected void onResume() {super.onResume();if(mCamera == null) { mCamera = getCameraInstance(); mCamera.startPreview(); }}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);return true;}}
這裡一直有一個困惑我的問題,就是當手機鎖屏以後再次喚醒螢幕,程式會立即終止。因為找不到原因,所以目前只能用代碼控制裝置禁止鎖屏來避免這個問題的發生。這個問題和《Android開發學習之基於ZBar實現掃一掃》是一樣的,留給大家去思考了。記得給程式加入以下許可權:
晚上看到Camera360開放了SDK的訊息。主推拍照和濾鏡API,其實開源的濾鏡有很多啦,結合我們今天的例子,我們完全可以做出一個不錯的相機應用來,作為長期受益於開源社區的回報,我決定在整合濾鏡以後把全部的程式開源,希望大家支援我哦。唉,明天要考試了,我竟然還能如此充滿激情的編程,其實這就是編程的魅力啦,在編程的世界裡,我可以完全按照自己的意願去做自己喜歡的,而這就夠了,當老師和同學們都覺得我在抱怨專業課的時候,其實我只是想讓自己的內心感到快樂而已。好了,晚安,各位!