Android Camera、Camera2使用

Android Camera、Camera2详解

前言

Android5.0之前使用android.hardware包下的Camera类进行拍照、录视频等功能。5.0以后,新增了android.hardware.camera2包,利用新的机制、新的类进行拍照、录视频。

camera使用

摄像头权限自己去AndroidMainfest.xml配置就行了
直接上代码工具类
下面展示一些 代码

package com.tony.sonicinspection.ui.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.WindowManager;

import com.serenegiant.widget.Camera2Helper;
import com.tony.sonicinspection.App;
import com.tony.sonicinspection.Const;
import com.tony.sonicinspection.bean.eventbean.CameraPicPathEventBean;
import com.tony.sonicinspection.utils.ImageUtil;

import org.greenrobot.eventbus.EventBus;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;

/**
 * **************************************************************************************************
 * 修改日期                    功能或Bug描述      作者
 * 2021/08/02               TextureView类目	     ljh
 * **************************************************************************************************
 */
public class MyTextureView extends TextureView implements View.OnLayoutChangeListener {
    public Camera mCamera;
    private Context context;
    private Camera.Parameters param;
    private boolean isCanTakePicture = false;
    int mWidth = 0;
    int mHeight = 0;
    int mDisplayWidth = 0;
    int mDisplayHeight = 0;
    int mPreviewWidth = 540;
    int mPreviewHeight = 399;
    public MyTextureView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        init();
    }

    private void init() {
        if (null == mCamera) {
            mCamera = Camera.open();
        }
        this.setSurfaceTextureListener(new SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
                param = mCamera.getParameters();
                param.setPreviewFpsRange(4,10);
                param.getPictureSize().width = mPreviewWidth;
                param.getPictureSize().height = mPreviewHeight;
//param.setPictureSize(mPreviewWidth,mPreviewHeight);这么写会报错,有想法的可以去查查代码学习学习
                param.setPictureFormat(PixelFormat.JPEG);
                param.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
                param.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);// 1连续对焦
                mCamera.setParameters(param);
                //变形处理
                RectF previewRect = new RectF(0, 0, mPreviewWidth, mPreviewHeight);
                RectF surfaceDimensions = new RectF(0, 0, mPreviewWidth, 960);
                Matrix matrix = new Matrix();
                matrix.setRectToRect(previewRect, surfaceDimensions, Matrix.ScaleToFit.FILL);
                MyTextureView.this.setTransform(matrix);
                //<-处理变形 处理旋转
                int displayRotation = 0;
                WindowManager windowManager = (WindowManager) context
                        .getSystemService(Context.WINDOW_SERVICE);
                int rotation = windowManager.getDefaultDisplay().getRotation();
                switch (rotation) {
                    case Surface.ROTATION_0:
                        displayRotation = 0;
                        break;
                    case Surface.ROTATION_90:
                        displayRotation = 90;
                        break;
                    case Surface.ROTATION_180:
                        displayRotation = 180;
                        break;
                    case Surface.ROTATION_270:
                        displayRotation = 270;
                        break;
                }
                Camera.CameraInfo info = new Camera.CameraInfo();
                Camera.getCameraInfo(0, info);
                //设置方向
                int orientation;
                if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                    orientation = (info.orientation - displayRotation + 360) % 360;
                } else {
                    orientation = (info.orientation + displayRotation) % 360;
                    orientation = (360 - orientation) % 360;
                }
                mCamera.setParameters(param);
                mCamera.setDisplayOrientation(orientation);
                try {
                    mCamera.setPreviewTexture(surfaceTexture);
                    mCamera.startPreview();
                    isCanTakePicture = true;
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }

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

            }

            @Override
            public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
                if (mCamera != null) {
                    mCamera.stopPreview();
                    mCamera.release();
                    mCamera = null;
                    isCanTakePicture = true;
                }
                return true;
            }

            @Override
            public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {

            }
        });
    }

    /**
     * 拍照
     */
    public void take() {
        if (mCamera != null && isCanTakePicture) {
//            isCanTakePicture = false;
            mCamera.takePicture(new Camera.ShutterCallback() {
                @Override
                public void onShutter() {
                }
            }, null, mPictureCallback);
        }
    }

    public void startPreview() {
        if (mCamera != null && !isCanTakePicture) {
            MyTextureView.this.setBackgroundDrawable(null);
            mCamera.startPreview();
            isCanTakePicture = true;
        }
    }

    public void stopPreview() {
        if (mCamera != null) {
            mCamera.stopPreview();
            isCanTakePicture = false;
        }
    }
    public void releaseTextureView(){
        if (mCamera != null) {
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
            isCanTakePicture = true;
        }
    }


    Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            if (mCamera != null) {
                try {
                /**这里是为了展示照片做的处理,可根据自己的需要去做相应操作
                 EventBus.getDefault().post(new CameraPicPathEventBean(data));*/
                    Const.PHOTO_PATH = App.getInstance().generatePhotoPath();
                    File file = new File(Const.PHOTO_PATH);
                    file.createNewFile();
                    FileOutputStream os = new FileOutputStream(file);
                    BufferedOutputStream bos = new BufferedOutputStream(os);
                    Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                    bitmap = ImageUtil.rotaingImageView(90, bitmap);
                    bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos);
                    bos.flush();
                    bos.close();
                    os.close();
//                    MyTextureView.this.setBackgroundDrawable(new BitmapDrawable(bitmap));
                    mCamera.startPreview();
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }
    };

    @Override
    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
        mWidth = right - left;
        mHeight = bottom - top;
    }

}

Activity使用 ,这我是我项目中的一些代码 ,根据自己需要自行处理逻辑

    @BindView(R.id.textureView_system_camera)
    MyTextureView textureViewSystemCamera;
    //开启相机预览
    textureViewSystemCamera.startPreview();
    //关闭相机预览
    textureViewSystemCamera.stopPreview();
   @Subscribe(threadMode = ThreadMode.MAIN)
    public void showPic(CameraPicPathEventBean cameraPicPathEventBean) {
        if (cameraPicPathEventBean != null) {
            showDialog(cameraPicPathEventBean.getData());
        }
    }

    //初始化并弹出对话框方法
    private void showDialog(byte[] data) {
        View view = LayoutInflater.from(getActivity()).inflate(R.layout.popup_capture, null, false);
        final AlertDialog dialog = new AlertDialog.Builder(getActivity()).setView(view).create();
        TextView delete = (TextView) view.findViewById(R.id.tv_delete);
        TextView save = (TextView) view.findViewById(R.id.tv_save);
//        Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
//        bitmap = ImageUtil.rotaingImageView(90, bitmap);//图片旋转
        Glide.with(this)
                .load(data)
                .into((ImageView) view.findViewById(R.id.captureImage));
        delete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                File file = new File(Const.PHOTO_PATH);
                Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
                Uri uri = Uri.fromFile(file);
                intent.setData(uri);
                getActivity().sendBroadcast(intent);
                file.delete();
                mainActivity.showToast(R.string.device_socket_capture_delete_success);
                //... To-do
                dialog.dismiss();
            }
        });

        save.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //... To-do
                  mainActivity.showToast("保存成功:" + Const.PHOTO_PATH);
                  curInspectionData = null;
                  curImageIndex = 0;
                  CommonUtils.updateGallery(mainActivity, Const.PHOTO_PATH);
                  dialog.dismiss();
            }
        });
        dialog.show();
        //此处设置位置窗体大小,我这里设置为了手机屏幕宽度的3/4  注意一定要在show方法调用后再写设置窗口大小的代码,否则不起效果会
    	dialog.getWindow().setLayout((getActivity().getResources().getDisplayMetrics().widthPixels/4*3),LinearLayout.LayoutParams.WRAP_CONTENT);
    }

camera2使用

直接上使用方法


 	@BindView(R.id.surfaceView_system_camera)
    SurfaceView surfaceViewSystemCamera;
    
    private SurfaceHolder mSurfaceHolder;
    private CameraManager cameraManager;
    private String cameraID;//摄像头Id 0 为后  1 为前
    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
    ///为了使照片竖直显示
    static {
        ORIENTATIONS.append(Surface.ROTATION_0, 90);
        ORIENTATIONS.append(Surface.ROTATION_90, 0);
        ORIENTATIONS.append(Surface.ROTATION_180, 270);
        ORIENTATIONS.append(Surface.ROTATION_270, 180);
    }
    private CameraDevice mCameraDevice;//摄像头
    private Handler childHandler, mainHandler;
    private ImageReader mImageReader; //摄像头照片
    private CameraCaptureSession mCameraCaptureSession;//摄像头预览

    /**
     * 初始化摄像头view
     */
    private void initCameraView() {
        mSurfaceHolder = surfaceViewSystemCamera.getHolder();
        mSurfaceHolder.setKeepScreenOn(true);
        mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                initCamera2();
            }

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

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                // 释放Camera资源
                if (null != mCameraDevice) {
                    mCameraDevice.close();
                    mCameraDevice = null;
                }
            }
        });
    }

    /**
     * 初始化摄像头
     */
    private void initCamera2() {
        HandlerThread handlerThread = new HandlerThread("Camera2");
        handlerThread.start();
        childHandler = new Handler(handlerThread.getLooper());
        mainHandler = new Handler(Looper.getMainLooper());
        cameraID = "" + CameraCharacteristics.LENS_FACING_FRONT;
        mImageReader = ImageReader.newInstance(540, 399, ImageFormat.JPEG,1);
        //变形处理
        RectF previewRect = new RectF(0, 0, 540, 399);
        RectF surfaceDimensions = new RectF(0, 0, 540, 960);
        Matrix matrix = new Matrix();
        matrix.setRectToRect(previewRect, surfaceDimensions, Matrix.ScaleToFit.FILL);
        surfaceViewSystemCamera.setAnimationMatrix(matrix);

        mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader reader) {
                try {
                    // 拿到拍照照片数据
                    Image image = reader.acquireNextImage();
//                    Image image = reader.acquireLatestImage();
                    if (image != null) {
                        ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                        byte[] bytes = new byte[buffer.remaining()];
                        buffer.get(bytes);//由缓冲区存入字节数组
                        showDialog(bytes);
                        Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
                        Const.PHOTO_PATH = App.getInstance().generatePhotoPath();
                        File file = new File(Const.PHOTO_PATH);
                        file.createNewFile();
                        FileOutputStream os = new FileOutputStream(file);
                        BufferedOutputStream bos = new BufferedOutputStream(os);
                        bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos);
                        bos.flush();
                        bos.close();
                        os.close();
                        image.close();//如果频繁使用拍照,必须调用此方法要不然会报错
                        //   java.lang.IllegalStateException: maxImages (1) has already been acquired, call #close before acqu
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }


            }
        },mainHandler);
        cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
        if (ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        try {
            cameraManager.openCamera(cameraID, stateCallback, mainHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            mCameraDevice = camera;
            //开启预览
            takePreview();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            if (null != mCameraDevice) {
                mCameraDevice.close();
                mCameraDevice = null;
            }
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            Toast.makeText(getActivity(), "摄像头开启失败", Toast.LENGTH_SHORT).show();
        }
    };

    /**
     * 预览摄像头视频
     */
    private void takePreview() {
        // 创建预览需要的CaptureRequest.Builder
        try {
            final CaptureRequest.Builder previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            // 将SurfaceView的surface作为CaptureRequest.Builder的目标
            previewRequestBuilder.addTarget(mSurfaceHolder.getSurface());
            mCameraDevice.createCaptureSession(Arrays.asList(mSurfaceHolder.getSurface(), mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    if (null == mCameraDevice) return;
                    // 当摄像头已经准备好时,开始显示预览
                    mCameraCaptureSession = session;
                    try {
                        // 自动对焦
                        previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                        // 打开闪光灯
//                        previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                        // 显示预览
                        CaptureRequest previewRequest = previewRequestBuilder.build();
                        mCameraCaptureSession.setRepeatingRequest(previewRequest, null, childHandler);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                    Toast.makeText(getActivity(), "配置失败", Toast.LENGTH_SHORT).show();
                }
            },childHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
    /**
     * 拍照
     */
    private void takePicture() {
        if (mCameraDevice == null) return;
        // 创建拍照需要的CaptureRequest.Builder
        final CaptureRequest.Builder captureRequestBuilder;
        try {
            captureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            // 将imageReader的surface作为CaptureRequest.Builder的目标
            captureRequestBuilder.addTarget(mImageReader.getSurface());
            // 自动对焦
            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);
            // 获取手机方向
            int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
            // 根据设备方向计算设置照片的方向
            captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
            //拍照
            CaptureRequest mCaptureRequest = captureRequestBuilder.build();
            mCameraCaptureSession.capture(mCaptureRequest, null, childHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
	  //关闭
	  if (mCameraDevice != null) {
          mCameraDevice.close();
      }
      //开启
      if (mCameraDevice != null) {
          initCamera2();
      } else {
          initCameraView();//初始化一次就可以了,根据自己需要使用
      }
1.求最佳比例正方形分辨率

较为歪门邪道的方法,核心就是TextureView/SurfaceView的宽高比与摄像头的高宽比做差值比较,注意这里一个是宽高一个是高宽,求出来的结果就是在指定指定比例最接近正方形的分辨率

优点:因为是正方形的分辨率,所以在预览的时候不管是什么尺寸的TextureView/SurfaceView的都能显示的不会变形.所以比较适合在小尺寸TextureView/SurfaceView上

缺点:在预览的时候其实无法完全显示完整(正方形不管怎么样都有可能上下或者左右超出View的大小),所以TextureView/SurfaceView会自动忽略四周部分,只显示最中间的部分.这样拍照的时候就会发现预览与实际照片显示范围不一致.

	 /**
     * 获取匹配的大小 这里是Camera2获取分辨率数组的方式,Camera1获取不同,计算一样
     * @return
     */
    private Size getMatchingSize(){
            Size selectSize = null;
            float selectProportion = 0;
        try {
            float viewProportion = (float)mTextureView.getWidth() / (float)mTextureView.getHeight();//计算View的宽高比
            CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(mCurrentCameraId);
            StreamConfigurationMap streamConfigurationMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            Size[] sizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG);
            for (int i = 0; i < sizes.length; i++){
                Size itemSize = sizes[i];
                float itemSizeProportion = (float)itemSize.getHeight() / (float)itemSize.getWidth();//计算当前分辨率的高宽比
                float differenceProportion = Math.abs(viewProportion - itemSizeProportion);//求绝对值
                Log.e(TAG, "相减差值比例="+differenceProportion );
                if (i == 0){
                    selectSize = itemSize;
                    selectProportion = differenceProportion;
                    continue;
                }
                if (differenceProportion <= selectProportion){ //判断差值是不是比之前的选择的差值更小
                    if (differenceProportion == selectProportion){ //如果差值与之前选择的差值一样
                        if (selectSize.getWidth() + selectSize.getHeight() < itemSize.getWidth() + itemSize.getHeight()){//选择分辨率更大的Size
                            selectSize = itemSize;
                            selectProportion = differenceProportion;
                        }

                    }else { 
                        selectSize = itemSize;
                        selectProportion = differenceProportion;
                    }
                }
            }

        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        Log.e(TAG, "getMatchingSize: 比例="+selectProportion);
        Log.e(TAG, "getMatchingSize: 尺寸,宽="+selectSize.getWidth()+",高="+selectSize.getHeight());
        return selectSize;
    }
2.求最满足宽度的情况下,在找到最接近高度的分辨率

这个是最正常的算法了,核心就是找到与屏幕宽度最接近的分辨率,然后在找最接近屏幕高度的分辨率.这里是屏幕宽度是最高优先级的,其次在满足高度.

优点:预览图像与拍照照片的效果完全一致.

缺点:

只能满足全屏幕预览的拍照情况下,各种奇葩自定义大小的TextureView你基本上不可能找到满足的宽度的分辨率.
  因为需要让TextureView的高度跟随分辨率高度,所以预览的上面或者下面可能会有需要留出空白区域的情况.(可以用黑色背景View填充),全屏预览的时候可以忽略这个情况,因为基本上手机的摄像头都会有一个分辨率刚好与屏幕分辨率一致.但是不排除个别奇葩手机
  在看代码前这里说明一个重要知识!摄像头分辨率的宽度和高度与屏幕分辨率的宽度和高度的对应

1.摄像头分辨率的宽度和高度其实是手机横屏下的才是正确方向.如下图所示

在这里插入图片描述

2.屏幕的分辨率的宽度和高度依然是手机竖屏下的高度和宽度.
在这里插入图片描述

private Size getMatchingSize2(){
        Size selectSize = null;
        try {
            CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(mCurrentCameraId);
            StreamConfigurationMap streamConfigurationMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            Size[] sizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG);
            DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); //因为我这里是将预览铺满屏幕,所以直接获取屏幕分辨率
            int deviceWidth = displayMetrics.widthPixels; //屏幕分辨率宽
            int deviceHeigh = displayMetrics.heightPixels; //屏幕分辨率高
            Log.e(TAG, "getMatchingSize2: 屏幕密度宽度="+deviceWidth);
            Log.e(TAG, "getMatchingSize2: 屏幕密度高度="+deviceHeigh );
            /**
             * 循环40次,让宽度范围从最小逐步增加,找到最符合屏幕宽度的分辨率,
             * 你要是不放心那就增加循环,肯定会找到一个分辨率,不会出现此方法返回一个null的Size的情况
             * ,但是循环越大后获取的分辨率就越不匹配
             */
            for (int j = 1; j < 41; j++) {
                for (int i = 0; i < sizes.length; i++) { //遍历所有Size
                    Size itemSize = sizes[i];
                    Log.e(TAG,"当前itemSize 宽="+itemSize.getWidth()+"高="+itemSize.getHeight());
                    //判断当前Size高度小于屏幕宽度+j*5  &&  判断当前Size高度大于屏幕宽度-j*5
                    if (itemSize.getHeight() < (deviceWidth + j*5) && itemSize.getHeight() > (deviceWidth - j*5)) {
                        if (selectSize != null){ //如果之前已经找到一个匹配的宽度
                            if (Math.abs(deviceHeigh-itemSize.getWidth()) < Math.abs(deviceHeigh - selectSize.getWidth())){ //求绝对值算出最接近设备高度的尺寸
                                selectSize = itemSize;
                                continue;
                            }
                        }else {
                            selectSize = itemSize;
                        }

                    }
                }
                /**
                 * 因为需要满足宽度,所以原则上如果没有必要,首次找到最接近宽度屏幕的摄像头高度后就跳出循环
                 */
                if(selectSize != null){             Log.e(TAG, "getMatchingSize2: 选择的分辨率宽度="+selectSize.getWidth());              Log.e(TAG, "getMatchingSize2: 选择的分辨率高度="+selectSize.getHeight());
                    return selectSize;
                }
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        Log.e(TAG, "宽="+selectSize.getWidth());
        Log.e(TAG, "高="+selectSize.getHeight());
        return selectSize;
    }

暂时就这么多,具体camera2原理的话:
链接: https://www.jianshu.com/p/0ea5e201260f

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值