一切源于在項(xiàng)目過程中的一個(gè)Bug:我的需求是在MainActivity 實(shí)現(xiàn)自動(dòng)預(yù)覽,也可以點(diǎn)擊跳到簽到SignedActivity去實(shí)現(xiàn)拍照簽到,第一次進(jìn)入界面的時(shí)候都是正常的,但是有時(shí)候返回來的時(shí)候預(yù)覽失敗,即從MainActivity跳轉(zhuǎn)到SignedActivity偶爾預(yù)覽失敗和從SignedActivity返回MainActivity偶爾失敗,都是報(bào)(CAMERA_IN_USE)ERRO=1的錯(cuò)誤,奇怪的是的的確確做了完全釋放操作,加上以前用的更多的是Camera api 對(duì)于Camer2 的機(jī)制沒有完整去研究過,一下子懵了,于是乎先去找了Stack Overflow,查到一個(gè)解決方案是:"我棄用了新API,換回舊API",ORZ,找了其他的也沒有答案,可是我不服呀,我就把官方的文檔全部啃了一遍,于是乎便有了以下的理解,我想如果你不懂得怎么使用Camera2的話,這篇絕對(duì)值得你去閱讀,你會(huì)發(fā)現(xiàn)Camera2 并非像大多數(shù)說得那樣使用起來很復(fù)雜。
莊浪網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)!從網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、成都響應(yīng)式網(wǎng)站建設(shè)公司等網(wǎng)站項(xiàng)目制作,到程序開發(fā),運(yùn)營維護(hù)。創(chuàng)新互聯(lián)于2013年開始到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)。
全新的android.hardware.Camera2 。Android 5.0對(duì)拍照API進(jìn)行了全新的設(shè)計(jì),新增了全新設(shè)計(jì)的Camera 2 API,這些API不僅大幅提高了Android系統(tǒng)拍照的功能,還能支持RAW照片輸出,甚至允許程序調(diào)整相機(jī)的對(duì)焦模式、曝光模式、快門等。
在Camera2 架構(gòu)在核心參與類角色有: CameraManager 、 CameraDevice 、 CameraCharacteristics 、 CameraRequest與CameraRequest.Builder 、 CameraCaptureSession 以及 CaptureResult 。
位于android.hardware.camera2.CameraManager下,也是Android 21(5.0)添加的,和其他系統(tǒng)服務(wù)一樣通過 Context.getSystemService(CameraManager.class ) 或者 Context.getSystemService(Context.CAMERA_SERVICE) 來完成初始化,主要用于管理系統(tǒng)攝像頭:
CameraDevice是Camera2中抽象出來的一個(gè)對(duì)象,直接與系統(tǒng)硬件攝像頭相聯(lián)系。因?yàn)椴豢赡芩械臄z像頭都會(huì)支持高級(jí)功能(即攝像頭功能可被分為limit 和full 兩個(gè)級(jí)別),當(dāng)攝像頭處于limited 級(jí)別時(shí)候,此時(shí)Camera2和早期的Camera功能差不多,除此之外在Camera2架構(gòu)中,CameraDevice還承擔(dān)其他兩項(xiàng)重要任務(wù):
正如前面所說, 系統(tǒng)向攝像頭發(fā)送 Capture 請(qǐng)求,而攝像頭會(huì)返回 CameraMetadata,這一切都是在由對(duì)應(yīng)的CameraDevice創(chuàng)建的CameraCaptureSession 會(huì)話完成 ,當(dāng)程序需要預(yù)覽、拍照、再次預(yù)覽時(shí),都需要先通過會(huì)話。(A configured capture session for a CameraDevice , used for capturing images from the camera or reprocessing images captured from the camera in the same session previously.A CameraCaptureSession is created by providing a set of target output surfaces to createCaptureSession , or by providing an InputConfiguration and a set of target output surfaces to createReprocessableCaptureSession for a reprocessable capture session . Once created, the session is active until a new session is created by the camera device, or the camera device is closed.)CameraCaptureSession一旦被創(chuàng)建,直到對(duì)應(yīng)的CameraDevice關(guān)閉才會(huì)死掉。雖然CameraCaptureSession會(huì)話用于從攝像頭中捕獲圖像,但是只有同一個(gè)會(huì)話才能再次從同一攝像頭中捕獲圖像。另外, 創(chuàng)建會(huì)話是一項(xiàng)耗時(shí)的異步操作,可能需要幾百毫秒 ,因?yàn)樗枰渲孟鄼C(jī)設(shè)備的內(nèi)部管道并分配內(nèi)存緩沖區(qū)以將圖像發(fā)送到所需的目標(biāo),因而createCaptureSession和createReprocessableCaptureSession會(huì)將隨時(shí)可用的CameraCaptureSession發(fā)送到提供的監(jiān)聽器的onConfigured回調(diào)中。如果 無法完成配置,則觸發(fā)onConfigureFailed回調(diào) ,并且會(huì)話將不會(huì)變?yōu)榛顒?dòng)狀態(tài)。最后需要注意的是,如果 攝像頭設(shè)備創(chuàng)建了一個(gè)新的會(huì)話,那么上一個(gè)會(huì)話是被關(guān)閉的,并且會(huì)回調(diào)與其關(guān)聯(lián)的onClosed ,如果不處理好,當(dāng)會(huì)話關(guān)閉之后再次調(diào)用會(huì)話的對(duì)應(yīng)方法那么所有方法將會(huì)跑出IllegalStateException異常。關(guān)閉的會(huì)話清除任何重復(fù)的請(qǐng)求(和調(diào)用了stopRepeating()方法類似),但是在新創(chuàng)建的會(huì)話接管并重新配置攝像機(jī)設(shè)備之前,關(guān)閉的會(huì)話仍然會(huì)正常完成所有正在進(jìn)行的捕獲請(qǐng)求。簡(jiǎn)而言之,在Camera2中CameraCaptureSession承擔(dān)很重要的角色:
描述Cameradevice屬性的對(duì)象,可以使用CameraManager通過getCameraCharacteristics(String cameraId)進(jìn)行查詢。
CameraRequest代表了一次捕獲請(qǐng)求, 而CameraRequest.Builder用于描述捕獲圖片的各種參數(shù)設(shè)置,包含捕獲硬件(傳感器,鏡頭,閃存),對(duì)焦模式、曝光模式,處理流水線,控制算法和輸出緩沖區(qū)的配置。 ,然后傳遞到對(duì)應(yīng)的會(huì)話中進(jìn)行設(shè)置, CameraRequest.Builder則負(fù)責(zé)生成CameraRequest對(duì)象 。當(dāng)程序調(diào)用setRepeatingRequest()方法進(jìn)行預(yù)覽時(shí),或調(diào)用capture()方法進(jìn)行拍照時(shí),都需要傳入CameraRequest參數(shù)。CameraRequest可以通過CameraRequest.Builder來進(jìn)行初始化,通過調(diào)用createCaptureRequest來獲得。
CaptureRequest描述是從圖像傳感器捕獲單個(gè)圖像的結(jié)果的子集的對(duì)象。(CaptureResults are produced by a CameraDevice after processing a CaptureRequest)當(dāng)CaptureRequest被處理之后由CameraDevice生成。
CameraManager 處于頂層管理位置負(fù)責(zé) 檢測(cè)獲取所有攝像頭及其特性 和 傳入指定的CameraDevice.StateCallback回調(diào)打開指定攝像頭 , CameraDevice 是負(fù)責(zé)管理抽象對(duì)象,包括 監(jiān)聽Camera 的狀態(tài)回調(diào)CameraDevice.StateCallback 、 創(chuàng)建CameraCaptureSession和CameraRequest , CameraCaptureSession 用于描述一次圖像捕獲操作,主要負(fù)責(zé) 監(jiān)聽自己會(huì)話的狀態(tài)回調(diào)CameraCaptureSession.StateCallback 和 CameraCaptureSession.CaptureCallback捕獲回調(diào) ,還有 發(fā)送處理CameraRequest ; CameraRequest 則可以看成是一個(gè)"JavaBean"的作用用于描述希望什么樣的配置來處理這次請(qǐng)求;最后三個(gè)回調(diào)用于監(jiān)聽對(duì)應(yīng)的狀態(tài)。
CameraManager 處于頂層管理位置負(fù)責(zé)檢測(cè) 檢測(cè)獲取所有攝像頭并設(shè)置輸出參數(shù),傳入指定的CameraDevice.StateCallback回調(diào),然后打開指定攝像頭,并觸發(fā)CameraDevice.StateCallback中的onOpened方法,并在onOpened方法里開始通過調(diào)用創(chuàng)建預(yù)覽會(huì)話, ,CameraDevice負(fù)責(zé)創(chuàng)建請(qǐng)求 CameraCharacteristics 、 CameraRequest與CameraRequest.Builder 、 CameraCaptureSession 以及 CaptureResult 則可以看成是一個(gè)JavaBean的作用用于描述以什么樣的配置來處理這次請(qǐng)求。
Camera2Helper類只是簡(jiǎn)單的封裝了下,為了讓Camera2的初始化和Activity 高度分離,這個(gè)類只是Demo 階段部分有待優(yōu)化,另外結(jié)合我具體的業(yè)務(wù),對(duì)于圖片大小有限制,所以我都是默認(rèn)采用采樣壓縮率方式對(duì)圖片進(jìn)行壓縮
雖然Camera作為第一代原生android所提供的相機(jī)類一直被開發(fā)者甚至Google官方開發(fā)人員所詬病,但為了兼容和適配Android版本5.0以下的App應(yīng)用,我們別無選擇。因此,有了本篇文檔詳細(xì)闡述1.0版的Camera 是如何使用的。本篇使用的是SurfaceView與Camera類。
文檔下文會(huì)在拍照流程中的不同的階段應(yīng)用到上述四個(gè)角度,而“終端自然方向”貫穿整個(gè)流程當(dāng)中。這一個(gè)方向、四個(gè)角度非常重要,缺一不可,是支撐相機(jī)Camera 系列API的關(guān)鍵。在設(shè)計(jì)NXDesign的相機(jī)項(xiàng)目中,經(jīng)過對(duì)官方文檔的研讀和各路資料的調(diào)研之后發(fā)現(xiàn),我們?cè)诰W(wǎng)絡(luò)上查到的博客類相關(guān)資料有80%的實(shí)現(xiàn)方式是存在問題的,當(dāng)然,這也可以歸咎于該API其本身確實(shí)不好用,如果不對(duì)源碼注釋進(jìn)行仔細(xì)研究,很容易對(duì)開發(fā)者產(chǎn)生誤導(dǎo)。
更加準(zhǔn)確的說,相機(jī)的生命周期是依托于SurfaceView的創(chuàng)建和銷毀來完成的。SurfaceView的作用是提供相機(jī)內(nèi)容的實(shí)時(shí)預(yù)覽。我們需要在surfaceview創(chuàng)建好之后打開相機(jī)使用相機(jī)資源,在surfaceview被銷毀后釋放相機(jī)資源。
surfaceview 提供了holder機(jī)制向調(diào)用方通知surfaceview的變化時(shí)機(jī),為了在不同的時(shí)機(jī)對(duì)相機(jī)資源做不同的事情,需要調(diào)用SurfaceHolder.addCallback()方法。
現(xiàn)在的Android手機(jī)一般會(huì)有多個(gè)攝像頭,但根據(jù)其方向可以歸為兩類: CAMERA_FACING_BACK 和 CAMERA_FACING_FRONT 。在打開攝像頭之前,首先需要獲取相機(jī)資源,判斷相機(jī)個(gè)數(shù) Camera.getNumberOfCameras() 。每個(gè)相機(jī)對(duì)應(yīng)一個(gè)CameraInfo,它的定義如下:
這里涉及到一個(gè)重要概念:相機(jī)圖像傳感器(camera sensor),想要理解上述注釋的含義,就需要先理解下圖內(nèi)容。
左圖是通常情況下,我們對(duì)view的x y方向的認(rèn)知,以屏幕的左上角為原點(diǎn)向右為x正方向,向下為y正方向;但是,右圖描述的是絕大多數(shù)情況下, 相機(jī)圖像傳感器 的起始位置和方向判定。與view不同的是,傳感器以手機(jī)屏幕在自然方向上的右上角為原點(diǎn),向下為x正方向,向左為y正方向。因此,我們理解上述注釋就不難了。如果相機(jī)自帶的傳感器頂部與終端自然方向(手機(jī)屏幕的硬件方向,一般手機(jī)都是豎直方向,也就是文檔中說的naturally tall screen)的右邊緣一致,則這個(gè)值就是90度。如果前置攝像頭傳感器的頂部與手機(jī)自然方向一致,則這個(gè)值就是270度。
當(dāng)我們定義startCamera()方法時(shí),要做5件事情,1.遍歷攝像頭cameraId,找到想要打開的攝像頭(前置還是后置);2.獲取攝像頭信息,主要獲取orientation;3. 設(shè)置相機(jī)DisplayOrientation 4.設(shè)置相機(jī)參數(shù),主要是寬高比、對(duì)焦模式、圖片格式、setRotation等。5. 向camera設(shè)置surfaceview.viewholder,并且startPreview。主要邏輯如下:
拿到cameraInfo.orientation之后,要調(diào)用camera.setDisplayOrientation設(shè)置進(jìn)去,保證通過surfaceview預(yù)覽到的取景跟當(dāng)前的手機(jī)方向保持一致,但是,setDisplayOrientation設(shè)置的其實(shí)是經(jīng)過兩個(gè)角度計(jì)算之后的復(fù)合角度,而并不單純是cameraInfo.orientation。正確的做法是這樣的:先獲取手機(jī)屏幕的旋轉(zhuǎn)方向,然后與cameraInfo.orientation加和得到最終角度。通常情況下,如果我們?cè)O(shè)置相機(jī)為portrait,則不用考慮rotation。這也是為什么絕大部分網(wǎng)絡(luò)資料中都會(huì)粗暴的寫入一個(gè)90度完事兒而并沒有解釋這么做的道理。
調(diào)用camera.takePicture(null, null, pictureCallback)
這里需要做的僅僅是將callback中返回的data存儲(chǔ)為File。需要注意的是,data中會(huì)包含setRotation()方法中的角度信息,因此如果直接使用Bitmap工具類生成bitmap,再進(jìn)行存儲(chǔ)或者展示,生成出來的圖像其實(shí)是缺失了旋轉(zhuǎn)角度的原始方向,這十有八九會(huì)發(fā)生圖像展示角度錯(cuò)誤的情況。因此,需要直接保存,再通過Exif工具類讀取File中的角度信息(當(dāng)然Exif工具類就是為了讀取File中的各種信息而生的,比如拍照時(shí)間、經(jīng)緯度等等)。
基于Camera API,
surfaceview的預(yù)覽需要setDisplayOrientation(),入?yún)⒔嵌扰cCameraInfo.orientation(傳感器偏角)和WindowManager.default.displayOrientation(屏幕旋轉(zhuǎn)角度)兩個(gè)角度有關(guān)。
相機(jī)拍照前需要setRotation(),入?yún)⒔嵌扰cCameraInfo.orientation(傳感器偏角)和OrientationEventListener返回的orientation(終端自然角度偏角)有關(guān),二者的換算結(jié)果就是圖像寫入偏角,該偏角意味著圖像被順時(shí)針旋轉(zhuǎn)該角度就能夠回正展示。
Refrence:
Android 調(diào)用系統(tǒng)相機(jī)拍照適配主要經(jīng)歷了 6.0 7.0 10和11這幾個(gè)大版本:
其中:
常用到的為 external-path 和 external-files-path,name和path按照自己需求編寫
上述示例意思是,external-path標(biāo)簽指向的路徑后path中指向的文件/文件夾擁有被訪問權(quán)限,即 /storage/emulate/0/000 這個(gè)路徑擁有被訪問的權(quán)限。
簡(jiǎn)單示例:
若使用的是vivo手機(jī),相機(jī)無法使用處理方法如下:
1、若軟件無法打開相機(jī),進(jìn)入設(shè)置--應(yīng)用與權(quán)限/更多設(shè)置--權(quán)限管理--找到對(duì)應(yīng)的軟件--開啟相機(jī),在單項(xiàng)權(quán)限設(shè)置--開啟“使用攝像頭”權(quán)限;
2、卸載第三方管家類軟件,如:騰訊手機(jī)管家、360手機(jī)衛(wèi)士等,再嘗試開啟相機(jī)
3、進(jìn)入設(shè)置--應(yīng)用與權(quán)限/更多設(shè)置--應(yīng)用管理/應(yīng)用程序--(全部)--找到相機(jī)--存儲(chǔ)后清除數(shù)據(jù)和緩存,后重啟手機(jī);
4、進(jìn)入設(shè)置--系統(tǒng)管理/更多設(shè)置--備份與重置/恢復(fù)出廠設(shè)置--還原所有設(shè)置/清除所有數(shù)據(jù)(需進(jìn)行備份數(shù)據(jù));
5、固件升級(jí);
6、若以上方法未解決,可攜帶手機(jī)和有效購機(jī)憑證去客戶服務(wù)中心處理,關(guān)注微信公眾號(hào)“vivo”或者“vivo客戶服務(wù)”進(jìn)行查詢服務(wù)中心地址電話,建議去之前電話聯(lián)系,確保有工作人員接待再過去,避免耽誤寶貴時(shí)間白跑一趟。
1.拍照 (對(duì)于7.0以上的版本,不在允許直接訪問uri)
`
若不指定輸出路徑intent.putExtra(MediaStore.EXTRA_OUTPUT, getTempUri(srcActivity)); 在onActivityResult()中,通過
`
可以拿到uri,但獲得的圖片是被壓縮過的。若指定intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);輸出路徑,則此處的intent為null,但可以使用我們存的uri讀取照片,此時(shí)的照片沒有被壓縮。
2.從相冊(cè)中讀取照片, 方法:
`
`
即使設(shè)置 intent.putExtra(MediaStore.EXTRA_OUTPUT, getTempUri(srcActivity));輸出路徑,仍然不能從此路徑中讀取,只能在onActivityForResult()中通過event.uri = intent.getData();方式獲得圖片uri。
此種現(xiàn)象也好理解,拍照時(shí)產(chǎn)生新的圖片,自然可根據(jù)設(shè)置的uri進(jìn)行圖片保存,而讀取相冊(cè)時(shí),圖片已經(jīng)在目錄中不能轉(zhuǎn)移到自己設(shè)定的uri中。
Androidmanifest.xml中
`
在 res/xml/provider_paths.xml
`
?xml version="1.0" encoding="utf-8"?
paths
external-path name="JDTobs" path=""/
files-path name="name" path="path" /
cache-path name="name" path="path" / external-path name="name" path="path" /
external-files-path name="name" path="path" /
external-cache-path name="name" path="path" / /paths `
讀取uri
(1)申請(qǐng)權(quán)限
(2)設(shè)置布局
這里做了一個(gè)簡(jiǎn)單的布局:添加了一個(gè)按鈕和一個(gè)ImageView控件用于顯示拍攝的圖像。
(3)為按鈕添加點(diǎn)擊事件監(jiān)聽
點(diǎn)擊按鈕時(shí),調(diào)用系統(tǒng)相機(jī)進(jìn)行拍照,并在確定后將圖像顯示在ImageView控件中。
(1)申請(qǐng)權(quán)限
(2)設(shè)置布局
添加了一個(gè)按鈕和一個(gè)VideoView控件用于顯示錄制的視頻。
(3)為按鈕添加點(diǎn)擊事件監(jiān)聽
同前面一樣,點(diǎn)擊按鈕后調(diào)用系統(tǒng)相機(jī)進(jìn)行錄制視頻,錄制完成后點(diǎn)擊確定即可將錄制的視頻顯示在VideoView控件中。
對(duì)于Android11.0的版本,在調(diào)用系統(tǒng)相近進(jìn)行視頻錄制的時(shí)候,即使在AndroidMenifest.xml中申請(qǐng)了CAMERA權(quán)限,還是會(huì)在程序運(yùn)行時(shí)報(bào)錯(cuò): Permission? Denial , ? . .... ....? with revoked permission android.permission.CAMERA
解決方法是在程序中動(dòng)態(tài)申請(qǐng)權(quán)限:
寫在最后:文章是在學(xué)習(xí)過程中做的學(xué)習(xí)筆記,同時(shí)與志同道合者分享,文章內(nèi)容均經(jīng)過我自己實(shí)驗(yàn)證實(shí)可行,如有問題歡迎留言,很高興一起交流討論,共同進(jìn)步!