1.前言
-
1.1 初衷
在我們做很多項目的過程中,經(jīng)常會遇到很多需要自定義的相機(jī)的需求,這個時候,很多人第一步都是網(wǎng)上查資料,包括我也是這樣,但是我沒有發(fā)現(xiàn)有比較靠譜沒有Bug的開源項目,雖然這個需求也不是很難,但是由于android市場的碎片化,各機(jī)型的適配也是很頭疼,一步一步去寫難免會踩到不少的坑,所以,我打算長期維護(hù)這個項目,有問題的歡迎提交Issues,以便于我完善這個開源項目。
-
1.2 簡單介紹
該開源項目整體由Builder模式編寫,方便后期擴(kuò)展,支持鏈?zhǔn)秸{(diào)用。
目前可支持的自定義擴(kuò)展項:
1、相機(jī)拍攝方式:拍照or錄制
2、拍攝質(zhì)量
3、拍攝保存路徑、文件名
4、可預(yù)覽的imageView
5、拍照分辨率
6、錄制分辨率
-
1.3 開源地址:
Github地址:一個靈活配置的自定義相機(jī)庫(拍照+錄制視頻)
歡迎大家指正錯誤,和提出問題,如覺得幫助到了你希望能夠star和fork。。感激不盡,希望能夠幫到你。。
2.準(zhǔn)備
-
2.1 了解SurfaceView
2.1.1 簡介
通常情況程序的View和用戶響應(yīng)都是在同一個線程中處理的,這也是為什么處理長時間事件(例如訪問網(wǎng)絡(luò))需要放到另外的線程中去(防止阻塞當(dāng)前UI線程的操作和繪制)。但是在其他線程中卻不能修改UI元素,例如用后臺線程更新自定義View(調(diào)用View的在自定義View中的onDraw函數(shù))是不允許的。
如果需要在另外的線程繪制界面、需要迅速的更新界面或則渲染UI界面需要較長的時間,這種情況就要使用SurfaceView了。SurfaceView中包含一個Surface對象,而Surface是可以在后臺線程中繪制的。Surface屬于OPhone底層顯示系統(tǒng),SurfaceView的性質(zhì)決定了其比較適合一些場景:需要界面迅速更新、對幀率要求較高的情況。
SurfaceView的核心提供了兩個線程:UI線程和渲染線程。應(yīng)該注意的是:
a.所有的SurfaceView和SurfaceHolder.Callback的方法都應(yīng)該在UI線程里調(diào)用,一般來說就是應(yīng)用程序的主線程。渲染線程所要訪問的各種變量應(yīng)該做同步處理。
b.由于surface可能被銷毀,它只在SurfaceHolder.Callback.surfaceCreated()和SurfaceHoledr.Callback.surfaceDestroyed()之間有效,所以要確保渲染線程訪問的是合法有效地surface.
2.1.2 SurfaceView類 和View類的區(qū)別:
SurfaceView 和View的最本質(zhì)的區(qū)別在于,surfaceView是在一個在新起的單獨(dú)線程中可以重新繪制畫面,而View必須在UI的主線程中更新畫面。那么在UI的主線程中更新畫面,可能會引發(fā)問題,比如你更新畫面的時間過長,那么你的主UI線程會被你正在畫的函數(shù)阻塞,那么將無法響應(yīng)按鍵,觸摸等消息。當(dāng)使用surfaceView由于是在新的線程中更新畫面所以不會阻塞你的UI主線程,但是這也會有另外一個問題,就是事件同步。比如你觸屏了一下,你需要surfaceView中thread處理,一般就需要有一個event queue的設(shè)計來保存touch event,這樣就會有點(diǎn)復(fù)雜了。
View:必須在UI的主線程中更新畫面,用于被動更新畫面。
surfaceView:UI線程和子線程中都可以。在一個新啟動的線程中重新繪制畫面,主動更新畫面。
所以在游戲的應(yīng)用上,根據(jù)游戲的特點(diǎn),一般分為兩類:
a. 被動更新畫面的。比如棋類,這種用view就好。因為畫面的跟新依賴于onTouch來更新,可以直接使用invalidate.因為這種情況下,這一次Touch和下一次Touch需要的時間比較長些,不會產(chǎn)生
影響。
b.主動更新:比如一個人在一直跑動。這就需要一個單獨(dú)的thread不停地重繪人的轉(zhuǎn)臺,避免阻塞mian UI Thread 。所以顯然view 不適合,需要surfaceView來控制。
-
2.2理解Builder模式
其實在我們在編寫程序的很多時候,都會使用Builder模式,如v7包自帶的AlertDialog的實現(xiàn)、OkHttpClient的參數(shù)配置等等。
Builder模式也就是建造者模式,主要用于將一個復(fù)雜的對象分離,通過不同的構(gòu)造(不同的參數(shù)值)去構(gòu)建不同的對象,在使用的時候隱藏構(gòu)造過程和細(xì)節(jié),用戶不需要知道內(nèi)部實現(xiàn)過程,方便用戶創(chuàng)建復(fù)雜的對象
2.2.1 優(yōu)缺點(diǎn):
- 優(yōu)點(diǎn)
1.易于解耦
將產(chǎn)品本身與產(chǎn)品創(chuàng)建過程進(jìn)行解耦,可以使用相同的創(chuàng)建過程來得到不同的產(chǎn)品。也就說細(xì)節(jié)依賴抽象。
易于精確控制對象的創(chuàng)建
將復(fù)雜產(chǎn)品的創(chuàng)建步驟分解在不同的方法中,使得創(chuàng)建過程更加清晰
2.易于拓展
增加新的具體建造者無需修改原有類庫的代碼,易于拓展,符合“開閉原則“。
每一個具體建造者都相對獨(dú)立,而與其他的具體建造者無關(guān),因此可以很方便地替換具體建造者或增加新的具體建造者,用戶使用不同的具體建造者即可得到不同的產(chǎn)品對象。 - 缺點(diǎn)
建造者模式所創(chuàng)建的產(chǎn)品一般具有較多的共同點(diǎn),其組成部分相似;如果產(chǎn)品之間的差異性很大,則不適合使用建造者模式,因此其使用范圍受到一定的限制。
如果產(chǎn)品的內(nèi)部變化復(fù)雜,可能會導(dǎo)致需要定義很多具體建造者類來實現(xiàn)這種變化,導(dǎo)致系統(tǒng)變得很龐大。
3.編碼
-
3.1 自定義SurfaceView的實現(xiàn)和設(shè)計
3.1.1 自定義SurfaceView實現(xiàn)
編寫一個類CameraSurfaceView繼承surfaceView并實現(xiàn)SurfaceHolder.Callback接口,實現(xiàn)方法中包含了surfaceView生命周期的幾個方法:創(chuàng)建時候調(diào)用(surfaceCreated)、頁面更新時候調(diào)用(surfaceChanged)、surfaceView銷毀時候調(diào)用(surfaceDestroyed)。
由于我的需求事在直接使用cameraSufaceView時候就需要鏈?zhǔn)脚渲盟械膮?shù),所以我們將要在cameraSufaceView中寫一個Builder的靜態(tài)內(nèi)部類。
然后考慮到解耦性,我們單獨(dú)寫一個零件類(需要靈活配置的參數(shù)),包含了我們一開始提到的拍攝方式、質(zhì)量、保存地址等等參數(shù)
在Builder內(nèi)部類中,把所有的零件配置和獲取暴露提供給外部,最后調(diào)用某個方法(項目中是startCamera())開始組裝零件變成一個完整的產(chǎn)品。 -
3.2 輔助類的編寫思路
3.2.1 編寫前的思考
我們編寫這個類的主要目的是區(qū)別與SurfaceView內(nèi)部代碼,把所有邏輯代碼都分離出來,各司其職,這樣以后邏輯有改動不會過多的牽扯到view層,這和mvp的優(yōu)點(diǎn)類似。
那么,這個輔助類應(yīng)該具備什么樣的功能呢?
當(dāng)然首先會有一個方法去綁定SurfaceView用于初始化數(shù)據(jù),然后會有方法去一一對應(yīng)SurfaceView生命周期的幾個方法來創(chuàng)建camera、適配屏幕預(yù)覽、銷毀camera等。其中camera的創(chuàng)建應(yīng)該是一個單例,因為系統(tǒng)同時只能同時存在一個可操作的camera,除了這些,還有一系列的其他輔助方法,如矯正拍照角度、自適應(yīng)預(yù)覽畫面、錄制配置、拍照配置等等
這個輔助類應(yīng)該有些什么特征呢?
首先,由于我們會在SurfaceView內(nèi)部的生命周期方法里面和Activity/Fragment中使用到這個操作邏輯的輔助類,所以,我打算把它寫成單例,在任何位置操作都可以控制。
其次,輔助類中應(yīng)有對應(yīng)SurfaceView生命周期邏輯的方法去控制camera對象實例,如在SurfaceView創(chuàng)建(surfaceCreated)的時候去創(chuàng)建Camera對象,綁定SurfaceView的Holder并開始預(yù)覽,在SurfaceView更新的時候(surfaceChanged)去適應(yīng)相機(jī)方向和預(yù)覽方位防止變形和方向錯亂,在SurfaceView銷毀(surfaceDestroyed)的時候去釋放Camera實例。3.2.2 處理異常
在我們編寫過程中,某些地方可能會出現(xiàn)不可預(yù)知的錯誤,這個時候我們要通知到View層去提示用戶異常信息,所以這里我們要用到一個接口去供View層去實現(xiàn),通過這個接口去通知View層出現(xiàn)了某些異常。
-
3.3 運(yùn)行時權(quán)限的處理
在Android6.0(API級別 23)中,在使用到部分危險權(quán)限時候,會向系統(tǒng)申請權(quán)限才能正常使用某些功能,所以我們要針對權(quán)限進(jìn)行適配,這里我推薦PermissionsDispatcher,該庫還針對xiaomi做了專門的適配。
3.3.1 PermissionsDispatcher介紹
PermissionsDispatcher是一個基于注解、幫助開發(fā)者簡單處理Android 6.0系統(tǒng)中的運(yùn)行時權(quán)限的開源庫。避免了開發(fā)者編寫大量繁瑣的樣板代碼。
開源地址:https://github.com/hotchemi/PermissionsDispatcher
文檔介紹:http://hotchemi.github.io/PermissionsDispatcher/使用方法可以參見github地址給出的步驟。
4.遇到的問題
5.結(jié)語
-
5.1 該自定義庫優(yōu)點(diǎn)
a.可以大大減少Activity/Fragment的邏輯代碼,增強(qiáng)可讀性
b.完全發(fā)揮了Builder設(shè)計模式的優(yōu)點(diǎn),可配置性和靈活性得到了提升
c.解決了大部分開發(fā)者遇到的問題(圖像變形、方向錯亂、位置異常等),若有其他問題歡迎大家提Issues,我看到了會盡快修復(fù)~ -
5.2 參考