有了framework後,我們不用面對赤裸裸的OS API,做一些重複而繁雜的事情。但天下沒有免費的午餐,我們還是需要學會高效正確的使用不同的framework,很多處理某一特定問題的手法在不同的framework中,用起來都會有所不同的。
在Android中,下層是Linux的核,但上層的java做的framework把這一切封裝的密不透風。以訊息處理為例,在MFC中,我們可以用PreTranslateMessage等東東自由處理訊息,在C#中,Anders Hejlsberg老大說了,他為我們通向底層開了一扇“救生窗”,但很遺憾,在Android中,這扇窗戶也被關閉了(至少我現在沒發現...)。
在Android中,你想處理一些訊息(比如:Keydown之類的...),你必須尋找Activity為你提供的一些重載函數(比如
onKeyDown之類的...)或者是各式各樣的listener(比如OnKeyDownListner之類的...)。這樣做的好處是顯而易見的,
越多的自由就會有越多的危險和越多的晦澀,條條框框畫好了,用起來省心看起來省腦,這是一個設計良好的framework應該提供的享受。對於我目前的工
程而言,我沒有什麼BT的需求在當前API下做不到的,google的設計ms還是很nice的。
但世界是殘酷的,有的時候我們還是必須有機制提供訊息的分發和處理的,因為有的工作是不能通過直接調用來同步處理的,同時也不能通過Activity中內
嵌的訊息分發和介面設定來做到,比如說事件的定時觸法,非同步迴圈事件的處理,高耗時的工作等等。在Android中,它提供了一些蠻有意思的方式來做這
件事情(不好意思,我見不多識不廣,我沒見過類似玩法,有見過的提個醒 &&
嘴下超生^_^),它有一個android.os.Handler的類,這個類接受一個Looper參數,顧名思義,這是一個封裝過的,表徵訊息迴圈的
類。預設情況下,Handler接受的是當前線程下的訊息迴圈執行個體,也就是說一個訊息迴圈可以被當前線程中的多個對象來分發,
來處理(在UI線程中,系統已經有一個Activity來處理了,你可以再起若干個Handler來處理...)。在執行個體化一個
handlerInstance之後,你可以通過sendMessage等訊息發送機制來發送訊息,通過重載handleMessage等函數來分發消
息。但是!該handlerInstance能夠接受到的訊息,只有通過handlerInstance.obtainMessage構造出來的訊息(這
種說法是不確切的,你也可以手動new一個Message,然後配置成該handlerInstance可以處理的,我沒有跟進去分析其識別機制,有興趣
的自己玩吧^_^)。也就是說A, B, C, D都可以來處理同一線程內的訊息分發,但各自都只能處理屬於自己的那一份訊息,
這抹殺了B想偷偷進入A領地,越俎代庖做一些非份之事的可能(從理論上看,B還是有可能把訊息偽裝的和A他們家的一樣,我沒有嘗試挑戰一下google的
智商,有BT需求的自行研究^_^)。這樣做,不但兼顧了靈活性,也確保了安全性,用起來也會簡單,我的地盤我做主,不用當心傷及無辜,左擁右抱是一件很
開心的事情。。。
很顯然,訊息寄件者不局限於自己線程,否者只能做一些定時,延時之類的事情,豈不十分無趣。在執行個體化Handler的時候,Looper可以是任意線程
的,只要有Handler的指標,任何線程也都可以sendMessage(這種構造方式也很有意思,你可以在A線程裡面傳B線程的Looper來構造
Handler,也可以在B線程裡構造,這給記憶體管理的方法帶來很大的變數...)。但有條規則肯定是不能破壞的,就是非UI線程,是不能觸碰UI類的。
在不同平台上有很多解決方式(如果你有多的不能再多的興趣,可以看一下很久很久以前我寫的一個,不SB不要錢)。我特意好好跟了一下android中的AsyncQueryHandler類,來瞭解google官方的解決方案。
AsyncQueryHandler是Handler的子類,文檔上說,如果處理ContentProvider相關的內容,不用需要自行定義一套東西,
而可以簡單的使用async方式。我想指代的就應該是AsyncQueryHandler類。該類是一個典型的模板類,為ContentProvider
的增刪改查提供了很好的介面,提供了一個解決架構,final了一些方法,置空了一些方法。通過派生,執行個體化一些方法(不是每個對
ContentProvider的處理多需要全部做增刪改查,我想這也是該類預設置空一些方法而不是抽象一些方法的原因),來達到這個目的。在內部,該類
隱藏了多執行緒的細節,當你使用時,你會感覺異常便利。以query為例,你可以這麼來用:
// 定義一個handler,採用的是匿名類的方式,只處理query,因此只重寫了onQueryComplete函數:
queryHandler = new AsyncQueryHandler(this.getContentResolver()){ // 傳入的是一個ContentResolver執行個體,所以必須在OnCreate後執行個體化該Handler類
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
// 在這裡你可以獲得一個cursor和你傳入的附加的token和cookie。
// 該方法在當前線程下(如果傳入的是預設的Looper話),可以自由設定UI資訊
}
};
// 調用時只需要調用startQuery(int token, Object cookie, ContentURI uri,
String[] projection, String selection, String[] selectionArgs, String
sortOrder)函數即可:
queryHandler.startQuery(token, cookie, uri, projection, selection, selectionArgs, sortBy);
可見,該類的使用是多麼簡單(其實現可不會很容易,因為我嘗試做了一次造車輪的工作*_*),比直接用Handler簡單無數倍。但讓我倍感孤獨的是,不
知道是沒人做非同步ContentProvider訪問,還是這個類使用太過於弱智(這個使用方法可是我摸索了半天的啊,難道我真的如此的弱@_@),抑
或是大家都各有高招,從SDK到網上,沒有任何關於該類的有點用的說明。而我又恰巧悲傷的發現,這個類其實有很多的問題,比如他吃掉異常,有錯誤時只是簡
單的返回null指標(這個其實不能怪他,你可以看看這裡...);當你傳一個null的ContentResolver進去的時候,沒有任何異常,只是莫名其妙的丟棄所有訊息,使你陷入苦苦的等待而不知其因;更憤慨的是,他的token傳遞竟然有Bug(難
道還是我使用不對&_&),從startXX傳入的token,到了onXXComplete裡面一律變成1,而文檔上明明寫著兩個是一
個東西(我的解決方案是用cookie做token,這個不會丟*_*)。不過我暫時還沒有遺棄它的打算,雖然沒人理睬,雖然有一堆問題,雖然我按圖索驥
造了個新輪子,但為了節省剩下的一些無聊的工作,我決定苟且偷生了。。。
還是習慣性跑題了,其實,我是想通過我對這個類的無數次Debugger跟進,說說它的多線程非同步處理的解決方案策略的。他的基本策略如下:
1. 當你執行個體化一個AsyncQueryHandler類時(包括其子類...),它會單件構造一個線程(後面會詳述...),這個線程裡面會構建一個訊息迴圈。
2. 獲得該訊息迴圈的指標,用它做參數執行個體化另一個Handler類,該類為內部類。至此,就有了兩個線程,各自有一個Handler來處理訊息。
3. 當調用onXXX的時候,在XXX函數內部會將請求封裝成一個內部的參數類,將其作為訊息的參數,將此訊息發送至另一個線程。
4. 在該線程的Handler中,接受該訊息,並分析傳入的參數,用初始化時傳入的ContentResolver進行XXX操作,並返回Cursor或其他傳回值。
5. 構造一個訊息,將上述傳回值以及其他相關內容綁定在該訊息上,發送回主線程。
6. 主線程預設的AsyncQueryHandler類的handleMessage方法(可自訂,但由雩都是內部類,基本沒有意義...)會分析該訊息,並轉寄給對應的onXXXComplete方法。
7. 使用者重寫的onXXXComplete方法開始工作。
這就是它偷偷摸摸做過的事情,基本還是很好理解的。我唯一好奇的是它的線程管理方式,我猜測他是用的單件模式。
第一個AsyncQueryHandler的執行個體化會導致建立一個線程,從此該線程成為不死老處男,所有的ContentResolver相關的工作,都
由該線程統一完成。個人覺得這種解決方式很贊。本來這個線程的生命週期就很難估量,並且,當你有一個ContentProvider的請求的時候,判斷你
會做更多的類似操作並不過分。就算錯了,花費的也只是一個不死的線程(與進程同生死共存亡...),換來的卻是簡單的生命週期管理和無數次線程生死開銷的
節約。同時另外一個很重要的問題,他並會涉及到單件中資料同步的問題,每個類都有各自的Handler類,彼此互不干擾,分發可以分別進行。當多個資料請
求的時候,在同一個ContentResolver上進行的可能微乎其微,這就避免了堵塞。總而言之,這套解決辦法和Android的整體設計算是天作之
合了。
所以建議,如果你有什麼非ContentProvider操作,卻需要非同步多線程執行的話,類比一套,是個不錯的策略,當然,具體情況具體分析,生搬硬套是學不好馬列主義的。。。