詳記Android打開相機(jī)拍照流程

寫在前面

本文并不是基于Camera2的暇屋,所以想要了解Camera2的同學(xué)可以先散了柒昏。文題加了詳記二字苦蒿,因?yàn)橄鄼C(jī)整個(gè)打開的流程的確是比較復(fù)雜的儿捧,稍有疏忽可能就會(huì)引發(fā)一系列問題。我也是看了一下Android的文檔才整理了這篇文章墨闲,想看原文的戳這今妄。不得不說,文檔還是詳細(xì)啊~

本文主要會(huì)涉及以下內(nèi)容:

  • 相機(jī)的使用流程
  • 拍照及拍照期間的聚焦
  • 保存圖片

先放一下最終效果圖吧鸳碧,做的比較簡(jiǎn)單蛙奖,各位不用擔(dān)心:

最終也就這樣~

主要功能就是拍照保存,多的也沒啥了杆兵,項(xiàng)目地址在文末有。

使用流程

在詳細(xì)的研究相機(jī)之前仔夺,首先熟悉一下使用相機(jī)的整個(gè)流程:

  • 檢測(cè)和訪問相機(jī):創(chuàng)建代碼檢測(cè)相機(jī)的存在和請(qǐng)求訪問
  • 創(chuàng)建預(yù)覽類:創(chuàng)建繼承自SurfaceView和實(shí)現(xiàn)SurfaceHolder接口的預(yù)覽類琐脏。這個(gè)類展示來自相機(jī)的實(shí)時(shí)圖片
  • 創(chuàng)建一個(gè)預(yù)覽布局:如果你有相機(jī)預(yù)覽類,你就需要?jiǎng)?chuàng)建一個(gè)和用戶交互的界面布局
  • 為拍照或者錄像設(shè)置監(jiān)聽:對(duì)用戶的行為作出響應(yīng)缸兔,你要為你的控件設(shè)置監(jiān)聽去開始拍照或者錄像日裙,比如你設(shè)置了一個(gè)拍照的按鈕,用戶點(diǎn)擊之后就要開始拍照惰蜜。監(jiān)聽用戶行為只是其一昂拂,還有就是拍照的監(jiān)聽,這個(gè)放到后文討論
  • 捕獲和保存文件:無論是拍照還是錄像抛猖,都需要有保存的功能
  • 釋放相機(jī):在不使用相機(jī)時(shí)候格侯,你的應(yīng)用一定要釋放相機(jī)。

那么為什么一定要釋放相機(jī)資源呢财著?因?yàn)橄鄼C(jī)硬件是一個(gè)共享臨界資源联四,不僅你的應(yīng)用會(huì)使用,其他的應(yīng)用也會(huì)使用相機(jī)撑教。所以在不用相機(jī)的時(shí)候朝墩,一定要釋放相機(jī),不然你自己的應(yīng)用和后續(xù)其他要使用相機(jī)的應(yīng)用都無法使用相機(jī)伟姐。

流程大致就是這樣收苏,接下來一步一步的跟進(jìn)亿卤,看看這個(gè)相機(jī)到底特么的是怎么用的。

申請(qǐng)權(quán)限

Android 6.0之前 & targetSdkVersion < 23 只需要在清單文件中聲明一下權(quán)限就行

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

上面那個(gè)uses-feature鹿霸,文檔中說如果聲明了這個(gè)排吴,在Google Play中會(huì)阻止沒有相機(jī)的設(shè)備下載你的應(yīng)用。國(guó)內(nèi)的應(yīng)用商店就不知道了= =杜跷。在Android 6.0之后且targetSdkVersion >= 23就需要申請(qǐng)相機(jī)權(quán)限了傍念。我們可以在Activity的onResume中檢測(cè)是否擁有相機(jī)權(quán)限,如果擁有權(quán)限就進(jìn)行下一步的操作葛闷,如果沒有就申請(qǐng)權(quán)限憋槐,并在回調(diào)中檢測(cè)是否申請(qǐng)成功,成功的話就進(jìn)行下一步操作淑趾。代碼如下:

    @Override
    protected void onResume() {
        super.onResume();
        // 檢查權(quán)限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) !=
                PackageManager.PERMISSION_GRANTED) {
            // 申請(qǐng)權(quán)限
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1);
        } else {
            // 已有權(quán)限
            startCameraPre();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 如果權(quán)限申請(qǐng)成功
            startCameraPre();
        } else {
            Toast.makeText(this, "您已拒絕打開相機(jī)阳仔,想要使用此功能請(qǐng)手動(dòng)打開相機(jī)權(quán)限", Toast.LENGTH_SHORT).show();
        }
    }

檢測(cè)相機(jī)是否存在

在使用之前需要先檢測(cè)一下設(shè)備是否有相機(jī):

    /**
     * 檢查是否擁有相機(jī)
     *
     * @return 如果有返回true,沒有返回false
     */
    public static boolean checkCameraHardware(Context context) {
        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
            // 有相機(jī)
            Log.i(TAG, "有相機(jī)");
            return true;
        } else {
            // 沒有相機(jī)
            Log.i(TAG, "沒有相機(jī)");
            return false;
        }
    }

代碼比較簡(jiǎn)單扣泊,比較坑爹的是什么呢近范?有一些嵌入式設(shè)備的Android系統(tǒng)通過這個(gè)方法是無法獲取到到底特么的是有沒有相機(jī)的。獲取的可能是錯(cuò)誤的信息延蟹,我在我們的設(shè)備上用這個(gè)代碼檢測(cè)评矩,明明沒有相機(jī)也判斷成有相機(jī)了。如果一定要判斷……還是有辦法的阱飘,直接Camera.open試一試斥杜,成功就說明有,失敗就……就失敗了唄沥匈。

訪問相機(jī)

訪問相機(jī)的代碼比較簡(jiǎn)單蔗喂,就是通過Camera.open方法拿到一個(gè)Camera的實(shí)例,需要注意的是這個(gè)open方法可能會(huì)引發(fā)異常高帖,最好還是要try catch一下的缰儿。

    /**
     * 獲取前置相機(jī)實(shí)例,注意6.0以上的系統(tǒng)需要?jiǎng)討B(tài)申請(qǐng)權(quán)限(如果
     * target >= 23)則必須動(dòng)態(tài)申請(qǐng)散址,否則無法打開相機(jī)
     *
     * @return 打開成功則返回相機(jī)實(shí)例乖阵,失敗則返回null
     */
    public static Camera getCameraInstance() {
        Camera c;
        try {
            c = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
        } catch (Exception e) {
            e.printStackTrace();
            // 相機(jī)正在使用或者不存在
            Log.e(TAG, "相機(jī)打開失敗,正在使用或者不存在预麸,或者义起,沒有權(quán)限?");
            return null;
        }
        return c;
    }

Android 2.3版本以后可以通過Camera.open(int)來打開指定的相機(jī)师崎,我這里打開了后置攝像頭默终。

創(chuàng)建預(yù)覽類

預(yù)覽類就是用來播放相機(jī)畫面的類,預(yù)覽類是繼承自SurfaceView的。普通的View以及其子類都是共享同一個(gè)surface的齐蔽,所有的繪制都必須在UI線程進(jìn)行两疚。而SurfaceView是一種比較特殊的view,他并不與其他view共享surface含滴,而是在內(nèi)部持有了一個(gè)獨(dú)立的surface诱渤,SurfaceView負(fù)責(zé)管理這個(gè)surface的格式、尺寸以及顯示位置谈况。由于UI線程還要同事處理其他交互邏輯勺美,因此對(duì)View的更新速度和幀率無法保證,而surfaceview由于持有一個(gè)獨(dú)立的surface碑韵,因而可以在獨(dú)立的線程中進(jìn)行繪制赡茸,因此可以提供更高的幀率。自定義相機(jī)的預(yù)覽圖像由于對(duì)更新速度和幀率要求比較高祝闻,所以比較適合用surfaceview來顯示占卧。(關(guān)于surfaceview的介紹摘自Android自定義相機(jī)詳細(xì)講解

介紹了這么多,對(duì)SurfaceView大概有個(gè)了解就可以了联喘,這個(gè)類和相機(jī)的聲明周期息息相關(guān)华蜒,需要實(shí)現(xiàn)SurfaceHolder.Callback接口來接收View創(chuàng)建和銷毀的事件。代碼如下:

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.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

        Log.i(TAG, "CameraPreview被創(chuàng)建 " + this.hashCode());
    }

    /**
     * surface在很多情況下都會(huì)被銷毀豁遭,這個(gè)時(shí)候相機(jī)也會(huì)被釋放叭喜。
     * 而這個(gè)類的camera就無法再使用了,所以需要外部再傳入一個(gè)
     * 正確的Camera實(shí)例
     *
     * @param mCamera Camera實(shí)例
     */
    public void setCamera(Camera mCamera) {
        this.mCamera = mCamera;
        mHolder.addCallback(this);
        surfaceCreated(getHolder());
        Log.i(TAG, "serCamera" + " release = " + CameraUtil.getInstance().isRelease());
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // surface創(chuàng)建完畢蓖谢,camera設(shè)置預(yù)覽
        Log.i(TAG, "surface view被創(chuàng)建");
        if (CameraUtil.getInstance().isRelease()) return;
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 在這里可以釋放相機(jī)資源
        // 也可以在Activity中釋放
        Log.i(TAG, "surface 被銷毀 ");
        holder.removeCallback(this);
        // 停止回調(diào)域滥,以防釋放的相機(jī)再被使用導(dǎo)致異常
        mCamera.setPreviewCallback(null);
        // 停止預(yù)覽
        mCamera.stopPreview();
        mCamera.lock();
        // 釋放相機(jī)資源
        CameraUtil.getInstance().releaseCamera();
        mCamera = null;

    }

    @Override
    public 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 exist
            return;
        }

        // stop preview before making changes
        try {
            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 settings
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

        } catch (Exception e) {
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }

    }

    /**
     * 給外部調(diào)用,用來拍照的方法
     */
    public void takePhoto() {
        // 因?yàn)樵O(shè)置了聚焦蜈抓,這里又設(shè)置了回調(diào)對(duì)象,所以重新開始預(yù)覽之后
        // 需要一個(gè)標(biāo)志判斷是否是拍照的聚焦回調(diào)
        canTake = true;
        // 首先聚焦
        mCamera.autoFocus(this);
//        mCamera.takePicture(null, null, this);
    }

    @Override
    public void onAutoFocus(boolean success, final Camera camera) {
        Log.i(TAG, "聚焦: " + canTake);
        // 不管聚焦成功與否昂儒,都開始拍照
        if (canTake) {
            camera.takePicture(null, null, CameraPreview.this);
        }
        canTake = false;
        // 延時(shí)一秒沟使,重新開始預(yù)覽
        new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
            @Override
            public void run() {
                mCamera.startPreview();
            }
        }, 1000);
    }

    @Override
    public void onPictureTaken(final byte[] data, Camera camera) {
        Log.i(TAG, "onPictureTaken");
        // 在子線程中進(jìn)行io操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                saveToSd(data);
            }
        }).start();
    }


    /**
     * 將照片保存至sd卡
     */
    private void saveToSd(byte[] data) {
        // 創(chuàng)建位圖,這一步在圖片比較大的時(shí)候可能會(huì)拋oom異常渊跋,所以跳過這一步腊嗡,直接將byte[]
        // 數(shù)據(jù)寫入文件,而且如果有進(jìn)行圖片處理的需求拾酝,盡量不要另外再申請(qǐng)內(nèi)存燕少,不然很容易
        // oom。所以盡量避免在這里處理圖片
//        Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
        // 系統(tǒng)時(shí)間
        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();//關(guān)閉
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        } else {
            // sd卡狀態(tài)異常蒿囤,直接插入系統(tǒng)相冊(cè)
            // 暫時(shí)是空實(shí)現(xiàn)
            insertIntoMediaPic();
        }

    }

    private void insertIntoMediaPic() {

    }
}

這個(gè)類可以說是字字血淚客们,各位看的時(shí)候可以結(jié)合注釋看……每一個(gè)我被坑過的地方我都詳細(xì)的注釋了出來。真的是都在代碼里了。關(guān)于圖片處理那一塊我是沒什么比較好的辦法底挫,內(nèi)存所限恒傻,在拿到byte[] data 這個(gè)圖片數(shù)據(jù)數(shù)組,我直接在轉(zhuǎn)成Bitmap那一步就OOM了建邓,后來看了一下我這里選取的是4160 * 2340的分辨率盈厘,直接寫入文件一張圖也有4~5M,這個(gè)時(shí)候的問題就是生成一個(gè)Bitmap需要申請(qǐng)很大的內(nèi)存官边,而原來的data數(shù)組因?yàn)檫@個(gè)方法還沒結(jié)束也無法釋放(Java參數(shù)傳遞是引用拷貝傳遞沸手,所以這時(shí)候依然有引用指向內(nèi)存中的data對(duì)象,GC無法回收這塊內(nèi)存)注簿,所以就算后續(xù)你不額外申請(qǐng)內(nèi)存契吉,有方法在原有的Bitmap對(duì)象上進(jìn)行操作,也是不行的滩援,因?yàn)樵谏傻臅r(shí)候就OOM了栅隐。

創(chuàng)建預(yù)覽布局

這里Android文檔中的建議是在布局中放置一個(gè)FrameLayout作為相機(jī)預(yù)覽類的父容器,我采用了這種做法玩徊,布局如下:

<?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">

    <FrameLayout
        android:id="@+id/camera_preview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ImageView
        android: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>

布局比較簡(jiǎn)單租悄,就一個(gè)FrameLayout和一個(gè)ImageView,點(diǎn)擊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;

    @Override
    protected 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() {
            @Override
            public 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() {
            @Override
            public void onClick(View v) {
                mPreview.takePhoto();
            }
        });

        mPreview = new CameraPreview(this);
        CameraUtil.getInstance().init(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        // 檢查權(quán)限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) !=
                PackageManager.PERMISSION_GRANTED) {
            // 申請(qǐng)權(quán)限
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1);
        } else {
            // 已有權(quán)限
            startCameraPre();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 如果權(quán)限申請(qǐng)成功
            startCameraPre();
        } else {
            Toast.makeText(this, "您已拒絕打開相機(jī)泣棋,想要使用此功能請(qǐng)手動(dòng)打開相機(jī)權(quán)限", 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);
    }
}

在開始的時(shí)候?qū)懥藘蓚€(gè)屬性動(dòng)畫,用戶在點(diǎn)擊的時(shí)候有點(diǎn)交互的感覺(貌似并沒有什么luan用)畔塔。在onResume中檢查是否擁有權(quán)限打開相機(jī)潭辈,因?yàn)?.0以上需要?jiǎng)討B(tài)申請(qǐng)啊,蛋疼澈吨。擁有權(quán)限或者用戶給了權(quán)限就執(zhí)行startCameraPre方法把敢,這個(gè)方法通過我自己寫的CameraUtil獲取并初始化了一個(gè)Camera實(shí)例。并且最后判斷FrameLayout中是否有子View谅辣,如果沒有就將我們自己的相機(jī)預(yù)覽類添加進(jìn)去修赞。這樣打開相機(jī)和拍照的整個(gè)流程就完成了,當(dāng)然了桑阶,最后還得貼一下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;
    }

    /**
     * 檢查是否擁有相機(jī)
     *
     * @return 如果有返回true柏副,沒有返回false
     */
    public static boolean checkCameraHardware(Context context) {
        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
            // 有相機(jī)
            return true;
        } else {
            // 沒有相機(jī)
            return false;
        }
    }

    /**
     * 獲取前置相機(jī)實(shí)例,注意6.0以上的系統(tǒng)需要?jiǎng)討B(tài)申請(qǐng)權(quán)限(如果
     * target >= 23)則必須動(dòng)態(tài)申請(qǐng)蚣录,否則無法打開相機(jī)
     *
     * @return 打開成功則返回相機(jī)實(shí)例割择,失敗則返回null
     */
    public Camera getCameraInstance() {
        if (camera != null) {
            Log.i(TAG, "camera已經(jīng)打開過,返回前一個(gè)值");
            return camera;
        }
        try {
            camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
            cameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
        } catch (Exception e) {
            e.printStackTrace();
            // 相機(jī)正在使用或者不存在
            Log.i(TAG, "相機(jī)打開失敗萎河,正在使用或者不存在荔泳,或者蕉饼,沒有權(quán)限?");
            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));
        }
    }

    /**
     * 允許從外部設(shè)置相機(jī)參數(shù)
     *
     * @param params 相機(jī)參數(shù)
     */
    public void setParams(Camera.Parameters params) {
        this.params = params;
    }

    /**
     * 生成默認(rèn)的相機(jī)參數(shù)
     *
     * @param camera 使用該參數(shù)的相機(jī)
     * @return 生成的參數(shù)
     */
    public Camera.Parameters generateDefaultParams(Camera camera) {
        Camera.Parameters parameters = camera.getParameters();
        // 設(shè)置聚焦
        if (parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);// 連續(xù)對(duì)焦模式
        }
        camera.cancelAutoFocus();//自動(dòng)對(duì)焦换可。
        // 設(shè)置圖片格式
        parameters.setPictureFormat(PixelFormat.JPEG);
        // 設(shè)置照片質(zhì)量
        parameters.setJpegQuality(100);
        if (cameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {
            // 默認(rèn)打開前置攝像頭椎椰,旋轉(zhuǎn)90度即可
            camera.setDisplayOrientation(90);
            parameters.setRotation(90);
        } else if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            // 打開后置攝像頭,旋轉(zhuǎn)270沾鳄,這個(gè)待驗(yàn)證
            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) {// 默認(rèn)w:h = 4:3
                    result = size;
                    break;
                }
            }
        }

        return result;
    }

    /**
     * 釋放相機(jī)資源
     */
    public void releaseCamera() {
        if (camera != null) {
            camera.release();
        }
        camera = null;
        release = true;
    }

    /**
     * 現(xiàn)在是否處于釋放狀態(tài)
     *
     * @return true釋放慨飘,false沒釋放
     */
    public boolean isRelease() {
        return release;
    }

}

這里需要注意的就是生成相機(jī)參數(shù)那一塊了,Android中的相機(jī)默認(rèn)是橫向的译荞,我們平時(shí)用的時(shí)候肯定不是那么用的瓤的,所以通過 camera.setDisplayOrientation(90)旋轉(zhuǎn)90度調(diào)整一下。不過設(shè)置了這個(gè)之后吞歼,如果不設(shè)置parameters.setRotation(90)那么保存的圖片方向也不對(duì)圈膏,設(shè)置了這個(gè)之后就可以了。不過我看網(wǎng)上很多都是采用自己生成Bitmap然后自己旋轉(zhuǎn)……如果parameters.setRotation(90)這種方式可以完成的話篙骡,最好不要采用自己處理的方式了稽坤,內(nèi)存開銷太大了。關(guān)于資源的釋放啊什么的糯俗,都在預(yù)覽類里面注釋寫好了= = 尿褪,這里就不再贅述了。

小結(jié)

最開始研究相機(jī)是因?yàn)轫?xiàng)目里一個(gè)用到相機(jī)三方總是報(bào)錯(cuò)得湘,在有空研究了一下相機(jī)之后杖玲,添了一行代碼,測(cè)試到現(xiàn)在還算比較穩(wěn)定淘正,沒有出現(xiàn)崩潰了摆马,有的時(shí)候真的是,一行代碼能改變的東西卻是非常多的鸿吆。跑題了跑題了囤采,現(xiàn)在突然感覺相機(jī)可以玩的東西很多……以后這個(gè)demo可能會(huì)繼續(xù)完善

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

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市惩淳,隨后出現(xiàn)的幾起案子蕉毯,更是在濱河造成了極大的恐慌,老刑警劉巖黎泣,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異缤谎,居然都是意外死亡抒倚,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門坷澡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來托呕,“玉大人,你說我怎么就攤上這事∠罱迹” “怎么了馅扣?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)着降。 經(jīng)常有香客問我差油,道長(zhǎng),這世上最難降的妖魔是什么任洞? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任蓄喇,我火速辦了婚禮,結(jié)果婚禮上交掏,老公的妹妹穿的比我還像新娘妆偏。我一直安慰自己,他們只是感情好盅弛,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布钱骂。 她就那樣靜靜地躺著,像睡著了一般挪鹏。 火紅的嫁衣襯著肌膚如雪见秽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天狰住,我揣著相機(jī)與錄音张吉,去河邊找鬼。 笑死催植,一個(gè)胖子當(dāng)著我的面吹牛肮蛹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播创南,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼伦忠,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了稿辙?” 一聲冷哼從身側(cè)響起昆码,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎邻储,沒想到半個(gè)月后赋咽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吨娜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年脓匿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宦赠。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡陪毡,死狀恐怖米母,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情毡琉,我是刑警寧澤铁瞒,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站桅滋,受9級(jí)特大地震影響慧耍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜虱歪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一蜂绎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧笋鄙,春花似錦师枣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至找岖,卻和暖如春陨倡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背许布。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工兴革, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蜜唾。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓杂曲,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親袁余。 傳聞我的和親對(duì)象是個(gè)殘疾皇子擎勘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,517評(píng)論 25 707
  • Android中開發(fā)相機(jī)的兩種方式Android系統(tǒng)提供了兩種使用手機(jī)相機(jī)資源實(shí)現(xiàn)拍攝功能的方法,一種是直接通過I...
    TensorFlow開發(fā)者閱讀 3,017評(píng)論 0 14
  • 上一篇介紹了如何使用系統(tǒng)相機(jī)簡(jiǎn)單颖榜、快速的進(jìn)行拍照棚饵,本篇將介紹如何使用框架提供的API直接控制攝像機(jī)硬件。 你還在為...
    Xiao_Mai閱讀 7,144評(píng)論 4 18
  • 一.Android中開發(fā)相機(jī)應(yīng)用的兩種方式 Android系統(tǒng)提供了兩種使用手機(jī)相機(jī)資源實(shí)現(xiàn)拍攝功能的方法掩完,一種是...
    GB_speak閱讀 5,793評(píng)論 2 25
  • 如果且蓬,您還可以看一眼現(xiàn)在的我欣硼, 您一定不會(huì)失望, 您一定會(huì)夸我做的很好缅疟。 我沒有讓您失望分别, 沒有忘記你臨終前對(duì)我說...
    言諾生生閱讀 199評(píng)論 0 0