使用Camera2实现预览功能

前言

首先需要知道的是该博客只是简单的将摄像头打开并进行预览的一个操作,对于横屏竖屏切换的一个判断处理并没有实现,后续会进行完善,但是不会在这个博客中进行说明。

其次在编写之前应该对整个预览过程用到的一些重要方法或重要的类有一个理解!

技术前瞻

1. CameraManager

摄像头管理器,用于打开和关闭系统摄像头

  • getCameraIdList()
    返回当前设备中可用的相机列表

  • getCameraCharacteristics(String cameraId)
    根据摄像头id返回该摄像头的相关信息

  • openCamera(String cameraId, final CameraDevice.StateCallback callback,Handler handler)
    打开指定cameraId的相机。参数callback为相机打开时的回调,参数handler为callback被调用时所在的线程

2. CameraDevice

描述系统摄像头,一般都是用于创建Capture请求以及创建CaptureSession会话

  • createCaptureRequest(int templateType)
    创建一个新的Capture请求。参数templateType代表了请求类型,请求类型一共分为六种,分别为:
  1. TEMPLATE_PREVIEW : 创建预览的请求
  2. TEMPLATE_STILL_CAPTURE: 创建一个适合于静态图像捕获的请求,图像质量优先于帧速率
  3. TEMPLATE_RECORD : 创建视频录制的请求
  4. TEMPLATE_VIDEO_SNAPSHOT : 创建视视频录制时截屏的请求
  5. TEMPLATE_ZERO_SHUTTER_LAG : 创建一个适用于零快门延迟的请求。在不影响预览帧率的情况下最大化图像质量
  6. TEMPLATE_MANUAL : 创建一个基本捕获请求,这种请求中所有的自动控制都是禁用的(自动曝光,自动白平衡、自动焦点)
  • createCaptureSession(List<Surface> outputs,CameraCaptureSession.StateCallback callback,Handler handler)
    创建CaptureSession会话。第一个参数 outputs 是一个 List 数组,相机会把捕捉到的图片数据传递给该参数中的 Surface 。第二个参数 StateCallback 是创建会话的状态回调。第三个参数描述了 StateCallback 被调用时所在的线程。

3. CameraCharacteristics

描述摄像头的各种特性,类似于Camera1中的CamerInfo。通过CameraManager的getCameraCharacteristics(String cameraId)方法来获取

  • get(Key<T> key)
    通过制定的key获取相应的相机参数。

常用的key值有:

  1. CameraCharacteristics.LENS_FACING :
    获取摄像头方向。前置摄像头(LENS_FACING_FRONT)或 后置摄像头(LENS_FACING_BACK)
  2. CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL:
    获取当前设备支持的相机特性
  3. CameraCharacteristics.SENSOR_ORIENTATION:
    获取摄像头方向
  4. CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP:
    获取StreamConfigurationMap,它是管理摄像头支持的所有输出格式和尺寸
  5. CameraCharacteristics.FLASH_INFO_AVAILABLE:
    是否支持闪光灯
  6. CameraCharacteristics.STATISTICS_INFO_MAX_FACE_COUNT:
    同时检测到人脸的数量
  7. CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES:
    相机支持的人脸检测模式

4. CaptureRequest

描述了一次操作请求,拍照、预览等操作都需要先传入CaptureRequest参数,具体的参数控制也是通过CameraRequest的成员变量来设置

  • addTarget(Surface outputTarget)
    给此次请求添加一个Surface对象作为图像的输出目标

  • set(Key<T> key, T value)
    设置指定的参数值。

// 自动对焦
captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
// 闪光灯
captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)
// 根据摄像头方向对保存的照片进行旋转,使其为"自然方向"
captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, mCameraSensorOrientation)
// 人脸检测模式
captureRequestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, CameraCharacteristics.STATISTICS_FACE_DETECT_MODE_SIMPLE)

5. CameraCaptureSession

当需要拍照、预览等功能时,需要先创建该类的实例,然后通过该实例里的方法进行控制(例如:拍照 capture())

  • setRepeatingRequest(CaptureRequest request,
    CaptureCallback listener, Handler handler)

    根据传入的 CaptureRequest 对象开始一个无限循环的捕捉图像的请求。第二个参数 listener 为捕捉图像的回调,在回调中可以拿到捕捉到的图像信息

  • capture( CaptureRequest request,
    CaptureCallback listener, Handler handler)

    拍照。第二个参数为拍照的结果回调

6. CaptureResult

描述拍照完成后的结果

7. ImageReader

用于接收拍照结果和访问拍摄照片的图像数据。
得到一个ImageReader对象的方法为newInstance(int width, int height, int format, int maxImages)。前两个参数是保存图片的宽高,第三个参数为保存图片的格式,第四个参数代表用户可以同时访问到的最大图片数量

注意:
这个参数应该根据具体需业务需求尽可能的小,因为它的数值越大意味着需要消耗的内存就越高

  • acquireNextImage()
    得到ImageReader图像队列中的下一张图片,返回值是一个Image对象

8. Image

一个完整的图片缓存

  • getPlanes()
    获取该图像的像素平面数组。这个数组的大小跟图片的格式有关,如 JPEG格式数组大小为1

9. Plane

图像数据的单色平面

  • getBuffer()
    获取包含帧数据的ByteBuffer。通过这个ByteBuffer我们就可以把图片保存下来

知识前瞻部分的内容,看了一篇帖子写的挺好,就搬运过来,这一部分的顺序很大程度的就是映射着开发中使用他们的顺序。

作者:Smashing丶
链接:https://www.jianshu.com/p/0ea5e201260f
来源:简书

代码实现

 本次demo实现的是使用viewpage2进行左右滑动切换拍照与录像功能,对于布局文件及布局代码在这里不是重点,我就把它给码下来不进行详细的讲解。

MainActivity布局文件

<?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"
    tools:context=".MainActivity">
    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/camerapage"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>


</RelativeLayout>

MainActivity的Java文件

package com.yjs.cameraapplication;

import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.viewpager.widget.ViewPager;
import androidx.viewpager2.widget.ViewPager2;

import android.Manifest;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.view.TextureView;
import android.view.View;
import android.widget.Adapter;

import com.yjs.cameraapplication.FragmentPackage.RecorderVideoFragment;
import com.yjs.cameraapplication.FragmentPackage.TakePhotoFragment;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private String[] permissions={Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.RECORD_AUDIO,Manifest.permission.CAMERA};
    private ViewPager2 viewPager;
    private List<Fragment> fragments;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        /*修改各种状态栏状态*/
        changeWindowStatus();
        /*初始化各种view*/
        initView();
    }

    private void initView() {
        fragments=new ArrayList<Fragment>();
        viewPager = findViewById(R.id.camerapage);
        TakePhotoFragment takePhotoFragment = new TakePhotoFragment();
        fragments.add(takePhotoFragment);
        RecorderVideoFragment recorderVideoFragment = new RecorderVideoFragment();
        fragments.add(recorderVideoFragment);
        MyViewAdapter myViewAdapter = new MyViewAdapter(getSupportFragmentManager(), getLifecycle(), fragments);
        viewPager.setAdapter(myViewAdapter);
    }

    private void changeWindowStatus() {
        //隐藏通知栏状态栏
        if (Build.VERSION.SDK_INT >= 21) {
            View decorView=getWindow().getDecorView();//获取当前界面的decorView
            int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    |View.SYSTEM_UI_FLAG_FULLSCREEN//隐藏状态栏
                    |View.SYSTEM_UI_FLAG_LAYOUT_STABLE//保持整个View的稳定,使其不会随着SystemUI的变化而变化;
                    |View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION//让导航栏悬浮在Activity上
                    |View.SYSTEM_UI_FLAG_HIDE_NAVIGATION//隐藏导航栏
                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;//沉浸模式且状态栏和导航栏出现片刻后会自动隐藏
            decorView.setSystemUiVisibility(option);
            getWindow().setStatusBarColor(Color.TRANSPARENT);//设置透明颜色
            getWindow().setNavigationBarColor(Color.TRANSPARENT);
        }
        ActionBar actionBar=getSupportActionBar();
        actionBar.hide();
    }
}

使用PageView需要使用AdaptView对每一页进行一个管理 

MyViewAdapter文件

package com.yjs.cameraapplication;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.lifecycle.Lifecycle;
import androidx.viewpager2.adapter.FragmentStateAdapter;

import java.util.List;

public class MyViewAdapter extends FragmentStateAdapter {
    private List<Fragment> fragments;
    public MyViewAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle, List<Fragment> fragmentList) {
        super(fragmentManager, lifecycle);
        this.fragments=fragmentList;
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        return fragments.get(position);
    }

    @Override
    public int getItemCount() {
        return fragments.size();
    }
}

其实对于上面都是基本操作没有什么好说的,但是有一个点可以注意一下,就是对于手机状态栏、功能栏的一些设置。 我在上面的注释中也是表明的比较明白。接下来就是我们核心功能实现代码。

拍照界面Fragment

我们将着重看向拍照界面的预览。首先一个相机预览的流程大概就是初始化TextureView,然后在TextureView的回调中进行设置相机特性,设置完相机特性后将设备相机设备进行打开操作。具体的实现细节将结合代码进行理解。

首先还是最简单的Fragment的布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".FragmentPackage.TakePhotoFragment">

    <!-- TODO: Update blank fragment layout -->
    <TextureView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/textureView"/>

    <ImageButton
        android:id="@+id/takePicture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="50dp"
        android:background="@drawable/shape_white_ring"
        android:scaleX="0.4"
        android:scaleY="0.4"
        android:src="@drawable/cam" />
    <ImageView
        android:id="@+id/image_show"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_marginLeft="50dp"
        android:layout_marginBottom="50dp" />
    <ImageButton
        android:id="@+id/change"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/changecam"
        android:scaleY="0.2"
        android:scaleX="0.2"
        android:background="@drawable/shape_white_ring"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="50dp"
        android:layout_marginBottom="50dp"
        android:layout_alignParentRight="true"
        />

</RelativeLayout>

 实现预览主要还是对TextureView控件的一个操作。我们首先需要在fragment创建View的回调函数中进行控件的绑定,动态授权的操作,以及初始化TextureView

@Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_take_photo, container, false);
        //控件绑定
        initView(view);
        //初始化TextureView
        textureView.setSurfaceTextureListener(textureListener);
        //动态授权
        getPermission();

        return view;
    }

控件的绑定、动态授权我这边就不在具体的去讲。下面我将动态授权的代码码出来。

private void getPermission() {
        Log.d(TAG, "getPermission: success");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            for (String permission : permissions) {
                if (ContextCompat.checkSelfPermission(getContext(), permission) != PackageManager.PERMISSION_GRANTED) {
                    permissionList.add(permission);
                }
            }
            if (!permissionList.isEmpty()) {
                //进行授权
                requestPermissions(permissionList.toArray(new String[permissionList.size()]), 1);
            } else {
                //表示全部已经授权
                //这时候回调一个预览view的回调函数
                textureView.setSurfaceTextureListener(textureListener);
            }
        }
    }

接下来我们来看看TextureView的初始化代码运行。

 //初始化TextureView
 textureView.setSurfaceTextureListener(textureListener);

 初始化其实就是设置一个SurfaceTexture的监听者,当其可用的时候进行回调函数

/*SurfaceView状态回调*/
    TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            Log.d(TAG, "onSurfaceTextureAvailable: success");
            //首先就需要设置相机,然后再打开相机
            setupCamera(width,height);
            openCamera();
        }
        //下面的方法可以先不看,我们先实现相机预览
        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
            return false;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surface) {

        }
    };

可以看到回调函数中其实就做了两件事, 在SufaceTexture可用的时候,设置相机的特性,设置完之后便打开相机相关操作,首先我们先看一下setupCamera方法进行设置相机

private void setupCamera(int width,int height) {
        Log.d(TAG, "setupCamera: success");
        CameraManager cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
        try {
            String[] cameraIdList = cameraManager.getCameraIdList();
            for (String cameraId : cameraIdList) {
                CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);
                if (cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {
                    continue;
                }
                StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                //相机支持的所有分辨率,下一步就是获取最合适的分辨率
                Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class);
                Size size = getOptimalSize(outputSizes, width, height);
                previewSize = size;
                mCameraId = cameraId;
                break;
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
//选择sizeMap中大于并且接近width和height的size
    private Size getOptimalSize(Size[] outputSizes, int width, int height) {
        Size tempSize=new Size(width,height);
        List<Size> sizes = new ArrayList<>();
        for (Size outputSize : outputSizes) {
            if (width > height) {
                //横屏的时候
                if (outputSize.getHeight() > height && outputSize.getWidth() > width) {
                    sizes.add(outputSize);
                }
            } else {
                //竖屏的时候
                if (outputSize.getWidth() > height && outputSize.getHeight() > width) {
                    sizes.add(outputSize);
                }
            }
        }
        if (sizes.size() > 0) {
            //如果有多个符合条件找到一个差距最小的,最接近预览分辨率的
             tempSize= sizes.get(0);
            int minnum = 999999;
            for (Size size : sizes) {
                int num = size.getHeight() * size.getHeight() - width * height;
                if (num < minnum) {
                    minnum = num;
                    tempSize = size;
                }
            }
        }
        return tempSize;
        /*if (sizes.size() > 0) {
            return Collections.min(sizes, new Comparator<Size>() {
                @Override
                public int compare(Size size, Size t1) {
                    return Long.signum(size.getWidth() * size.getHeight() - t1.getWidth() * t1.getHeight());
                }
            });
        }
        return outputSizes[0];*/

    }

看着上面的设置相机部分代码,我们可以知道, 我们通过getActivity().getSystemService(Context.CAMERA_SERVICE)去获得CameraManager去对相机特性进行设置。其中cameraCharacteristics是获取相机的所有特性,例如分辨率、相机前后摄像头等特性的设置。设置相机代码主要设置了后摄的最佳分辨率的一个选择初始化。

 设置好相机之后,我们便可以进行打开相机的操作,在打开相机操作的过程中再初始化一下ImageReader为后面拍照保存图片做好准备。

//打开摄像头
    private void openCamera() {
        Log.d(TAG, "openCamera: success");
        CameraManager cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
        try {
            if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                // TODO: Consider calling
                //    ActivityCompat#requestPermissions
                // here to request the missing permissions, and then overriding
                //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
                //                                          int[] grantResults)
                // to handle the case where the user grants the permission. See the documentation
                // for ActivityCompat#requestPermissions for more details.
                return;
            }
            cameraManager.openCamera(mCameraId, stateCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

其实打开相机函数,就是通过CameraManager进行openCamera方法,这个方法有三个参数去进行完善,首先第一个参数是相机的Id,因为一个设备上可能有多个相机设备,刚刚的设置相机中已经提及,第二个参数则是监控相机设备状态的回调,第三个参数是设置该方法在哪个线程进行操作,如果是null则代表是在当前线程下进行opencamera。接下来我们来看一下第二个参数监控相机设备的回调函数。

//摄像头状态回调
    private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            Log.d(TAG, "onOpened: success");
            mCameraDevice = camera;
            //开启预览
            startPreview();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            Toast.makeText(getContext(), "摄像头设备连接失败", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            Toast.makeText(getContext(), "摄像头设备连接出错", Toast.LENGTH_SHORT).show();
        }
    };

当检测到相机设备打开时回调onOpened函数,首先在这时我们可以获得对应的cameradevice对象,再其后开启预览界面startPreview()。

//预览功能
    private void startPreview() {
        Log.d(TAG, "startPreview: success");
        //设置图片阅读器
        setImageReader();
        //注意这里:sufacetexture跟surfaceview是两个东西,需要注意!
        //sufacetexture是textureview的重要属性
        SurfaceTexture surfaceTexture = textureView.getSurfaceTexture();
        //设置textureview的缓存区大小
        surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
        //设置surface进行预览图像数据
        mPreviewSurface = new Surface(surfaceTexture);
        //创建CaptureRequest
        setCaptureRequest();
        //创建capturesession
        /*Surface表示有多个输出流,我们有几个显示载体,就需要几个输出流。
        对于拍照而言,有两个输出流:一个用于预览、一个用于拍照。
        对于录制视频而言,有两个输出流:一个用于预览、一个用于录制视频。*/
        // previewSurface 用于预览, mImageReader.getSurface() 用于拍照
        try {
            mCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    //当回调创建成功就会调用这个回调
                    mCaptureSession = session;
                    setRepeatCapture();
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {

                }
            }, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

    }

在打开预览界面之前,我们先对ImageReader进行一个初始化,为后续拍照做准备。ImageReader主要时实现对图片的保存,以及通知广播对图库数据库进行一个更新。过程还是比较直白,就直接贴一下代码

//设置图片阅读器
    private void setImageReader() {
        Log.d(TAG, "setImageReader: success");
        //创建ImageReader实例,接下来应该是设置一些属性参数
        mImageReader = ImageReader.newInstance(previewSize.getWidth(), previewSize.getHeight(), ImageFormat.JPEG, 1);
        //果然跟我想的一样,接下来是设置监听当图片流可用的时候的监听器,即为拍照之后产生照片流
        mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader reader) {
                Image image = reader.acquireLatestImage();
                //进行保存图片,开一个线程进行保存
                ImageSaver imageSaver = new ImageSaver(getContext(), image);
                new Thread(imageSaver).start();
                Toast.makeText(getContext(), "保存图片成功", Toast.LENGTH_SHORT).show();
            }
        }, null);
    }
//创建一个图片保存类
    public class ImageSaver implements Runnable {
        private Context context;
        private Image image;

        public ImageSaver(Context context, Image image) {
            Log.d(TAG, "ImageSaver: success");
            this.context = context;
            this.image = image;
        }

        @Override
        public void run() {
            Image.Plane[] planes = image.getPlanes();
            ByteBuffer buffer = planes[0].getBuffer();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            System.out.println(planes);
            String filname = Environment.getExternalStorageDirectory() + "/DCIM/Camera/"  + System.currentTimeMillis() + ".jpg";
            File file = new File(filname);
            FileOutputStream fileOutputStream=null;
            try {
                //保存图片
                fileOutputStream = new FileOutputStream(file);
                fileOutputStream.write(bytes,0,bytes.length);

            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            finally {
                if (fileOutputStream!=null){
                    try {
                        fileOutputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                //最后还要广播通知相册更新数据库
                notiBroadcast();
                //保存操作结束后,需要用handle进行主线程数据的更新
                Message message = new Message();
                message.what=0;
                Bundle bundle = new Bundle();
                bundle.putString("path",filname);
                message.setData(bundle);
                handler.sendMessage(message);
                //image也算是个流也需要进行关闭,否则可能拍照的时候会报错
                image.close();
            }
        }
    }
private void notiBroadcast(){
        String path = Environment.getExternalStorageDirectory() + "/DCIM/";
        Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
        Uri uri = Uri.fromFile(new File(path));
        intent.setData(uri);
        getContext().sendBroadcast(intent);
    }
private void setLastImagePath(String path){
        Log.d(TAG, "setLastImagePath: success");
        //拿到最后一张拍照照片
        Bitmap bitmap = BitmapFactory.decodeFile(path);
        mImageView.setImageBitmap(bitmap);
    }
private Handler handler=new Handler(Looper.myLooper()){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what==0){
                Bundle pathdata = msg.getData();
                String path = (String) pathdata.get("path");
                imageList.add(path);
                //设置拍照界面显示的一个图片(左下角的图片预览)
                setLastImagePath(path);
            }
        }
    };

 设置好ImageReader之后,以及对Surface进行设置尺寸大小之后,便需要setCaptureRequest();还没讲解setCaptureRequest()之前,可能会疑问Surface是拿来干什么的,接下来,我把setCaptureRequest()码出来就好理解很多。

//无论是预览还是拍照都需要设置capturerequest
    private void setCaptureRequest(){
        try {
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mPreviewRequestBuilder.addTarget(mPreviewSurface);
            //手动对焦,还不会
            /*
                TODO
             */
            // 自动对焦
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            // 闪光灯
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
            // 人脸检测模式
            mPreviewRequestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, CameraCharacteristics.STATISTICS_FACE_DETECT_MODE_SIMPLE);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

可以看到我们设计的相机怎么知道预览界面的,便是通过 mPreviewRequestBuilder.addTarget(mPreviewSurface)进行一个设置,然后builder还可以对自动对焦、闪光灯等进行一个设置,当builder都设置好之后,便可以调用build进行captureRequire的生成。创建好captureRequire之后我们便需要capturesession去调用预览方法,预览方法中需要captureRequire,其实整个过程都是迭迭相加的。

private void setRepeatCapture(){
        Log.d(TAG, "setRepeatCapture: success");
        mPreviewRequestBuilder.setTag(TAG);
        //首先要知道整个调用顺序 devices创建出capturebuilder,当builder设置好各种参数之后,就可以build出capturerequire
        mPreviewRequest=mPreviewRequestBuilder.build();
        //session中需要用到capturerequire
        try {
            mCaptureSession.setRepeatingRequest(mPreviewRequest, new CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                    super.onCaptureCompleted(session, request, result);
                }
            },null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

 致辞预览就简单实现了,但是我个人文字逻辑并不清晰,估计这个就我自己能看懂,我就把拍照的Fragement的所有代码进行码。大家一步一步慢慢看,总能看懂。

package com.yjs.cameraapplication.FragmentPackage;

import android.Manifest;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.SurfaceTexture;
import android.graphics.drawable.Drawable;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;

import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.util.Size;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.Toast;

import com.yjs.cameraapplication.MainActivity;
import com.yjs.cameraapplication.R;
import com.yjs.cameraapplication.SelfView.MyImageView;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.sql.Time;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * A simple {@link Fragment} subclass.
 * Use the {@link TakePhotoFragment#newInstance} factory method to
 * create an instance of this fragment.
 */
public class TakePhotoFragment extends Fragment implements View.OnClickListener {
    private static final String TAG = "TakePhotoFragment";
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";
    private String mParam1;
    private String mParam2;
    /*fragment空间声明*/
    private TextureView textureView;
    private ImageButton takePhotoBtn;
    private ImageView mImageView;
    /*private MyImageView mImageView;*/
    private ImageButton changeCamBtn;
    /*除此之外,还需要一些参数*/
    private String mCameraId; //摄像头ID
    private Size previewSize; //预览分辨率
    private ImageReader mImageReader; //图片阅读器
    private static CameraDevice mCameraDevice;   //摄像头设备
    private static CameraCaptureSession mCaptureSession;   //获取会话
    private CaptureRequest mPreviewRequest;      //获取预览请求
    private CaptureRequest.Builder mPreviewRequestBuilder;   //获取到预览请求的Builder通过它创建预览请求
    private Surface mPreviewSurface;  //预览显示图
    //新建一个权限链
    private String[] permissions = {Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO};
    private List<String> permissionList = new ArrayList();
    //添加一个图片集合
    List<String> imageList = new ArrayList<>();
    private Boolean isCreated=false;
    private Boolean isLeave=false;


    public TakePhotoFragment() {
        // Required empty public constructor
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param param1 Parameter 1.
     * @param param2 Parameter 2.
     * @return A new instance of fragment TakePhotoFragment.
     */
    // TODO: Rename and change types and number of parameters
    public static TakePhotoFragment newInstance(String param1, String param2) {
        TakePhotoFragment fragment = new TakePhotoFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        Log.d(TAG, "onCreateView: success");
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_take_photo, container, false);
        initView(view);
        textureView.setSurfaceTextureListener(textureListener);
        //动态授权
        getPermission();
        isCreated=true;
        return view;
    }
    //看看viewpage对fragment的生命周期的影响
    @Override
    public void onPause() {
        super.onPause();
        Log.d(TAG, "onPause: success");
        closeCamera();
        System.out.println(textureView.isAvailable());
        if (textureView.isAvailable()){
            //如果可用,就是除了进行切换pageview之外的所有操作
            isLeave=true;
        }
        else {
            //不可用说明就是直接切换了pageview
            closeCamera();
        }

    }

    @Override
    public void onStop() {
        super.onStop();
        Log.d(TAG, "onStop: success");
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        Log.d(TAG, "onDestroyView: success");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy: success");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.d(TAG, "onResume: success");
        System.out.println("++++++++++");
        System.out.println(textureView.isAvailable());
        if (textureView.isAvailable()){
            if (isLeave)
            {
                //息屏等操作会自动关闭camera,所以就得手动再打开一次
                openCamera();
                isLeave=false;
            }
        }

    }

    @Override
    public void onStart() {
        super.onStart();
        Log.d(TAG, "onStart: success");
        /*if (textureView.isAvailable()){

            openCamera();
        }*/
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        Log.d(TAG, "onViewCreated: success");
    }




    private void closeCamera(){
        Log.d(TAG, "closeCamera: success");
        //首先要关闭session
        if (mCaptureSession!=null){
            mCaptureSession.close();
        }
        if (mCameraDevice!=null){
            mCameraDevice.close();
        }
    }

    //绑定控件
    private void initView(View view) {
        textureView = view.findViewById(R.id.textureView);
        takePhotoBtn = view.findViewById(R.id.takePicture);
        mImageView = view.findViewById(R.id.image_show);
        changeCamBtn = view.findViewById(R.id.change);
        changeCamBtn.setOnClickListener(this);
    }

    private void getPermission() {
        Log.d(TAG, "getPermission: success");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            for (String permission : permissions) {
                if (ContextCompat.checkSelfPermission(getContext(), permission) != PackageManager.PERMISSION_GRANTED) {
                    permissionList.add(permission);
                }
            }
            if (!permissionList.isEmpty()) {
                //进行授权
                ActivityCompat.requestPermissions(getActivity(),permissionList.toArray(new String[permissionList.size()]),1);
            } else {
                //表示全部已经授权
                //这时候回调一个预览view的回调函数
                textureView.setSurfaceTextureListener(textureListener);
            }
        }
    }
    //只能写在Activity中,下次把授权写到activity中,减少麻烦
    /*@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        Log.d(TAG, "onRequestPermissionsResult: success");
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode==1){
            if (grantResults.length!=0){
                //表示有权限没有授权
                getPermission();
            }
            else {
                //表示都授权
                openCamera();
            }
        }
    }*/

    /*SurfaceView状态回调*/
    TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            Log.d(TAG, "onSurfaceTextureAvailable: success");
            //首先就需要设置相机,然后再打开相机
            setLastImagePath();
            setupCamera(width,height);
            openCamera();
        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
            return false;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surface) {

        }
    };

    private void setupCamera(int width,int height) {
        Log.d(TAG, "setupCamera: success");
        CameraManager cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
        try {
            String[] cameraIdList = cameraManager.getCameraIdList();
            for (String cameraId : cameraIdList) {
                CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);
                if (cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {
                    continue;
                }
                StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                //相机支持的所有分辨率,下一步就是获取最合适的分辨率
                Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class);
                Size size = getOptimalSize(outputSizes, width, height);
                previewSize = size;
                mCameraId = cameraId;
                break;
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    //打开摄像头
    private void openCamera() {
        Log.d(TAG, "openCamera: success");
        CameraManager cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
        try {
            if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                // TODO: Consider calling
                //    ActivityCompat#requestPermissions
                // here to request the missing permissions, and then overriding
                //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
                //                                          int[] grantResults)
                // to handle the case where the user grants the permission. See the documentation
                // for ActivityCompat#requestPermissions for more details.
                AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
                builder.setMessage("该应用需要相机授权,点击授权按钮跳转到设置进行设置");
                builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        getActivity().finish();
                    }
                });
                builder.setPositiveButton("设置", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS");
                        intent.setData(Uri.fromParts("package", getActivity().getPackageName(), null));
                        startActivity(intent);
                    }
                }).create().show();
                return;
            }
            cameraManager.openCamera(mCameraId, stateCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    //摄像头状态回调
    private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            Log.d(TAG, "onOpened: success");
            mCameraDevice = camera;
            //开启预览
            startPreview();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            Toast.makeText(getContext(), "摄像头设备连接失败", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            Toast.makeText(getContext(), "摄像头设备连接出错", Toast.LENGTH_SHORT).show();
        }
    };


    //预览功能
    private void startPreview() {
        Log.d(TAG, "startPreview: success");
        //设置图片阅读器
        setImageReader();
        //注意这里:sufacetexture跟surfaceview是两个东西,需要注意!
        //sufacetexture是textureview的重要属性
        SurfaceTexture surfaceTexture = textureView.getSurfaceTexture();
        //设置textureview的缓存区大小
        surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
        //设置surface进行预览图像数据
        mPreviewSurface = new Surface(surfaceTexture);
        //创建CaptureRequest
        setCaptureRequest();
        //创建capturesession
        /*Surface表示有多个输出流,我们有几个显示载体,就需要几个输出流。
        对于拍照而言,有两个输出流:一个用于预览、一个用于拍照。
        对于录制视频而言,有两个输出流:一个用于预览、一个用于录制视频。*/
        // previewSurface 用于预览, mImageReader.getSurface() 用于拍照
        try {
            mCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    //当回调创建成功就会调用这个回调
                    mCaptureSession = session;
                    setRepeatCapture();
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {

                }
            }, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

    }

    //设置图片阅读器
    private void setImageReader() {
        Log.d(TAG, "setImageReader: success");
        //创建ImageReader实例,接下来应该是设置一些属性参数
        mImageReader = ImageReader.newInstance(previewSize.getWidth(), previewSize.getHeight(), ImageFormat.JPEG, 1);
        //果然跟我想的一样,接下来是设置监听当图片流可用的时候的监听器,即为拍照之后产生照片流
        mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader reader) {
                Image image = reader.acquireLatestImage();
                //进行保存图片,开一个线程进行保存
                ImageSaver imageSaver = new ImageSaver(getContext(), image);
                new Thread(imageSaver).start();
                Toast.makeText(getContext(), "保存图片成功", Toast.LENGTH_SHORT).show();
            }
        }, null);
    }

    //选择sizeMap中大于并且接近width和height的size
    private Size getOptimalSize(Size[] outputSizes, int width, int height) {
        Size tempSize=new Size(width,height);
        List<Size> sizes = new ArrayList<>();
        for (Size outputSize : outputSizes) {
            if (width > height) {
                //横屏的时候
                if (outputSize.getHeight() > height && outputSize.getWidth() > width) {
                    sizes.add(outputSize);
                }
            } else {
                //竖屏的时候
                if (outputSize.getWidth() > height && outputSize.getHeight() > width) {
                    sizes.add(outputSize);
                }
            }
        }
        if (sizes.size() > 0) {
            //如果有多个符合条件找到一个差距最小的,最接近预览分辨率的
             tempSize= sizes.get(0);
            int minnum = 999999;
            for (Size size : sizes) {
                int num = size.getHeight() * size.getHeight() - width * height;
                if (num < minnum) {
                    minnum = num;
                    tempSize = size;
                }
            }
        }
        return tempSize;
        /*if (sizes.size() > 0) {
            return Collections.min(sizes, new Comparator<Size>() {
                @Override
                public int compare(Size size, Size t1) {
                    return Long.signum(size.getWidth() * size.getHeight() - t1.getWidth() * t1.getHeight());
                }
            });
        }
        return outputSizes[0];*/

    }

    /*Fragment控件点击事件*/
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.change:
                changeLens();
                break;
        }
    }

    private void changeLens(){
        Log.d(TAG, "changeLens: success");
        if (mCameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_BACK))){
            mCameraId=String.valueOf(CameraCharacteristics.LENS_FACING_FRONT);
        }
        else {
            if (mCameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_FRONT))){
                mCameraId=String.valueOf(CameraCharacteristics.LENS_FACING_BACK);
            }
        }
        CameraManager cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
        try {
            CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(mCameraId);
            StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class);
            Size optimalSize = getOptimalSize(outputSizes, textureView.getWidth(), textureView.getHeight());
            previewSize=optimalSize;
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        /*这里不能直接使用关闭camera这个方法*/
        /*closeCamera();*/
        closeCamera();
        openCamera();
    }

    //创建一个图片保存类
    public class ImageSaver implements Runnable {
        private Context context;
        private Image image;

        public ImageSaver(Context context, Image image) {
            Log.d(TAG, "ImageSaver: success");
            this.context = context;
            this.image = image;
        }

        @Override
        public void run() {
            Image.Plane[] planes = image.getPlanes();
            ByteBuffer buffer = planes[0].getBuffer();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            System.out.println(planes);
            String filname = Environment.getExternalStorageDirectory() + "/DCIM/Camera/"  + System.currentTimeMillis() + ".jpg";
            File file = new File(filname);
            FileOutputStream fileOutputStream=null;
            try {
                //保存图片
                fileOutputStream = new FileOutputStream(file);
                fileOutputStream.write(bytes,0,bytes.length);

            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            finally {
                if (fileOutputStream!=null){
                    try {
                        fileOutputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                //最后还要广播通知相册更新数据库
                notiBroadcast();
                //保存操作结束后,需要用handle进行主线程数据的更新
                Message message = new Message();
                message.what=0;
                Bundle bundle = new Bundle();
                bundle.putString("path",filname);
                message.setData(bundle);
                handler.sendMessage(message);
                //image也算是个流也需要进行关闭,否则可能下一次拍照的时候会报错
                image.close();
            }
        }
    }
    private Handler handler=new Handler(Looper.myLooper()){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what==0){
                Bundle pathdata = msg.getData();
                String path = (String) pathdata.get("path");
                imageList.add(path);
                //设置拍照界面显示的一个图片(左下角的图片预览)
                setLastImagePath();
            }
        }
    };

    private void notiBroadcast(){
        String path = Environment.getExternalStorageDirectory() + "/DCIM/";
        Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
        Uri uri = Uri.fromFile(new File(path));
        intent.setData(uri);
        getContext().sendBroadcast(intent);
    }

    private void setLastImagePath(){
        Log.d(TAG, "setLastImagePath: success");
        //先判断一下手机有没有权限
        if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
            return;
        }
        //拿到最后一张拍照照片,遍历所有相册照片
        String DCIMPath=Environment.getExternalStorageDirectory()+"/DCIM/Camera";
        File file = new File(DCIMPath);
        //对该文件夹进行遍历
        String[] jpgs = file.list(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                if (name.contains("jpg")) {
                    return true;
                } else {
                    return false;
                }
            }
        });
        String finalImagePath=Environment.getExternalStorageDirectory()+"/DCIM/Camera/"+jpgs[jpgs.length-1];
        Bitmap bitmap = BitmapFactory.decodeFile(finalImagePath);
        /*Canvas canvas = new Canvas();
        canvas.drawBitmap(bitmap,0,0,new Paint());
        canvas.drawCircle(bitmap.getWidth()/2,bitmap.getHeight()/2,bitmap.getWidth()/2,new Paint());
        mImageView.draw(canvas);*/
        RoundedBitmapDrawable roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(getResources(), bitmap);
        roundedBitmapDrawable.setCircular(true);
        /*mImageView.setImageBitmap(bitmap);*/
        mImageView.setImageDrawable(roundedBitmapDrawable);

    }

    //无论是预览还是拍照都需要设置capturerequest
    private void setCaptureRequest(){
        try {
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mPreviewRequestBuilder.addTarget(mPreviewSurface);
            //手动对焦,还不会
            /*
                TODO
             */
            // 自动对焦
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            // 闪光灯
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
            // 人脸检测模式
            mPreviewRequestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, CameraCharacteristics.STATISTICS_FACE_DETECT_MODE_SIMPLE);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void setRepeatCapture(){
        Log.d(TAG, "setRepeatCapture: success");
        mPreviewRequestBuilder.setTag(TAG);
        //首先要知道整个调用顺序 devices创建出capturebuilder,当builder设置好各种参数之后,就可以build出capturerequire
        mPreviewRequest=mPreviewRequestBuilder.build();
        //session中需要用到capturerequire
        try {
            mCaptureSession.setRepeatingRequest(mPreviewRequest, new CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                    super.onCaptureCompleted(session, request, result);
                }
            },null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
}

后续补充

这是写完这篇文章的一个补充,上面的所有代码示例,我也更新到完善Bug之后的代码。首先说一下这次预览主要遇到的问题:因为我设计的这个demo是搭载在Fragment上,所以出现的问题都是在当Fragment不可见的时候出现的问题,例如当切换ViewPage时,切换回相机预览页面,CameraDevice的回调会调disconnnect方法表示连接出错;以及当息屏时,亮屏预览只显示息屏前一帧;以及当在该Activity跳转到其他Activity时,返回的时候,预览也停留在离开该Activity的前一帧。

对于这几个bug的解决之路也是非常艰辛,一开始的时候就发现了第一个情况的bug,没当回事直接找到onPause调用closeCamera方法,然后再resume的时候再重新打开,但是随着调试的不断进行,这个方法只能将第一种情况的bug解决。后续又尝试了getUserVisibleHint回调方法,但是发现程序并不会回调这个方法。

后来没办法,只能从fragment的生命周期出发,发现viewpage切换的时候,textureview会自动变为不可用,滑动回预览界面的时候,textureview在加载完之后会回调自己的监听函数进行重新打开相机的操作,也就是说我们在onpause的时候记得将camera关闭就行,而对于锁屏或是跳到其他Activity返回的时候,可以发现textureview时一直可用的,也就是说上面两个情况重新回到页面,是不会自动调用textureview的加载完的回调函数,所以我们这时候就可以设置一个标识符,然后在resume函数中进行判断,如果符合这个标识符就进行重新打开相机。(onpause的时候也要关闭camera,否则后续会报错)。具体看代码实现

//回调这个函数的时候,无论什么情况都要关闭相机
//若是已经离开该活动的相关操作,只需要将isLeave置为true就可
@Override
    public void onPause() {
        super.onPause();
        Log.d(TAG, "onPause: success");
        closeCamera();
        System.out.println(textureView.isAvailable());
        if (textureView.isAvailable()){
            //如果可用,就是除了进行切换pageview之外的所有操作
            isLeave=true;
        }
        /*else {
            //不可用说明就是直接切换了pageview
            closeCamera();
        }*/

    }

//这个方法是无论什么操作,回到预览界面都会被回调的,这时候只要稍加区别
//就可以很好的解决预览的各种极端情况下的问题
@Override
    public void onResume() {
        super.onResume();
        Log.d(TAG, "onResume: success");
        System.out.println("++++++++++");
        System.out.println(textureView.isAvailable());
        if (textureView.isAvailable()){
            if (isLeave)
            {
                //息屏等操作会自动关闭camera,所以就得手动再打开一次
                openCamera();
                isLeave=false;
            }
        }

  • 21
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: 使用 camera2 API 可以更加灵活、可定制和高效地完成 Android 相机应用开发,其相比 camera1 API 的性能有大幅提升。 在使用 camera2 API 完成预览和拍照前,需要进行以下几个步骤: 1. 获取 CameraManager 对象,查找可用的摄像头列表,并选择需要打开的摄像头。 2. 创建 CameraCaptureSession 对象,用于处理相机触发器的请求,并连接 CameraDevice 和 Surface。 3. 匹配预览和图片输出的 Surface,设置相应的尺寸和格式。 4. 创建 CaptureRequest 对象,设置相应的参数,如自动对焦模式、曝光模式等。 5. 使用 CameraCaptureSession 进行预览或拍照。 在预览时,可以使用 TextureView 或 SurfaceView 进行实时数据渲染,比如显示相机预览画面、拍照后处理和显示等,同时可以通过设置监听器动态获取相机输出的图像流数据。 在拍照时,需要创建 ImageReader 对象,设置输出数据的格式和尺寸,同时需要建立对应的 Surface,将其传入 CaptureRequest.Builder,设置请求类型并发起拍照请求。通过设置 ImageReader 的 OnImageAvailableListener 接口,即可接收到图片数据,并进行后续处理和保存。 以上是使用 camera2 API 完成预览和拍照的基本流程,实际开发中需要根据具体需求进行优化和调整。 ### 回答2: Android Camera2 API 是 Android 系统中相机功能的一种全新的 API,使用 Camera2 可以更灵活地操作相机设备并获得更高质量的照片。 使用 Camera2 实现预览非常简单,我们只需要实现一个 CameraDevice.StateCallback 接口实现类和一个 SurfaceView 主界面。在 StateCallback 的 onOpened 回调中获得 CameraDevice 的实例,然后通过 ImageReader 创建 SurfaceHolder,最后将 SurfaceHolder 通过 CameraDevice.createCaptureSession 接口跟 CameraDevice 进行绑定即可实现预览。 拍照的实现过程与预览类似,首先获得 CameraDevice 实例,然后创建一个 CaptureRequest.Builder 对象,将拍照设置参数通过 CaptureRequest.Builder.set 方法设置到 CaptureRequest.Builder 对象中,最后通过 CameraCaptureSession.capture 接口启动拍照操作即可。 当然,在使用 Camera2 API 进行操作相机时,还需要注意一些其他问题,比如不同的相机设备有不同的特性,需要针对不同的设备进行优化和适配,还需要保证应用的流畅性和稳定性,以达到更好的用户体验。 总之,使用 Camera2 API 实现预览和拍照是 Android 开发的一个重要技能,需要开发者深入了解该 API 的机制和使用方式,才能更好地实现优秀的相机应用。 ### 回答3: Android中的camera2是一种相机应用程序接口(API),旨在提高相机应用程序的功能和性能。相较于早期版本的camera API,camera2 API提供了更多的控制选项,允许开发者定制相机应用程序的功能,从而实现更好的用户体验。 使用camera2 API实现预览和拍照需要以下步骤: 1. 获取CameraManager对象。使用该对象可以获取系统中可用的相机列表,并在需要的时候打开指定相机。 2. 打开指定相机。调用CameraManager.openCamera()方法打开相机。 3. 创建CaptureSession。CaptureSession是与相机关联的一组输出Surface的集合。 4. 创建CaptureRequest。CaptureRequest是一个指定相机操作和设置的重要对象,可以通过它来设置各种模式、参数和目标Surface。 5. 创建Preview Request。处理预览界面。 6. 启动相机预览。启动前,可以使用CaptureRequest.Builder设置其他预览参数。 7. 拍照。当用户点击拍照按钮时,调用CaptureSession.capture()方法,即可拍照并接收回调。 8. 关闭相机。释放所有占用的资源,以便其他应用程序可以使用相机。 总之,在使用camera2 API实现预览和拍照时,需要使用许多类和方法。但只要开发者掌握了API中的基本概念和流程,就可以自由地使用该API,设计新型的相机应用程序,提供更好的功能和性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值