修正Android攝像頭API

chris發表於2014-08-15

這幾天本人參加了一個公司舉辦的程式設計馬拉松,我打算使用Android攝像頭來做。我一直都認為Android的API很糟糕,但是沒有詳細說出哪些地方糟糕,也沒有說怎麼改進會更好。趁這個機會,現在我就來解釋解釋。

我認為,Android關於攝像頭的API非常糟糕,如果你沒有用過,那麼自己花點時間看看去吧。使用這個Camera API的時候,經常會使開發者使用錯誤,會導致開發者忽略很多重要的東西,然後出問題了也很難發現,甚至在StackOverFlow上也很不樂觀。換句話說,如果有API需要你讀完十個步驟(還有幾個標記為“重要”的),那麼,其實它是有問題的,怎麼會寫出這麼麻煩的API呢?我們要改進它。

所以我打算重新構造它。我把重構後的程式碼放在GitHub上( EasyCamera),以下列出了我改的地方,還有修改的理由:

  1. 在使用startPreview()方法之前,需要先呼叫setPreviewDisplay(..)方法,然後再進行拍照的時候又需要呼叫它。為什麼不改進一下呢?如果不執行這個方法的話就會出問題,我們可以簡單地丟擲一個異常“Preview display not set”,但是這樣終究不太好。所以,這個setPreviewDisplay(..)可以不要了,我們直接讓startPreview(..)帶上surface作引數,然後還可以過載它讓SurfaceTexture也作為引數傳遞進去。

  2. 在改進了預覽設定後,現在開始改進預覽操作。如果在拍照的時候,沒有呼叫startPreview這個方法,那也會出問題,這裡也可以簡單丟擲一個異常“Preview not started”,但是要知道,這個異常是在執行時丟擲的。所以,takePicture(..)這個方法也要改進,把它移到Camera這個類外面,放到CameraAction類裡。其它的需要在preview呼叫後才能使用的方法也需要這樣改進(我暫時不知道有哪一些,當前的API裡也沒有列出來)。接下來就是怎麼獲取一個CameraAction類例項了,很簡單,直接呼叫startPreview(..)就可以返回一個CameraAction了。

  3. 目前我們的改進只是讓使用方法更加直觀,然後減少出錯的情況。不過takePicture這個方法好像有點奇怪,可以給它的引數全部傳null值,但是裡面還有兩個重寫的方法需要這些引數。理論上說,傳null值也可以,但是還有別的法子解決這個問題。其一就是介紹一個介面叫PictureCallback,它有四個可以呼叫的方法,然後還提供一個預設的實現類,叫BasePictureCallback。當然,你會發現,在這個場景中,好像也不太合適,因為這個例子中你傳遞這個callback介面的話,什麼事都不會發生,但是傳遞null值的話,就不一樣了(會出問題),但是在我的手機上測試的時候,我傳遞一個shutter callback時,快門聲音就會響,而傳遞null值就不會響。所以,這裡主要介紹的就是使用這個callback介面,帶有一個實現類,包含所有你需要的所有方法。

  4. 到目前為止,改的還不錯,還有就是,在照相完成後,需要restart preview,但是預設讓它自動restart也不是很好。目前的情況是,我們只有一個CameraAction類,然後呼叫startPreview(..)方法需要傳遞一個surface引數,是不是需要引進一個restartPreview()方法呢?當然不用,這裡我們讓這個介面類的方法都返回一個布林值,如果需要restart preview,就可以返回true,這樣會比較好,然後又有一個問題,如果這個callback類沒有這4個方法呼叫呢,依賴這4個方法來得出是否需要restart preview終究不好,所以,還需要在callback實現類中加一個restartPreview屬性,只有在最後一個方法被呼叫時這個屬性才會被設定成true。

  5. 主要的步驟已經改進好了,還有其它的一些小問題需要改進,比如,關閉和開啟camera的方法是不對稱的,“open”和“release”配對感覺不好,應該是“open”和“close”或者“acquire”和“release”。所以我覺得用“close”更好一些,況且(如果使用java7),可以好好利用AutoClosable介面,還有try-with-resource結構(詳情請檢視java7新特性)。

  6. 在呼叫getParameter()方法的時候可以得到一份引數的副本,然後,你可能需要把更改過的這個副本設定回去,也是挺合理的。如果能提供一個camera.setParameter(..)方法,可能使用起來會方便一些,不過Parameter類已經提供了很多方法了,所以在Camera類裡面新增不太好。或許這裡可以設定可變引數?(當然現在也還沒實現)

  7. 這個Camera的API糟糕的原因之一還有它的錯誤反饋,基本上可能得到一個報錯資訊“Came.takePicture failed”,無論什麼原因導致的,通過以上的這些步驟,在某些情況下會過濾掉一些我們需要的異常資訊,不過最好還是應該得到精確的報錯資訊。

  8. 我們的目的就是要讓Camera使用更加友好(現在還不是),所以EasyCamera是一個容易使用的介面,CameraActions也是一個可造性很好的類。

本人的這個EasyCamera工程目前只是一個初版,也還沒有用於實際生產環境中,但是我也希望得到大家的寶貴意見,爭取把它做得更好,不斷改進它。有的情況下,它也可以包含其它一些攝像頭的功能,比如前後攝像頭切換等等。

蹩腳的API會讓開發者浪費大量的時間,金錢和精力,所以在設計API的時候,需要某些特殊的技能,還要多進行思考。Josh Bloch的關於API設計的見解很中肯,我強烈推薦它。事實上我也違反了其中一條-“if in doubt, leave it out”-通過CameraAction這個類我們可以完全掌控Camera,所以也有可能有很多操作是沒用的,因為我對所有Camera的屬性也不是很瞭解,所以在程式碼裡我也不會限制使用者去做一些其它的操作。

一開始我寫這篇文章的時候,其實我也沒打算寫EasyCamera這個東西,而事實上我是花了兩個小時把它寫出來了。最後,我建議所有開發者們,在你遇到一些蹩腳的API時,可以像以上操作一樣,自己動手做一些改造,多思考怎麼設計和實現它們,然後你會發現這樣比你慢慢修改和封住更省事,然後用起來也就得心應手了。

相關文章