一、前期基础知识储备
在Android应用中想要使用相机拍照功能,通常有两种是实现方式:第一是调用系统中自带的相机应用或者是手机现有第三方相机应用进行拍照;第二种方式是为自己的应用自定义一个相机,实现具有自己应用特色的相机拍照功能。本节文章将详细分析两种调用相机拍照的实现方式。
二、上代码,具体实现调用系统自带相机应用
首先,我们来到官方文档中关于相机Camera API中查看
(注:本篇文章的文档来自Camera API,而非Camera2 API,后者对系统有要求是5.0,而根据友盟数据显示4.0-5.0占比依旧保持在50%左右,所以开发中采用的更多的还是Camera API,要求实现更加高级更加复杂的功能时建议使用Camera2 API):
Using Existing Camera Apps
A quick way to enable taking pictures orvideos in your application without a lot of extra code is to use an Intentto invoke an existing Android camera application. A camera intent makes arequest to capture a picture or video clip through an existing camera app andthen returns control back to your application. The procedure for invoking acamera intent follows these general steps:
①Compose a Camera Intent——MediaStore.ACTION_IMAGE_CAPTURE - Intent action type for requesting an image from an existing cameraapplication.
②Start the Camera Intent - Use the startActivityForResult()method to execute the camera intent
③Receive the Intent Result - Set up an onActivityResult()method in your application to receive the callback and data from the cameraintent.
从上面的官方文档,我们知道,调用系统现有相机的关键三个点,使用Intent调用相机应用;使用
startActivityForResult()方法开启相机应用;使用onActivityResult()接收相机应用拍摄的照片。
public class MainActivity extends AppCompatActivity {
private static int REQUESTCODE_1 = 1;
private static int REQUESTCODE_2 = 2;
//用于显示拍照后图片的imageView
private ImageView mImageView;
//自定义用于存储照片的路径
private String mFilePath;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView = (ImageView) findViewById(R.id.mimageview);
//自定义照片存储的新路径
mFilePath = Environment.getExternalStorageDirectory().getPath();
mFilePath = mFilePath + "/" + "temp.png";
}
public void Camera_1(View view) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//使用startActivityForResult的方式启动而不是startActivity
startActivityForResult(intent, REQUESTCODE_1);
}
public void Camera_2(View view) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
Uri photoUri = Uri.fromFile(new File(mFilePath));
//1. 检查用户是否授权
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.
WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
//2.如果没有授权,那么申请授权
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
} else {
//前面那个常量MediaStore.EXTRA_OUTPUT用于声明我们可以修改图片存储路径,然后再传入一个新的路径
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
}
startActivityForResult(intent, REQUESTCODE_2);
}
@Override
//与startActivityForResult方法配套使用的onActivityResult方法
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RESULT_OK) {
if (resultCode == REQUESTCODE_1) {
//系统默认的照片存储data,拿到的是缩略图
Bundle bundle = data.getExtras();
Bitmap bitmap = (Bitmap) bundle.get("data");
mImageView.setImageBitmap(bitmap);
} else if (resultCode == REQUESTCODE_2) {
FileInputStream fis = null;
try {
//把照片存入自定义的路径后通过流的方式取出,此时得到的是原图
fis = new FileInputStream(mFilePath);
Bitmap bitmap = BitmapFactory.decodeStream(fis);
mImageView.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
上面的代码,简单实现了调用系统自带相机进行拍照,并且将照片显示在自己的应用上,提醒一点,如果想要设置显示原图的同学,一定记得运行时权限的申请,因为“写进内存-WRITE_EXTERNAL_STORAGE”的权限是26个危险权限之一。另外延伸一点,此处实现的只是简单的显示,实际开发中,这么直接拍照的原图的话非常容易造成OOM,所以一定要对拍照的图片用采样率进行压缩—感兴趣的读者可以参考笔者的文章《Bitmap精炼详解第(一)课:Bitmap解析和加载》里面对于采样率的压缩方法分析的较为详细。
三、上代码,具体实现自定义相机

下面开始简单的自定义一个相机,同样的我们来看看官方文档中对于自定义相机的介绍:
①自定义相机时,相关权限的申请:
Before starting development on yourapplication with the Camera API, you should make sure your manifest has theappropriate declarations to allow use of camera hardware and other relatedfeatures.
<uses-permissionandroid:name="android.permission.CAMERA" />
<uses-featureandroid:name="android.hardware.camera" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
②自定义相机的具体步骤:
The general steps for creating a customcamera interface for your application are as follows:
Detectand Access Camera - Create code to check for theexistence of cameras and request access.
Createa Preview Class - Create a camera preview classthat extends SurfaceView and implements the SurfaceHolder interface. This classpreviews the live images from the camera.
Builda Preview Layout - Once you have the camera previewclass, create a view layout that incorporates the preview and the userinterface controls you want.
SetupListeners for Capture - Connect listeners for yourinterface controls to start image or video capture in response to user actions,such as pressing a button.
Captureand Save Files - Setup the code for capturingpictures or videos and saving the output.
Releasethe Camera - After using the camera, yourapplication must properly release it for use by other applications.
从官方文档中,我们可以知道自定义相机时一般必要的申请权限共三项,然后有关键的6个步骤:对于相机硬件的检测、创建一个预览Preview类、创建布局文件、设置拍照监听事件、拍照和储存照片、释放相机资源。下面,我们结合代码具体理解:
1)检测相机硬件:
/** Check if this device has a camera */
private boolean checkCameraHardware(Context context) {
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
// this device has a camera
return true;
} else {
// no camera on this device
return false;
}
}
2)创建相机实例:
/** A safe way to get an instance of the Camera object. */
public static Camera getCameraInstance(){
Camera c = null;
try {
c = Camera.open(); // attempt to get a Camera instance
}
catch (Exception e){
// Camera is not available (in use or does not exist)
}
return c; // returns null if camera is unavailable
}
在相机实例创建的这一步中,我们可以进行自定义相机的一些参数的设置,比如闪光灯、白平衡、照片的大小、前后摄像头等等,实现方式是通过Camera.getParameters()方法拿到Parameters,然后进行一系列相关的设置
3)创建预览Preview类(核心步骤):
/** A basic Camera preview class */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mHolder;
private Camera mCamera;
public CameraPreview(Context context, Camera camera) {
super(context);
mCamera = camera;
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);
// deprecated setting, but required on Android versions prior to 3.0
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void surfaceCreated(SurfaceHolder holder) {
// The Surface has been created, now tell the camera where to draw the preview.
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
Log.d(TAG, "Error setting camera preview: " + e.getMessage());
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
// empty. Take care of releasing the Camera preview in your activity.
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
// If your preview can change or rotate, take care of those events here.
// Make sure to stop the preview before resizing or reformatting it.
if (mHolder.getSurface() == null){
// preview surface does not exist
return;
}
// stop preview before making changes
try {
mCamera.stopPreview();
} catch (Exception e){
// ignore: tried to stop a non-existent preview
}
// set preview size and make any resize, rotate or
// reformatting changes here
// start preview with new settings
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e){
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
}
该Preview类继承自SurfaceView,这一View的双胞胎兄弟,其专门用于处理要求频繁刷新的页面或者请求频繁的页面(SurfaceView内部存在双缓冲机制);并且实现SurfaceHolder.Callback接口,然后重写父类的三个关键方法—surfaceCreated、surfaceChanged、 surfaceDestroyed。
在上述的三个方法内部,完成另外关键的两个方法:
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
通过这两个关键方法的实现,我们就做到了将Camera和Preview进行绑定,完成之后,Preview就可以实现照片的实时预览。
4)创建布局文件,用FrameLayout为Preview占位
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<FrameLayout
android:id="@+id/camera_preview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
/>
<Button
android:id="@+id/button_capture"
android:text="Capture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
/>
</LinearLayout>
public class MainActivity extends Activity {
private Camera mCamera;
private CameraPreview mPreview;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Create an instance of Camera
mCamera = getCameraInstance();
// Create our Preview view and set it as the content of our activity.
mPreview = new CameraPreview(this, mCamera);
FrameLayout preview = (FrameLayout) findViewById(id.camera_preview);
preview.addView(mPreview);
}
}
这里我们创建了一个MainActivity用作我们的主Activity,并在里面执行一些初始化的操作。
5)设置拍照监听事件,拍照的关键方法takePicture()
// Add a listener to the Capture button
Button captureButton = (Button) findViewById(id.button_capture);
captureButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
// get an image from the camera
mCamera.takePicture(null, null, mPicture);
}
}
);
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: " +
e.getMessage());
return;
}
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(data);
fos.close();
} catch (FileNotFoundException e) {
Log.d(TAG, "File not found: " + e.getMessage());
} catch (IOException e) {
Log.d(TAG, "Error accessing file: " + e.getMessage());
}
}
}
其内部,我们要传入三个参数,前面两个均可以设为null,第三个参数PictureCallback尤为关键,在这个参数内部,我们实现写入存储位置并且实现存储逻辑。
6)释放相机资源
public class CameraActivity extends Activity {
private Camera mCamera;
private SurfaceView mPreview;
private MediaRecorder mMediaRecorder;
...
@Override
protected void onPause() {
super.onPause();
releaseMediaRecorder(); // if you are using MediaRecorder, release it first
releaseCamera(); // release the camera immediately on pause event
}
private void releaseCamera(){
if (mCamera != null){
mCamera.release(); // release the camera for other applications
mCamera = null;
}
}
}
这一步很重要,如果处理不慎会造成应用关闭。
通过以上6步就完成了自定义相机的关键步骤。
以下是Activity和Preview的完整代码,做完之后就可以简答实现一个自定义的相机,感兴趣的读者可以参考。
public class MainActivity extends Activity {
private Button button_capture;
private FrameLayout fLayout;
private Camera cm;
private CameraPreview preview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button_capture = findViewById(R.id.button_capture);
fLayout = findViewById(R.id.camera_preview);
cm = getCameraInstance(); // 创建camera的实例
if (cm != null) {
preview = new CameraPreview(this, cm);
fLayout.addView(preview);
}
boolean hasCamera = checkCameraHardware(this);
if (hasCamera) {
Toast.makeText(this, "检测到摄像头", 0).show();
} else {
Toast.makeText(this, "未检测到摄像头", 0).show();
}
button_capture.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//1. 检查用户是否授权
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.
CAMERA) != PackageManager.PERMISSION_GRANTED) {
//2.如果没有授权,那么申请授权
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA}, 1);
} else {
//这段代码是用来设置是否自动对焦,用来包裹整个takePicture()方法
cm.autoFocus(new AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
if (success) {
cm.takePicture(new ShutterCallback() {
@Override
public void onShutter() {
Toast.makeText(getApplicationContext(), "点击快门", 0).show();
}
}, null, new PictureCallback() {
//第三个回调方法,用来设置存储路径,这里我们也要做一次运行时权限的申请
@Override
public void onPictureTaken(byte[] data, Camera camera) {
//1. 检查用户是否授权
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.
WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
//2.如果没有授权,那么申请授权
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}
try {
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), SystemClock.uptimeMillis() + ".jpg");
FileOutputStream fos = new FileOutputStream(file);
fos.write(data);
fos.close();
Toast.makeText(getApplicationContext(), "拍照成功", 0).show();
cm.startPreview();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
});
;
}
}
});
}
/**
* 创建一个摄像头的实例.可以预见的是这段代码中的参数多,相机的功能才强大,那么这里之后肯定会写非常多的判断
*/
public static Camera getCameraInstance() {
Camera c = null;
try {
c = Camera.open(1); // open()不传值或者传0,就是使用后置摄像头,如果传1,那就是使用前置摄像头。
Parameters parameters = c.getParameters();
parameters.setFlashMode(parameters.FLASH_MODE_AUTO);// 设置闪光灯强制打开 FLASH_MODE_TORCH
parameters.setWhiteBalance(Parameters.WHITE_BALANCE_AUTO);// 设置白平衡,WHITE_BALANCE
parameters.setColorEffect(parameters.EFFECT_SEPIA);//设置照片颜色特效,EFFECT
parameters.setPictureSize(1280, 720);//设置拍摄照片的尺寸
parameters.setPreviewSize(1280, 720);//设置照片的预览尺寸
parameters.setJpegQuality(100);//设置照片的质量
// 2.2以后
c.setDisplayOrientation(90); //0 水平 90垂直方向
System.out.println("parameters:" + parameters.flatten());
} catch (Exception e) {
// Camera is not available (in use or does not exist)
}
return c; // returns null if camera is unavailable
}
/**
* 检测手机上是否有摄像头
*/
private boolean checkCameraHardware(Context context) {
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
// this device has a camera
return true;
} else {
// no camera on this device
return false;
}
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
cm.stopPreview();
cm.release();
cm = null;
}
}
/** A basic Camera preview class */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private String TAG="CameraPreview";
private SurfaceHolder mHolder;
private Camera mCamera;
public CameraPreview(Context context, Camera camera) {
super(context);
mCamera = camera;
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);
// deprecated setting, but required on Android versions prior to 3.0
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void surfaceCreated(SurfaceHolder holder) {
// The Surface has been created, now tell the camera where to draw the preview.
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
Log.d(TAG, "Error setting camera preview: " + e.getMessage());
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
// empty. Take care of releasing the Camera preview in your activity.
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
// If your preview can change or rotate, take care of those events here.
// Make sure to stop the preview before resizing or reformatting it.
if (mHolder.getSurface() == null){
// preview surface does not exist
return;
}
// stop preview before making changes
try {
mCamera.stopPreview();
} catch (Exception e){
// ignore: tried to stop a non-existent preview
}
// set preview size and make any resize, rotate or
// reformatting changes here
// start preview with new settings
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e){
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
}
这篇文章写了蛮久的,结束了反而有点舍不得,不知道说点啥好,那就提前祝各位读者青年节快乐吧!
青年 国家 时代