Bootstrap

android Camera摄像头-Surface view 预览拍照 并将拍的照片插入到系统图库

由于最近项目需求,需要做一个摄像头预览拍照的功能。写完之后,来写下总结:


1.Android 利用系统Camera来预览拍照,步骤如下:

(1)调用Camera的open()方法打开相机。
(2)调用Camera的getParameters()获取拍照参数,该方法返回一个Cmera.Parameters对象。
(3)调用Camera.Parameters对象对照相的参数进行设置。
(4)调用Camera的setParameters(),并将Camera.Parameters对象作为参数传入,这样就可以对拍照进行参数控制,Android2.3.3以后不用设置。


(5)调用Camerade的startPreview()的方法开始预览取景,在之前需要调用Camera的setPreviewDisplay(SurfaceHolder holder)设置使用哪个SurfaceView来显示取得的图片。
(6)调用Camera的takePicture()方法进行拍照。
(7)程序结束时,要调用Camera的stopPreview()方法停止预览,并且通过Camera.release()来释放资源。


2.预览到的画面是通过SurfaceView进行显示的。然后SurfaceHolder是系统提供的一个用来设置SurfaceView的对象,可以通过SurfaceView对象的getHolder()方法来获得。SurfaceHolder.Callback是Holder用来显示SurfaceView数据的接口,接口有3个方法,分别代表不同的时候。


(1)SurfaceView 被创建的时候

 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)

(2)SurfaceView 改变的时候

 public void surfaceCreated(SurfaceHolder holder)

(3)SurfaceView被销毁的时候

public void surfaceDestroyed(SurfaceHolder holder)


2.1根据以上分析可知其中(1)(2)(3)(4)(5)步可以在SurfaceView created的时候,也就是SurfaceHolder.Callback 接口的回调函数surfaceCreated(SurfaceHolder holder)那里。在设置预览大小和拍照图片大小的时候,如果你的屏幕方向不是固定的话,最好是可以根据屏幕实时的转向来选择不同的长宽,这样才不会出现预览拉伸的情况。还有就是设置预览大小和图片大小的时候,是有限制的,不能随便乱写。

例如以下这些:1920x1080 1280x720 800x480 768x432 720x480 640x480 576x432 480x320 384x288 352x288 320x240 240x160 176x144

@Override
public void surfaceCreated(SurfaceHolder holder) {
    // TODO Auto-generated method stub
    //当surfaceview创建时开启相机
    if(camera == null) {
        camera = Camera.open();
        try {
            //设置参数,开始预览
            Camera.Parameters params = camera.getParameters();
            params.setPictureFormat(PixelFormat.JPEG);//图片格式
            params.setPreviewSize(PREVIEW_WIDTH, PREVIEW_HEIGHT);//预览
            params.setPictureSize(PREVIEW_WIDTH, PREVIEW_HEIGHT);//图片大小
            params.setJpegQuality(100);
            camera.setParameters(params);//将参数设置到我的camera
            camera.setPreviewDisplay(holder);//通过surfaceview显示取景画面
            camera.startPreview();//开始预览
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
2.2 当SurfaceView 改变的时候我们要做的就是重新打开预览(即停止预览,然后又重新打开预览)
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    // TODO Auto-generated method stub
    if (holder.getSurface() == null) {
        return;
    }
    try {
        camera.stopPreview();
    } catch (Exception e) {
        e.printStackTrace();
    }
    try {
        camera.setPreviewDisplay(holder);
        camera.startPreview();
    } catch (Exception e) {
        Log.d(TAG, "Error starting camera preview: " + e.getMessage());
    }
}
2.3为了更好的进行内存管理,让app不至于有过多的内存碎片,因此在SurfaceView销毁的时候,我们应该停止预览,release Camera,然后告诉虚拟机回收不用的对象。

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    // TODO Auto-generated method stub
    //当surfaceview关闭时,关闭预览并释放资源
    camera.stopPreview();
    camera.release();
    camera = null;
    holder = null;
    surface = null;
}
3.权限问题,由于系统的升级,对于一些敏感权限,系统要求你必须动态获取。而写权限和Camera就属于这类敏感权限,因此必须动态获取

private void requestPermission() {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED
            ||ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED ) {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
                Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.CAMERA}, REQUEST_EXTERNAL_STORAGE);
    }
}
权限回调的接口 重写Activity的onRequestPermissionResult()方法即可

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (grantResults.length > 0 && requestCode == REQUEST_EXTERNAL_STORAGE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        Toast.makeText(MainActivity.this, R.string.granted, Toast.LENGTH_LONG).show();
    } else {
        Toast.makeText(MainActivity.this, R.string.nogradnted, Toast.LENGTH_LONG).show();
    }
}

AndroidManifest中静态申请的权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />

4.点击拍照按钮进行拍照 调用camera对象的takePicture方法即可。第三个参数是拍完照的时候的数据放回的接口。

camera.autoFocus(new Camera.AutoFocusCallback() {//自动对焦
    @Override
    public void onAutoFocus(boolean success, Camera camera) {
        // TODO Auto-generated method stub
        if(success) {
            camera.takePicture(null, null, picture_callback);//将拍摄到的照片给自定义的对象
        }
    }
});
在存储照片的时候,为了不影响主线程的流畅性,应该将写入的方法放到子线程中去。带写入完成的时候,插入到系统图库即可。

//创建jpeg图片回调数据对象
Camera.PictureCallback picture_callback = new Camera.PictureCallback() {
    @Override
    public void onPictureTaken(final byte[] data, Camera camera) {
        //将保存图片的放到子线程中去,别影响主线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                    //自定义文件保存路径  以拍摄时间区分命名
                    filepath = "/sdcard/myCamera/"+new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())+ ".jpg";
                    final File file = new File(filepath);
                    if (!file.exists()) {
                        file.getParentFile().mkdir();
                    }
                    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);//将图片压缩的流里面
                    bos.flush();// 刷新此缓冲区的输出流
                    bos.close();// 关闭此输出流并释放与此流有关的所有系统资源
                    bitmap.recycle();//回收bitmap空间
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                //图片插入到系统图库中
                                MediaStore.Images.Media.insertImage(getContentResolver(), filepath, file.getName(), null);
                            } catch (FileNotFoundException e) {
                                e.printStackTrace();
                            }
                            Toast.makeText(MainActivity.this, "照片保存成功" + filepath, Toast.LENGTH_SHORT).show();
                        }
                    });
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }).start();
        camera.stopPreview();//关闭预览 处理数据
        camera.startPreview();//数据处理完后继续开始预览

    }
};
5.切换摄像头,一般手机都是默认有前后摄像头的,

//切换前后摄像头
int cameraCount = 0;
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
cameraCount = Camera.getNumberOfCameras();//得到摄像头的个数
for(int i = 0; i < cameraCount; i++) {
    Camera.getCameraInfo(i, cameraInfo);//得到每一个摄像头的信息
    if(cameraPosition == 1) {
        //现在是后置,变更为前置
        if(cameraInfo.facing  == Camera.CameraInfo.CAMERA_FACING_FRONT) {//代表摄像头的方位,CAMERA_FACING_FRONT前置      CAMERA_FACING_BACK后置
            camera.stopPreview();//停掉原来摄像头的预览
            camera.release();//释放资源
            camera = null;//取消原来摄像头
            camera = Camera.open(i);//打开当前选中的摄像头
            try {
                camera.setPreviewDisplay(holder);//通过surfaceview显示取景画面
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            camera.startPreview();//开始预览
            cameraPosition = 0;
            break;
         }
    } else {
        //现在是前置, 变更为后置
        if(cameraInfo.facing  == Camera.CameraInfo.CAMERA_FACING_BACK) {/
            // /代表摄像头的方位,CAMERA_FACING_FRONT前置 
            //    CAMERA_FACING_BACK后置
            camera.stopPreview();//停掉原来摄像头的预览
            camera.release();//释放资源
            camera = null;//取消原来摄像头
            camera = Camera.open(i);//打开当前选中的摄像头
            try {
                camera.setPreviewDisplay(holder);//通过surfaceview显示取景画面
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            camera.startPreview();//开始预览
            cameraPosition = 1;
            break;
        }
    }
}
6 demo效果图


7完整代码

布局代码:

<?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
        <SurfaceView
            android:layout_centerInParent="true"
            android:id="@+id/cp_surface"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
        <ImageView
            android:layout_alignLeft="@+id/cp_surface"
            android:layout_alignTop="@+id/cp_surface"
            android:id="@+id/iv_back"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:src="@mipmap/back"/>

        <ImageView
            android:layout_alignRight="@+id/cp_surface"
            android:layout_alignTop="@+id/cp_surface"
            android:id="@+id/iv_switch_camera"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:src="@mipmap/swap"/>

        <ImageView
            android:layout_alignBottom="@+id/cp_surface"
            android:layout_centerHorizontal="true"
            android:layout_margin="10dp"
            android:id="@+id/iv_shutter"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/take_photo"/>
    </RelativeLayout>
Activity代码:

import android.Manifest;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback{
    private static final String TAG = "MainActivity";
    private static final int REQUEST_EXTERNAL_STORAGE = 10086;
    private static final int PREVIEW_WIDTH = 1920;
    private static final int PREVIEW_HEIGHT = 1080;
    private ImageView iv_back, iv_switch_camera;//返回和切换前后置摄像头
    private SurfaceView surface;
    private ImageView iv_shutter;//快门
    private SurfaceHolder holder;
    private Camera camera;//声明相机
    private String filepath = "";//照片保存路径
    private int cameraPosition = 1;//0代表前置摄像头,1代表后置摄像头
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);//没有标题
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);//设置全屏
        this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);//拍照过程屏幕一直处于高亮
        //设置手机屏幕朝向,一共有7种
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        //SCREEN_ORIENTATION_BEHIND: 继承Activity堆栈中当前Activity下面的那个Activity的方向
        //SCREEN_ORIENTATION_LANDSCAPE: 横屏(风景照) ,显示时宽度大于高度
        //SCREEN_ORIENTATION_PORTRAIT: 竖屏 (肖像照) , 显示时高度大于宽度
        //SCREEN_ORIENTATION_SENSOR  由重力感应器来决定屏幕的朝向,它取决于用户如何持有设备,当设备被旋转时方向会随之在横屏与竖屏之间变化
        //SCREEN_ORIENTATION_NOSENSOR: 忽略物理感应器——即显示方向与物理感应器无关,不管用户如何旋转设备显示方向都不会随着改变("unspecified"设置除外)
        //SCREEN_ORIENTATION_UNSPECIFIED: 未指定,此为默认值,由Android系统自己选择适当的方向,选择策略视具体设备的配置情况而定,因此不同的设备会有不同的方向选择
        //SCREEN_ORIENTATION_USER: 用户当前的首选方向
        setContentView(R.layout.activity_main);
        initView();
        setListener();
        requestPermission();
    }

    private void initView() {
        iv_back = (ImageView) findViewById(R.id.iv_back);
        iv_switch_camera = (ImageView) findViewById(R.id.iv_switch_camera);
        surface = (SurfaceView) findViewById(R.id.cp_surface);
        iv_shutter = (ImageView) findViewById(R.id.iv_shutter);
        holder = surface.getHolder();//获得句柄
        holder.addCallback(this);//添加回调
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);//surfaceview不维护自己的缓冲区,等待屏幕渲染引擎将内容推送到用户面前
    }

    private void setListener() {
        //设置监听
        iv_back.setOnClickListener(listener);
        iv_switch_camera.setOnClickListener(listener);
        iv_shutter.setOnClickListener(listener);
    }

    private void requestPermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED
                ||ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED ) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.CAMERA}, REQUEST_EXTERNAL_STORAGE);
        }
    }

    //响应点击事件
    View.OnClickListener listener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
            switch (v.getId()) {
                case R.id.iv_back:
                    //返回
                    MainActivity.this.finish();
                    break;
                case R.id.iv_switch_camera:
                    //切换前后摄像头
                    int cameraCount = 0;
                    Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
                    cameraCount = Camera.getNumberOfCameras();//得到摄像头的个数
                    for(int i = 0; i < cameraCount; i++) {
                        Camera.getCameraInfo(i, cameraInfo);//得到每一个摄像头的信息
                        if(cameraPosition == 1) {
                            //现在是后置,变更为前置
                            if(cameraInfo.facing  == Camera.CameraInfo.CAMERA_FACING_FRONT) {//代表摄像头的方位,CAMERA_FACING_FRONT前置      CAMERA_FACING_BACK后置
                                camera.stopPreview();//停掉原来摄像头的预览
                                camera.release();//释放资源
                                camera = null;//取消原来摄像头
                                camera = Camera.open(i);//打开当前选中的摄像头
                                try {
                                    camera.setPreviewDisplay(holder);//通过surfaceview显示取景画面
                                } catch (IOException e) {
                                    // TODO Auto-generated catch block
                                    e.printStackTrace();
                                }
                                camera.startPreview();//开始预览
                                cameraPosition = 0;
                                break;
                             }
                        } else {
                            //现在是前置, 变更为后置
                            if(cameraInfo.facing  == Camera.CameraInfo.CAMERA_FACING_BACK) {//代表摄像头的方位,CAMERA_FACING_FRONT前置      CAMERA_FACING_BACK后置
                                camera.stopPreview();//停掉原来摄像头的预览
                                camera.release();//释放资源
                                camera = null;//取消原来摄像头
                                camera = Camera.open(i);//打开当前选中的摄像头
                                try {
                                    camera.setPreviewDisplay(holder);//通过surfaceview显示取景画面
                                } catch (IOException e) {
                                    // TODO Auto-generated catch block
                                    e.printStackTrace();
                                }
                                camera.startPreview();//开始预览
                                cameraPosition = 1;
                                break;
                            }
                        }
                    }
                    break;
                case R.id.iv_shutter:
                    //快门
                    camera.autoFocus(new Camera.AutoFocusCallback() {//自动对焦
                        @Override
                        public void onAutoFocus(boolean success, Camera camera) {
                            // TODO Auto-generated method stub
                            if(success) {
                                camera.takePicture(null, null, picture_callback);//将拍摄到的照片给自定义的对象
                            }
                        }
                    });
                    break;
            }
        }
    };

    /*surfaceHolder他是系统提供的一个用来设置surfaceView的一个对象,而它通过surfaceView.getHolder()这个方法来获得。
     Camera提供一个setPreviewDisplay(SurfaceHolder)的方法来连接*/

    //SurfaceHolder.Callback,这是个holder用来显示surfaceView 数据的接口,他必须实现以下3个方法
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        // TODO Auto-generated method stub
        if (holder.getSurface() == null) {
            return;
        }
        try {
            camera.stopPreview();
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            camera.setPreviewDisplay(holder);
            camera.startPreview();
        } catch (Exception e) {
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // TODO Auto-generated method stub
        //当surfaceview创建时开启相机
        if(camera == null) {
            camera = Camera.open();
            try {
                //设置参数,开始预览
                Camera.Parameters params = camera.getParameters();
                params.setPictureFormat(PixelFormat.JPEG);//图片格式
                params.setPreviewSize(PREVIEW_WIDTH, PREVIEW_HEIGHT);//预览
                params.setPictureSize(PREVIEW_WIDTH, PREVIEW_HEIGHT);//图片大小
                params.setJpegQuality(100);
                camera.setParameters(params);//将参数设置到我的camera
                camera.setPreviewDisplay(holder);//通过surfaceview显示取景画面
                camera.startPreview();//开始预览
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // TODO Auto-generated method stub
        //当surfaceview关闭时,关闭预览并释放资源
        camera.stopPreview();
        camera.release();
        camera = null;
        holder = null;
        surface = null;
    }

    //创建jpeg图片回调数据对象
    Camera.PictureCallback picture_callback = new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(final byte[] data, Camera camera) {
            //将保存图片的放到子线程中去,别影响主线程
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                        //自定义文件保存路径  以拍摄时间区分命名
                        filepath = "/sdcard/myCamera/"+new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())+ ".jpg";
                        final File file = new File(filepath);
                        if (!file.exists()) {
                            file.getParentFile().mkdir();
                        }
                        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
                        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);//将图片压缩的流里面
                        bos.flush();// 刷新此缓冲区的输出流
                        bos.close();// 关闭此输出流并释放与此流有关的所有系统资源
                        bitmap.recycle();//回收bitmap空间
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    //图片插入到系统图库中
                                    MediaStore.Images.Media.insertImage(getContentResolver(), filepath, file.getName(), null);
                                } catch (FileNotFoundException e) {
                                    e.printStackTrace();
                                }
                                Toast.makeText(MainActivity.this, "照片保存成功" + filepath, Toast.LENGTH_SHORT).show();
                            }
                        });
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }).start();
            camera.stopPreview();//关闭预览 处理数据
            camera.startPreview();//数据处理完后继续开始预览

        }
    };

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (grantResults.length > 0 && requestCode == REQUEST_EXTERNAL_STORAGE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(MainActivity.this, R.string.granted, Toast.LENGTH_LONG).show();
        } else {
            Toast.makeText(MainActivity.this, R.string.nogradnted, Toast.LENGTH_LONG).show();
        }
    }

}
参考链接:http://blog.csdn.net/gf771115/article/details/19438409

以上就是摄像头预览拍照的所有介绍。希望对你有所帮助。也感谢其他博主的分享

;