写在前面

本文并不是基于Camera2的,所以想要了解Camera2的同学可以先散了。文题加了详记二字,因为相机整个打开的流程的确是比较复杂的,稍有疏忽可能就会引发一系列问题。我也是看了一下Android的文档才整理了这篇文章,想看原文的戳这。不得不说,文档还是详细啊~

本文主要会涉及以下内容:

  • 相机的使用流程
  • 拍照及拍照期间的聚焦
  • 保存图片

先放一下最终效果图吧,做的比较简单,各位不用担心:

主要功能就是拍照保存,多的也没啥了,项目地址在文末有。

使用流程

在详细的研究相机之前,首先熟悉一下使用相机的整个流程:

  • 检测和访问相机:创建代码检测相机的存在和请求访问
  • 创建预览类:创建继承自SurfaceView和实现SurfaceHolder接口的预览类。这个类展示来自相机的实时图片
  • 创建一个预览布局:如果你有相机预览类,你就需要创建一个和用户交互的界面布局
  • 为拍照或者录像设置监听:对用户的行为作出响应,你要为你的控件设置监听去开始拍照或者录像,比如你设置了一个拍照的按钮,用户点击之后就要开始拍照。监听用户行为只是其一,还有就是拍照的监听,这个放到后文讨论
  • 捕获和保存文件:无论是拍照还是录像,都需要有保存的功能
  • 释放相机:在不使用相机时候,你的应用一定要释放相机。

那么为什么一定要释放相机资源呢?因为相机硬件是一个共享临界资源,不仅你的应用会使用,其他的应用也会使用相机。所以在不用相机的时候,一定要释放相机,不然你自己的应用和后续其他要使用相机的应用都无法使用相机。

流程大致就是这样,接下来一步一步的跟进,看看这个相机到底特么的是怎么用的。

申请权限

Android 6.0之前 & targetSdkVersion < 23 只需要在清单文件中声明一下权限就行

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
复制代码

上面那个uses-feature,文档中说如果声明了这个,在Google Play中会阻止没有相机的设备下载你的应用。国内的应用商店就不知道了= =。在Android 6.0之后且targetSdkVersion >= 23就需要申请相机权限了。我们可以在Activity的onResume中检测是否拥有相机权限,如果拥有权限就进行下一步的操作,如果没有就申请权限,并在回调中检测是否申请成功,成功的话就进行下一步操作。代码如下:

    @Overrideprotected void onResume() {super.onResume();// 检查权限if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) !=PackageManager.PERMISSION_GRANTED) {// 申请权限ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1);} else {// 已有权限startCameraPre();}}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {// 如果权限申请成功startCameraPre();} else {Toast.makeText(this, "您已拒绝打开相机,想要使用此功能请手动打开相机权限", Toast.LENGTH_SHORT).show();}}
复制代码

检测相机是否存在

在使用之前需要先检测一下设备是否有相机:

    /*** 检查是否拥有相机** @return 如果有返回true,没有返回false*/public static boolean checkCameraHardware(Context context) {if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {// 有相机Log.i(TAG, "有相机");return true;} else {// 没有相机Log.i(TAG, "没有相机");return false;}}
复制代码

代码比较简单,比较坑爹的是什么呢?有一些嵌入式设备的Android系统通过这个方法是无法获取到到底特么的是有没有相机的。获取的可能是错误的信息,我在我们的设备上用这个代码检测,明明没有相机也判断成有相机了。如果一定要判断……还是有办法的,直接Camera.open试一试,成功就说明有,失败就……就失败了呗。

访问相机

访问相机的代码比较简单,就是通过Camera.open方法拿到一个Camera的实例,需要注意的是这个open方法可能会引发异常,最好还是要try catch一下的。

    /*** 获取前置相机实例,注意6.0以上的系统需要动态申请权限(如果* target >= 23)则必须动态申请,否则无法打开相机** @return 打开成功则返回相机实例,失败则返回null*/public static Camera getCameraInstance() {Camera c;try {c = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);} catch (Exception e) {e.printStackTrace();// 相机正在使用或者不存在Log.e(TAG, "相机打开失败,正在使用或者不存在,或者,没有权限?");return null;}return c;}
复制代码

Android 2.3版本以后可以通过Camera.open(int)来打开指定的相机,我这里打开了后置摄像头。

创建预览类

预览类就是用来播放相机画面的类,预览类是继承自SurfaceView的。普通的View以及其子类都是共享同一个surface的,所有的绘制都必须在UI线程进行。而SurfaceView是一种比较特殊的view,他并不与其他view共享surface,而是在内部持有了一个独立的surface,SurfaceView负责管理这个surface的格式、尺寸以及显示位置。由于UI线程还要同事处理其他交互逻辑,因此对View的更新速度和帧率无法保证,而surfaceview由于持有一个独立的surface,因而可以在独立的线程中进行绘制,因此可以提供更高的帧率。自定义相机的预览图像由于对更新速度和帧率要求比较高,所以比较适合用surfaceview来显示。(关于surfaceview的介绍摘自Android自定义相机详细讲解)

介绍了这么多,对SurfaceView大概有个了解就可以了,这个类和相机的声明周期息息相关,需要实现SurfaceHolder.Callback接口来接收View创建和销毁的事件。代码如下:

package com.xiasuhuei321.cameradieorme.camera;/*** Created by xiasuhuei321 on 2017/8/22.* author:luo* e-mail:xiasuhuei321@163.com*/import android.content.Context;
import android.hardware.Camera;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback, Camera.AutoFocusCallback, Camera.PictureCallback {public static final String TAG = "CameraPreview";public static final String DIRNAME = "MyCamera";private SurfaceHolder mHolder;private Camera mCamera;private boolean canTake = false;private Context context;public CameraPreview(Context context) {super(context);this.context = context;// Install a SurfaceHolder.Callback so we get notified when the// underlying surface is created and destroyed.mHolder = getHolder();// deprecated setting, but required on Android versions prior to 3.0mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);Log.i(TAG, "CameraPreview被创建 " + this.hashCode());}/*** surface在很多情况下都会被销毁,这个时候相机也会被释放。* 而这个类的camera就无法再使用了,所以需要外部再传入一个* 正确的Camera实例** @param mCamera Camera实例*/public void setCamera(Camera mCamera) {this.mCamera = mCamera;mHolder.addCallback(this);surfaceCreated(getHolder());Log.i(TAG, "serCamera" + " release = " + CameraUtil.getInstance().isRelease());}@Overridepublic void surfaceCreated(SurfaceHolder holder) {// surface创建完毕,camera设置预览Log.i(TAG, "surface view被创建");if (CameraUtil.getInstance().isRelease()) return;try {mCamera.setPreviewDisplay(holder);mCamera.startPreview();} catch (IOException e) {Log.d(TAG, "Error setting camera preview: " + e.getMessage());}}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {// 在这里可以释放相机资源// 也可以在Activity中释放Log.i(TAG, "surface 被销毁 ");holder.removeCallback(this);// 停止回调,以防释放的相机再被使用导致异常mCamera.setPreviewCallback(null);// 停止预览mCamera.stopPreview();mCamera.lock();// 释放相机资源CameraUtil.getInstance().releaseCamera();mCamera = null;}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {// If your preview can change or rotate, take care of those events here.// Make sure to stop the preview before resizing or reformatting it.if (mHolder.getSurface() == null) {// preview surface does not existreturn;}// stop preview before making changestry {mCamera.stopPreview();} catch (Exception e) {// ignore: tried to stop a non-existent preview}// set preview size and make any resize, rotate or// reformatting changes here// start preview with new settingstry {mCamera.setPreviewDisplay(mHolder);mCamera.startPreview();} catch (Exception e) {Log.d(TAG, "Error starting camera preview: " + e.getMessage());}}/*** 给外部调用,用来拍照的方法*/public void takePhoto() {// 因为设置了聚焦,这里又设置了回调对象,所以重新开始预览之后// 需要一个标志判断是否是拍照的聚焦回调canTake = true;// 首先聚焦mCamera.autoFocus(this);
//        mCamera.takePicture(null, null, this);}@Overridepublic void onAutoFocus(boolean success, final Camera camera) {Log.i(TAG, "聚焦: " + canTake);// 不管聚焦成功与否,都开始拍照if (canTake) {camera.takePicture(null, null, CameraPreview.this);}canTake = false;// 延时一秒,重新开始预览new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {@Overridepublic void run() {mCamera.startPreview();}}, 1000);}@Overridepublic void onPictureTaken(final byte[] data, Camera camera) {Log.i(TAG, "onPictureTaken");// 在子线程中进行io操作new Thread(new Runnable() {@Overridepublic void run() {saveToSd(data);}}).start();}/*** 将照片保存至sd卡*/private void saveToSd(byte[] data) {// 创建位图,这一步在图片比较大的时候可能会抛oom异常,所以跳过这一步,直接将byte[]// 数据写入文件,而且如果有进行图片处理的需求,尽量不要另外再申请内存,不然很容易// oom。所以尽量避免在这里处理图片
//        Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);// 系统时间long dateTaken = System.currentTimeMillis();// 图像名称String fileName = DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken).toString() + ".jpg";FileOutputStream fos = null;if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {String filePath = Environment.getExternalStorageDirectory() + File.separator +DIRNAME + File.separator + fileName;Log.i(TAG, "文件路径:" + filePath);File imgFile = new File(filePath);if (!imgFile.getParentFile().exists()) {imgFile.getParentFile().mkdirs();}try {if (!imgFile.exists()) {imgFile.createNewFile();}fos = new FileOutputStream(imgFile);
//                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);fos.write(data);fos.flush();insertIntoMediaPic();} catch (Exception e) {} finally {try {if (fos != null) {fos.close();//关闭}} catch (Exception e) {e.printStackTrace();}}} else {// sd卡状态异常,直接插入系统相册// 暂时是空实现insertIntoMediaPic();}}private void insertIntoMediaPic() {}
}复制代码

这个类可以说是字字血泪,各位看的时候可以结合注释看……每一个我被坑过的地方我都详细的注释了出来。真的是都在代码里了。关于图片处理那一块我是没什么比较好的办法,内存所限,在拿到byte[] data 这个图片数据数组,我直接在转成Bitmap那一步就OOM了,后来看了一下我这里选取的是4160 * 2340的分辨率,直接写入文件一张图也有4~5M,这个时候的问题就是生成一个Bitmap需要申请很大的内存,而原来的data数组因为这个方法还没结束也无法释放(Java参数传递是引用拷贝传递,所以这时候依然有引用指向内存中的data对象,GC无法回收这块内存),所以就算后续你不额外申请内存,有方法在原有的Bitmap对象上进行操作,也是不行的,因为在生成的时候就OOM了。

创建预览布局

这里Android文档中的建议是在布局中放置一个FrameLayout作为相机预览类的父容器,我采用了这种做法,布局如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="fill_parent"android:orientation="horizontal"><FrameLayoutandroid:id="@+id/camera_preview"android:layout_width="match_parent"android:layout_height="match_parent" /><ImageViewandroid:id="@+id/iv_take"android:layout_width="80dp"android:layout_height="80dp"android:layout_alignParentBottom="true"android:layout_centerHorizontal="true"android:layout_marginBottom="16dp"android:src="@drawable/takephoto" />
</RelativeLayout>
复制代码

布局比较简单,就一个FrameLayout和一个ImageView,点击ImageView开始拍照。

接下来看一下Activity的代码:

package com.xiasuhuei321.cameradieorme.camera;import android.Manifest;
import android.animation.ObjectAnimator;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.Toast;import com.xiasuhuei321.cameradieorme.R;/*** Created by xiasuhuei321 on 2017/8/22.* author:luo* e-mail:xiasuhuei321@163.com*/public class CameraActivity extends AppCompatActivity {private Camera camera;private FrameLayout preview;private CameraPreview mPreview;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);supportRequestWindowFeature(Window.FEATURE_NO_TITLE);setContentView(R.layout.activity_camera);getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);View iv_take = findViewById(R.id.iv_take);final ObjectAnimator scaleX = ObjectAnimator.ofFloat(iv_take, "scaleX", 1f, 0.8f);final ObjectAnimator scaleY = ObjectAnimator.ofFloat(iv_take, "scaleY", 1f, 0.8f);iv_take.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:v.setScaleX(0.9f);v.setScaleY(0.9f);scaleX.start();scaleY.start();break;case MotionEvent.ACTION_UP:v.setScaleX(1f);v.setScaleY(1f);scaleX.reverse();scaleY.reverse();break;}return false;}});iv_take.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {mPreview.takePhoto();}});mPreview = new CameraPreview(this);CameraUtil.getInstance().init(this);}@Overrideprotected void onResume() {super.onResume();// 检查权限if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) !=PackageManager.PERMISSION_GRANTED) {// 申请权限ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1);} else {// 已有权限startCameraPre();}}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {// 如果权限申请成功startCameraPre();} else {Toast.makeText(this, "您已拒绝打开相机,想要使用此功能请手动打开相机权限", Toast.LENGTH_SHORT).show();}}private void startCameraPre() {if (CameraUtil.checkCameraHardware(this)) {camera = CameraUtil.getInstance().getCameraInstance();}mPreview.setCamera(camera);preview = (FrameLayout) findViewById(R.id.camera_preview);if (preview.getChildCount() == 0)preview.addView(mPreview);}
}复制代码

在开始的时候写了两个属性动画,用户在点击的时候有点交互的感觉(貌似并没有什么luan用)。在onResume中检查是否拥有权限打开相机,因为6.0以上需要动态申请啊,蛋疼。拥有权限或者用户给了权限就执行startCameraPre方法,这个方法通过我自己写的CameraUtil获取并初始化了一个Camera实例。并且最后判断FrameLayout中是否有子View,如果没有就将我们自己的相机预览类添加进去。这样打开相机和拍照的整个流程就完成了,当然了,最后还得贴一下CameraUtil代码:

package com.xiasuhuei321.cameradieorme.camera;import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.hardware.Camera;
import android.util.Log;
import android.view.WindowManager;import java.util.List;/*** Created by xiasuhuei321 on 2017/8/21.* author:luo* e-mail:xiasuhuei321@163.com*/public class CameraUtil {public static final String TAG = "CameraUtil";private Camera camera;private int cameraId;private int mScreenWidth;private int mScreenHeight;//    private Callback callback;private boolean release = false;private Camera.Parameters params;private CameraUtil() {}private static class CameraUtilHolder {private static CameraUtil instance = new CameraUtil();}public static CameraUtil getInstance() {return CameraUtilHolder.instance;}public void init(Context context) {WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);Point p = new Point();wm.getDefaultDisplay().getSize(p);mScreenWidth = p.x;mScreenHeight = p.y;}/*** 检查是否拥有相机** @return 如果有返回true,没有返回false*/public static boolean checkCameraHardware(Context context) {if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {// 有相机return true;} else {// 没有相机return false;}}/*** 获取前置相机实例,注意6.0以上的系统需要动态申请权限(如果* target >= 23)则必须动态申请,否则无法打开相机** @return 打开成功则返回相机实例,失败则返回null*/public Camera getCameraInstance() {if (camera != null) {Log.i(TAG, "camera已经打开过,返回前一个值");return camera;}try {camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);cameraId = Camera.CameraInfo.CAMERA_FACING_BACK;} catch (Exception e) {e.printStackTrace();// 相机正在使用或者不存在Log.i(TAG, "相机打开失败,正在使用或者不存在,或者,没有权限?");return null;}initParam();release = false;return camera;}public void initParam() {if (camera == null) {return;}if (params != null) {camera.setParameters(params);} else {camera.setParameters(generateDefaultParams(camera));}}/*** 允许从外部设置相机参数** @param params 相机参数*/public void setParams(Camera.Parameters params) {this.params = params;}/*** 生成默认的相机参数** @param camera 使用该参数的相机* @return 生成的参数*/public Camera.Parameters generateDefaultParams(Camera camera) {Camera.Parameters parameters = camera.getParameters();// 设置聚焦if (parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);// 连续对焦模式}camera.cancelAutoFocus();//自动对焦。// 设置图片格式parameters.setPictureFormat(PixelFormat.JPEG);// 设置照片质量parameters.setJpegQuality(100);if (cameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {// 默认打开前置摄像头,旋转90度即可camera.setDisplayOrientation(90);parameters.setRotation(90);} else if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {// 打开后置摄像头,旋转270,这个待验证camera.setDisplayOrientation(270);parameters.setRotation(180);}// 获取摄像头支持的PictureSize列表List<Camera.Size> picSizeList = parameters.getSupportedPictureSizes();for (Camera.Size size : picSizeList) {Log.i(TAG, "pictureSizeList size.width=" + size.width + "  size.height=" + size.height);}Camera.Size picSize = getProperSize(picSizeList, ((float) mScreenHeight / mScreenWidth));parameters.setPictureSize(picSize.width, picSize.height);// 获取摄像头支持的PreviewSize列表List<Camera.Size> previewSizeList = parameters.getSupportedPreviewSizes();for (Camera.Size size : previewSizeList) {Log.i(TAG, "previewSizeList size.width=" + size.width + "  size.height=" + size.height);}Camera.Size preSize = getProperSize(previewSizeList, ((float) mScreenHeight) / mScreenWidth);Log.i(TAG, "final size is: " + picSize.width + " " + picSize.height);if (null != preSize) {Log.i(TAG, "preSize.width=" + preSize.width + "  preSize.height=" + preSize.height);parameters.setPreviewSize(preSize.width, preSize.height);}return parameters;}private Camera.Size getProperSize(List<Camera.Size> pictureSizeList, float screenRatio) {Log.i(TAG, "screenRatio=" + screenRatio);Camera.Size result = null;for (Camera.Size size : pictureSizeList) {float currentRatio = ((float) size.width) / size.height;if (currentRatio - screenRatio == 0) {result = size;break;}}if (null == result) {for (Camera.Size size : pictureSizeList) {float curRatio = ((float) size.width) / size.height;if (curRatio == 4f / 3) {// 默认w:h = 4:3result = size;break;}}}return result;}/*** 释放相机资源*/public void releaseCamera() {if (camera != null) {camera.release();}camera = null;release = true;}/*** 现在是否处于释放状态** @return true释放,false没释放*/public boolean isRelease() {return release;}}复制代码

这里需要注意的就是生成相机参数那一块了,Android中的相机默认是横向的,我们平时用的时候肯定不是那么用的,所以通过 camera.setDisplayOrientation(90)旋转90度调整一下。不过设置了这个之后,如果不设置parameters.setRotation(90)那么保存的图片方向也不对,设置了这个之后就可以了。不过我看网上很多都是采用自己生成Bitmap然后自己旋转……如果parameters.setRotation(90)这种方式可以完成的话,最好不要采用自己处理的方式了,内存开销太大了。关于资源的释放啊什么的,都在预览类里面注释写好了= = ,这里就不再赘述了。

小结

最开始研究相机是因为项目里一个用到相机三方总是报错,在有空研究了一下相机之后,添了一行代码,测试到现在还算比较稳定,没有出现崩溃了,有的时候真的是,一行代码能改变的东西却是非常多的。跑题了跑题了,现在突然感觉相机可以玩的东西很多……以后这个demo可能会继续完善

代码地址:https://github.com/ForgetAll/CameraDieOrMe

参考资料

  • 官方文档
  • Android自定义相机详解
  • Android手把手带你玩转自定义相机

详记Android打开相机拍照流程相关推荐

  1. android 打开相机拍照功能吗,Android调用相机实现拍照功能

    引言 在Android开发中相信大家都会遇到修改用户头像的问题,用户信息常常包含用户头像,一般流程为:默认头像-->用户修改(拍照/相册选择)-->保存头像图片. 本期我们就来实现调用系统 ...

  2. Android Camera2 相机拍照流程详解

    实现特点 实现自动对焦 选择性正常触发闪光灯flash 复用CaptureRequest.Builder, 参数完全一致 拍照注意事项讲解 代码片段详解 流程 按照常规方式打开预览 设置好相应的全局变 ...

  3. android打开相机拍照及打开相册选择照片

    照相机拍照 Intent intent = new Intent();intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);// 照相机拍照// 需要说明 ...

  4. Android自定义相机拍照、图片裁剪的实现

    原文:Android自定义相机拍照.图片裁剪的实现 最近项目里面又要加一个拍照搜题的功能,也就是用户对着不会做的题目拍一张照片,将照片的文字使用ocr识别出来,再调用题库搜索接口搜索出来展示给用户,类 ...

  5. android调用相机与相册的方法,Android打开相机和相册实例代码

    本文实例为大家分享了Android打开相机和相册具体代码,供大家参考,具体内容如下 打开相机 /** * 选择相机 */ private void showCamera() { // 跳转到系统照相机 ...

  6. 十分钟实现 Android Camera2 相机拍照

    1. 前言 因为工作中要使用Android Camera2 API,但因为Camera2比较复杂,网上资料也比较乱,有一定入门门槛,所以花了几天时间系统研究了下,并在CSDN上记录了下,希望能帮助到更 ...

  7. android 调用相机拍照。适配到 Android 10

    Photograph 项目地址:donkingliang/Photograph 简介: android 调用相机拍照.适配到 Android 10 更多:作者   提 Bug 标签: android ...

  8. Unity for IOS 加载手机相册图片以及打开相机拍照获取图片

    Unity for IOS 加载手机相册图片以及打开相机拍照获取图片 最近想做一个使用unity for IOS获取手机图片的功能,所以就研究了一下 这里我们需要创建两个objective-c文件,最 ...

  9. Qt for Android调用原生接口打开相机拍照并存储照片

    Qt开发Android应用,需求是通过调用Android原生接口去打开系统相机拍照,并返回拍摄的照片.原理很简单,现在Java文件中写android代码调用相机,拍照后将将相片存储在SD卡,然后在C+ ...

最新文章

  1. 【OpenCV】读取csv文件
  2. 微软考虑将 Python 作为 Excel 官方脚本语言
  3. CTFshow 爆破 web21
  4. 《系统集成项目管理工程师》必背100个知识点-96我国企业信息化发展的战略要点...
  5. MIT线性代数:20.克拉默法则,逆矩阵和体积
  6. 第一次冲刺阶段(三)
  7. 信息学奥赛C++语言:5个人分糖块
  8. ubuntu pycharm设置快捷图标_这些Ubuntu中的小技巧,你知道吗?
  9. Java并发编程实践-总结
  10. Jmeter性能测试之命令行执行和生成测试报告
  11. ftp服务器支持ipv6,IPV6下搭建FTP服务器的步骤
  12. 轻量级Kubernetes之k3s:15:firewalld对应方法
  13. Web攻防之业务安全指南(网盘下载)
  14. 判断手机是否有虚拟键盘
  15. python 输出第一行1个* 第二行3个* 第三行5个*
  16. Ubuntu 16.04开机出现Kernel panic 。。。解决办法(亲测有效)
  17. Win10提示“无法创建新的分区也找不到现有的分区”
  18. Vue中为对象添加字段
  19. CentOS8国内镜像下载地址
  20. Linux2019/7/30.1

热门文章

  1. 圣斗士星矢游戏抽奖计算机怎么计算,圣斗士星矢手游抽奖技巧解析 教你抽橙卡...
  2. Cubemx配置STM32H7串口DMA
  3. 宏观人类工效学(人因工程学)
  4. 文本改写和论文查重工具:探狐文案AICopy for Mac中文版
  5. 三:将智能合约部署到ganache测试网
  6. Excel VBA 快速访问 Power Query 表格数据
  7. nginx负载均衡配置,宕机自动切换方式
  8. eeprom和编程器固件 k2_实战经验分享,编程器救砖修复k2p路由器,恢复无线信号和MAC...
  9. 安川e1000中文说明书_安川变频器E1000快速使用指南.pdf
  10. 华侨大学计算机本科导师,华侨大学计算机科学与技术学院导师介绍:李海波