天天看點

Study on Android【五】--自定義ContentProvider的語義

Android做到現在。開始感覺到Android确實還是太年輕。系統本身好不夠成熟,相關文檔更是少的可憐。在Android的旅途中到處是暗坑陷阱,掉進去摔得半死,還隻能靠自己琢磨着爬出來。

想 在大部分工作集中在了定義一套ContentProvider以及上層的顯示控件上。一個ContentProvider向外提供的接口十分有限,但就是 這幾個東西你要表征出正确(标準就是和系統ContentProvider一緻的行為)的語義,還是很費功夫的。至少我就摔了好幾個跟頭。為了降低後續部 隊的傷亡,我努力搬走幾個絆腳石、填掉幾個坑,希望能有一些作用。

ContentProvider中,最重要的就是query操作。query根 據輸入傳回一個符合條件的Cursor。這就可能出現以下幾種情況:1. 查詢成功,包含幾個正确的結果;2. 查詢失敗,沒有符合的結果;3. 輸入錯誤, 觸發了某個異常;4. 沒能查詢到結果,但無法确定是輸入錯誤還是查詢失敗。第一種情況是我們最需要的,當然是需要正确維系的,而最後一種情況在大部分應用中應該不會出現(但在 我的應用中會的*_#),而第二種第三種是比較常見的。

經過我的測試,系統的ContentProvider維持這樣的語義:如果是情況2,傳回 正常的Cursor,并且,其count為0,相當于empty cursor;如果是情況3,不抛出任何異常,傳回null的Cursor。這樣的話明明白白寫出來是很好了解的,但由于沒有官方的文檔說明,在自定義的 時候經常會誤用。比如在某些情況下,用null表征查詢失敗,用抛出異常來描述錯誤的輸入。

傳回empty cursor,如果是通過databasecursor自然會有db幫你維護,但是如果傳回ArrayListCursor,MergeCursor或其 他自定義的Cursor,就需要自己維系了。ArrayListCursor可以通過new ArrayListCursor(Columns, new ArrayList(){})來提供。其中Columns一定不為null。MergeCursor不能以new MergeCursor(new Cursor[]{})來建立,而需要通過new MergeCursor(new Cursor[]{aEmptyCursor, ...}來維系(其實很好了解,我呆了...)。自定義的Cursor也一定要提供生成empty cursor的方式。

如果将ContentProvider作為一個單獨的module來了解,不通過異常而是通過null來傳回MS是有好處 的。在module的出口吃掉所有異常,雖然不能提供足夠的資訊(異常資訊全部寫入日志),但可能會使上層使用更簡單。但在Android中,我并沒有感 覺到這一點。作為ContentProvider的上層函數,ListActivity.managedQuery、 ListView.setListAdapter等,根本不能處理一個null的Cursor,在ListView中這會觸發一個異常。更無語的是,當你 把一個null Cursor設定為manage的後。它不會立即抛異常,而是在OnFreeze等生命周期函數的時候,因無法處理null Cursor而抛出一個異常。這使得你根本無法在當地catch該異常,換句話,ListActivity的manageCursor根本是個無法使用的函數。你必須用getContext().query()獲得Cursor,然後判定該Cursor是否null,在進行startManagingCursor進行綁定。這遠不如直接用異常進行錯誤路徑的處理來的統一和友善。

當然,有些東西我們是不能改變的,隻能去适應。對于自定義的cursor, ContentProvider,最重要的,是在無人造錯誤輸入的情況下傳回empty cursor,而不是null。至于使用null響應還是異常響應上,我個人覺得還是和系統同步為好,雖然别扭,但至少統一不容易有歧義。

此外,ContentProvider還有很多細緻的語義。比如傳回的Cursor需要綁定一個URI,以便自動響應更新。自定義的更新需要支援deleteRow等操作語義等等。總之,我們需要更好的文檔或更多經驗文檔,以便我們更好的爬上巨人的肩膀。

PS:而上層的ListView,更是陷阱重重。首先綁定到ListView的Cursor必須有_id項,否則會有異常抛出。如果做過.net的開發, 這一點是可以想到的,但是,這種問題應該在文檔中寫明。另外,在ListView中,如果你不綁定一個資料源,你一定不能在layout中添加涉及内容的 屬性。比如android:height="wrap_content",這會在onMeasure的時候抛出異常。