IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 2021-07-29 -> 正文阅读

[移动开发]2021-07-29

Android Camera(一)

这篇文章主要介绍Camera拍照功能的实现。拍照功能的实现主要分为三个部分。

  1. 权限的添加
  2. CameraPreview类的实现
  3. 主活动的实现
    下面详细介绍这三个方面

权限的添加

Androidmanifest.xml文件中权限

<!--    添加camera的使用权限-->
    <uses-permission android:name="android.permission.CAMERA" />
    
    <!--可以防止APP被安装到没有相机的Android设备上(目前仅Google Play支持)-->
    <uses-feature android:name="android.hardware.camera" />

<!--    添加sdcard的读写权限-->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>

在高版本的API中,不仅要在Androidmanifest.xml配置权限, 还需要在代码中动态申请权限。

  if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)== PackageManager.PERMISSION_GRANTED){

         }else {
             ActivityCompat.requestPermissions(this,permission,1);
         }

        if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},2);
        }else {

        }

如果我们想要存储拍摄的照片,我们需要在Androidmanifest.xml中设置暂时不做分区存储


AndroidManifest.xml 中 配置requestLegacyExternalStorage即可

<application
        android:requestLegacyExternalStorage="true">

下面是完整的Androidmanifest.xml文件。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.huaqin">
<!--    添加camera的使用权限-->
    <uses-permission android:name="android.permission.CAMERA" />

    <!--可以防止APP被安装到没有相机的Android设备上(目前仅Google Play支持)-->
    <uses-feature android:name="android.hardware.camera" />

<!--    添加sdcard的读写权限-->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyCameraTest1"
        android:requestLegacyExternalStorage="true">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

CameraPreview类的实现

使用相机我们需要一个能够看到图像的地方,这里Google叫我们使用SurfaceView这个类,那么SurfaceView这个类是什么呢,首先这个类是继承View的,可以在将图像绘制在屏幕上并显示给用户。其实能够显示的原因是SurfaceView中包含一个Surface对象,Surface是SurfaceView的可见部分,好了我们提到了Surface,又是一个让很多人头疼的概念,好吧让我们重头来讲解。

首先我们在手机屏幕上看到的是这些画面都可以算是View(当然SurfaceView也算View),那么View是什么?View其实就是手机内存中的一小块区域,所谓显示,就是显卡等硬件将内存中的信息显示在屏幕上的过程,这下我想大家应该清楚一点了吧,我们继续,那我们说到的可见部分又是怎么回事呢,其实我们看到的屏幕可以说是2维的,也就是长和宽,但是在它的内部其实是3维的,还有一个维度就是层Layer,也就是层的概念,用过Visio或者AutoCAD的同学应该很好理解,在画图的时候,上层有时会将下层的遮挡,我们看到的图像就是这样一层一层堆叠起来的,这当中有些层不可见,有些层部分可见,有些层完全可见,我们看到的就是它们之中可见的部分,而Surface就是SurfaceView中的一个可见的部分,我们在摄像或者拍照用的就是它显示了。

了解了View的作用,我想有人会问了:为什么不使用View,而用SurfaceView,首先在这里我想说用View这是可以的,但是用SurfaceView会更好,SurfaceView中Google为摄像等方法重写了很多方法,而且SurfaceView类是在一个新起的单独线程中重新绘制画面,而View是在UI线程上绘制画面,可以想象,如果你用View来预览图像(当然你必须要重写View中的大量方法来实现预览,这是我们不愿意看到的),那么在摄像的时候你就什么都别想做了,因为如果你打算更新UI的话,线程就可能会阻塞,你的APP就可能未响应了,Android系统就自动提示关闭了,这是用户极其不好的体验,而我们希望在摄像的也能更新UI,所以我们用SurfaceView类来预览图像。

代码解释

首先别忘了最重要的事:就是获取Camera实例,不然肯定会报空指针异常的,我们用Camera.open()方法来获得实例对象,对于Andorid 2.3以前的手机其实是没有前置摄像头的,所以直接Camera.open()方法就行了,但是之后版本的手机拥有了前置摄像头,所以手机有了两个摄像头,那么Camera.open()就不能清楚表明到底开启那个摄像头了,所以Google提供了Camera.open(0)方法,这个方法其实就是指用手机的后置摄像头,而前置摄像头用Camera.open(1)方法,这样就可以获得Camera对象了。然后在活动中初始化CameraSerfaceView.

需要注意的是:由于Android机制的缘故,Android相机只能够被一个APP线程所绑定,也就是说如果你正在使用相机进行拍照摄像的时候,另一个程序便不能使用Camera类来启用相机,而且会报出Can not release的错误,那么如何判定你是否使用了相机呢,其实只要你使用了Camera.open()方法获得了相机的示例,系统就认为你使用了相机,所以当你使用了完了相机一定记得要释放相机的资源,不然别的应用程序用不了呀,我们可以使用 Camera.release()来释放相机资源,Google官方的意见是重写Activity的onPause()方法来Camera.release(),其实也可以重写Activity的onBackPressed()方法来释放相机,也就是用户按Back键的时候释放相机资源。

看了上面的讲述我们知道了Surface其实就是对应的一个内存区域,而在内存区中的数据是有生存周期的,可以动态申请创建和销毁,当然也会更新,于是就有了对内存区的操作,在本例中就是surfaceCreated/Changed/Destroyed,也就是创建/修改/销毁,而3个操作放在一起就是Callback。

Surface代码中要求我们implements SurfaceHolder.Callback,那么为什么要使用SurfaceHolder.Callback呢,callback 意思是回调,那么它为什么要回调呢?这里我解释一下回调,回调在大部分情况就是程序在运行到需要一个函数(但是它本身没有)这里就是程序需要查看一下它内部记录的能处理这种情况的函数了,借用网上的比方:A人有能力做某件事但是现在不用他去做,所以他去登记一下自己的能力,到了需要用到他的时候,就会有人叫他去做,到程序里面就是A人能做surfaceCreated/Changed/Destroyed 3件事,他去登记了,并有个统称就叫SurfaceHolder.Callback,而在此程序中需要做这3件事,所以会Callback。而SurfaceHolder是什么呢,它在本例中就好比用开发商,而SurfaceView就像一个建房子计划负责人,它知道要首先要找到开发商,所以mHolder = getHolder()找开发商,而开发商就找到能够有建房能力的这个人A,就是回调他登记的信息mHolder.addCallback(this),而A的能力可以比喻成surfaceCreated建房子,surfaceDestroyed拆房子,surfaceChanged装修房子,故名思意,surfaceChanged只能在建房和拆房之间了。

mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
这是给mHolder设置缓存信息了,这个在Android 3.0之后就是自动设置的了,所以我们可以忽略这句代码了。

好了到了surfaceCreated了,这个就是在屏幕创建时的准备了,首先我们需要将相机的预览界面绑定到我们的可见区域SurfaceView上,而getHolder()得到的Holder是对Surface有绝对控制权的,所以我们使用了holder,这个方法也就是把SurfaceView跟Camera连接起来,准备一个实时的Camera图像预览界面。

mCamera.setPreviewDisplay(holder); 

接下来就可以设置开始预览了

mCamera.startPreview(); 

当然别高兴了,这还没完呢,程序在执行完了surfaceCreated后是肯定会接着执行surfaceChanged的,这个方法的意思就是只要屏幕改变就会调用一个它,在首次启动程序时会调用surfaceCreated接着就会调用一次surfaceChanged,所以surfaceChanged方法在整个使用相机过程中必定至少调用一次,所以我们通常在surfaceChanged方法中进行判断可见区域Surface是否正常,也控制当界面改变时相机的改变,如横竖屏切换时相机的改变,下面这段代码大家就应该能够看懂了,其实意思就是当检测到surface改变的时候检测Surface是否正常,所有环节都是先停止相机的预览,再根据Surface的变化,改变相机的相关属性后再按如上述surfaceChanged方法中的mCamera.setPreviewDisplay(mHolder)与mCamera.startPreview()启动预览即可。
需要说一句:如果你要使用相机的自动聚焦以及闪光灯等一系列功能也可以在surfaceChanged方法中设置,这里贴出少量代码:

Camera.Parameters params = mCamera.getParameters();
List<String> focusModes = params.getSupportedFocusModes();     
if(
focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);}
mCamera.setParameters(params);

主要就是先判断手机有不有这个功能focusModes.contains(),然后再设置这个功能params.setFocusMode(),更多的功能设置可以在API中Camera.Parameters类中查看并使用。

package com.huaqin;

import android.content.Context;

import android.hardware.Camera;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import androidx.annotation.NonNull;

import java.io.IOException;

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {

    private static final String TAG = "zd";
    private Camera mCamera;
    private SurfaceHolder mHolder;

    public Camera getmCamera() {
        return mCamera;
    }

    //初始化
    public CameraPreview(Context context,Camera camera,SurfaceHolder holder) {
        super(context);

        //获取Camera 的实例
        this.mCamera = camera;
        //获取SurfaceHolder的实例

        if (getHandler()==null){
            Log.d(TAG, "CameraPreview: null1");
        }
        // Log.d(TAG, "CameraPreview: "+getHandler().toString());
        this.mHolder = holder;

        if (getHandler()==null){
            Log.d(TAG, "CameraPreview: null2");
        }

        //注册回调函数
        mHolder.addCallback(this);
    }

    @Override
    public void surfaceCreated(@NonNull SurfaceHolder holder) {
        //The Surface has been created, now tell the camera where to draw the preview.
        //创建Surface后,将相机的预览界面绑定到可见区域SurfaceView
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
        // 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){
            return;
        }


        //在改变预览界面前,需要先停止预览
        try {
            mCamera.stopPreview();
        }catch (Exception e){
            e.printStackTrace();
        }

        //更改预览


        //重新启动预览

        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
        mCamera.release();
    }
}

主活动的实现

视图

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

   <SurfaceView
       android:layout_width="wrap_content"
       android:layout_height="0dp"
       android:layout_weight="1"
       android:id="@+id/surface_view"/>


    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/button"
        android:text="button"/>

</LinearLayout>

活动

首先动态申请相应的权限,然后获取界面组件并且将surfaceView与cameraPreview绑定()

 cameraPreview = new CameraPreview(this, Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK),
                surfaceView.getHolder());

最后在按钮点击事件中,启动拍照

mCamera.takePicture(null, null, new Camera.PictureCallback() {
       @Override
public void onPictureTaken(byte[] data, Camera camera) {  

                        }
                    });

照片的保存

  1. 长按保存,自己先拼接好一个文件家路径.注意IO流只能帮忙建文件,但是不能帮忙建目录(路径)
  2. 引导具体的文件名和路径
  3. 文件的读取
  4. 利用广播告诉相册有图片更新
package com.huaqin;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "zd";
    private Button button;
    private CameraPreview cameraPreview;

    private static final String[] permission = new String[] {
           Manifest.permission.CAMERA,
           Manifest.permission.WRITE_EXTERNAL_STORAGE
    };
    @Overridel
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)== PackageManager.PERMISSION_GRANTED){

         }else {
             ActivityCompat.requestPermissions(this,permission,1);
         }

        if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},2);
        }else {

        }


        SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surface_view);
        Button button = (Button) findViewById(R.id.button);



        cameraPreview = new CameraPreview(this, Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK),
                surfaceView.getHolder());





        button.setOnClickListener(this);


    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.button:
                cameraPreview.getmCamera().takePicture(null, null, new Camera.PictureCallback() {
                    @Override
                    public void onPictureTaken(byte[] data, Camera camera) {
                        savePicture(data,"nini");
                        camera.startPreview();
                    }
                });
                break;
            default:
                break;
        }
    }


    private void savePicture(byte[] data,String saveName){
        //先拼好一个路径:在内存卡或手机上做好文件夹
        String filePath = Environment.getExternalStorageDirectory()+"/DCIM/Camera";
        Log.d(TAG, "savePicture: filePath = "+filePath);

        File localFile = new File(filePath);

        //引导具体的文件名和路径
        File finalImageFile = new File(localFile, saveName+".jpg");
        if (finalImageFile.exists()){
            finalImageFile.delete();
        }

        try {
            //创建新的空文件
            finalImageFile.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }

        //文件的读取
        FileOutputStream fos = null;

        //设置文件流输出地址
        try {
            fos = new FileOutputStream(finalImageFile);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        //输出文件流
        try {
            fos.write(data);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            fos.flush();
            fos.close();
            Toast.makeText(this,finalImageFile.getAbsolutePath(),Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
            e.printStackTrace();
        }

        //发广播告诉相册有图片需要更新,这样可以在图册下看到保存的图片了
        Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
        Uri uri = Uri.fromFile(finalImageFile);
        intent.setData(uri);
        sendBroadcast(intent);

    }

}
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-07-30 12:51:16  更:2021-07-30 12:53:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/7 8:15:46-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码