標籤:
寫一個listview容易,寫一個adapter容易,自己new一個線程過濾資料也容易,但是如何將過濾的效率發揮到最大化,不得不提一下android內建的filter類。
有同學肯定要問,過濾資料自己寫一個完全沒問題,為什麼要用android內建的filter類?我原來也是自己寫線程過濾,然而最近項目中遇到一個低配機,雙核0.8GCPU,過濾效果實在是卡頓厲害,最佳化起見,使用了android內部filter試一下效果,結果真是比自己寫的好用,於是認真學習了下源碼,從頭至尾備忘如下:
private static final String LOG_TAG = "Filter"; private static final String THREAD_NAME = "Filter"; private static final int FILTER_TOKEN = 0xD0D0F00D; private static final int FINISH_TOKEN = 0xDEADBEEF; private Handler mThreadHandler; private Handler mResultHandler; private Delayer mDelayer; private final Object mLock = new Object();
View Code
其實用到的全域變數只有8個,並且有一半是常量:兩個handler,一個delayer,一個對象鎖。google開發大牛用這幾個變數加上為數不多的幾個局部變數就做出來了一個拓展性極佳的過濾器,不得不讓人欽佩。
首先看構造方法:
public Filter() { mResultHandler = new ResultsHandler(); }View Code
構造方法中二小強之一——ResultsHandler已經被建立了,顧名思義處理過濾操作結果。我們看看這個類的定義:
private class ResultsHandler extends Handler { /** * <p>Messages received from the request handler are processed in the * UI thread. The processing involves calling * {@link Filter#publishResults(CharSequence, * android.widget.Filter.FilterResults)} * to post the results back in the UI and then notifying the listener, * if any.</p> * * @param msg the filtering results */ @Override public void handleMessage(Message msg) { RequestArguments args = (RequestArguments) msg.obj; publishResults(args.constraint, args.results); if (args.listener != null) { int count = args.results != null ? args.results.count : -1; args.listener.onFilterComplete(count); } } } /** * <p>Holds the arguments of a filtering request as well as the results * of the request.</p> */ private static class RequestArguments { /** * <p>The constraint used to filter the data.</p> */ CharSequence constraint; /** * <p>The listener to notify upon completion. Can be null.</p> */ FilterListener listener; /** * <p>The results of the filtering operation.</p> */ FilterResults results; } /** * @hide */ public interface Delayer { /** * @param constraint The constraint passed to {@link Filter#filter(CharSequence)} * @return The delay that should be used for * {@link Handler#sendMessageDelayed(android.os.Message, long)} */ long getPostingDelay(CharSequence constraint); }View Code
注意到主要的操作就是:ResultsHandler接收到訊息以後,調用抽象的publishResults()方法供UI線程更新畫面。
接下來直接看主力的filter方法:
public final void filter(CharSequence constraint, FilterListener listener) { synchronized (mLock) { if (mThreadHandler == null) { HandlerThread thread = new HandlerThread( THREAD_NAME, android.os.Process.THREAD_PRIORITY_BACKGROUND); thread.start(); mThreadHandler = new RequestHandler(thread.getLooper()); } final long delay = (mDelayer == null) ? 0 : mDelayer.getPostingDelay(constraint); Message message = mThreadHandler.obtainMessage(FILTER_TOKEN); RequestArguments args = new RequestArguments(); // make sure we use an immutable copy of the constraint, so that // it doesn‘t change while the filter operation is in progress args.constraint = constraint != null ? constraint.toString() : null; args.listener = listener; message.obj = args; mThreadHandler.removeMessages(FILTER_TOKEN); mThreadHandler.removeMessages(FINISH_TOKEN); mThreadHandler.sendMessageDelayed(message, delay); } }View Code
官方文檔介紹這個每次調用這個方法將會開啟一個非同步過濾操作,調用這個方法會取消之前沒有執行的過濾操作,讓我們分析一下他們是如何來做的。
首先整個方法都在同步塊synchronized (mLock){}中,意圖很直接:其他的方法我不管,排隊隊,吃果果,你一個,我一個,你下一個filter方法調用必須等我這次filter方法調用結束。接著呢,建立了一個HandlerThread,這個HandlerThread大家可以自行查看源碼,實際上就是自備一個Handler的維他命——Looper,有了Looper,可以初始化我們的filter二小強之二(處理請求操作):mThreadHandler = new RequestHandler(thread.getLooper());然後獲得一個Message message = mThreadHandler.obtainMessage(FILTER_TOKEN);最後移除掉之前帶有FILTER_TOKEN和FINISH_TOKEN的標記(因為後面的操作很有耗時的,你不曉得到底是執行到什麼狀態了),這樣保證了這個方法執行以後,所有執行過濾的操作肯定只有一個message在傳遞了
這個方法的妙處在,方法本身很簡單,幾乎不耗時,即使你不斷地調用filter()方法,程式始終能保證只有一個message在做過濾操作。
接下來看看這個RequestHandler類;
private class RequestHandler extends Handler { public RequestHandler(Looper looper) { super(looper); } /** * <p>Handles filtering requests by calling * {@link Filter#performFiltering} and then sending a message * with the results to the results handler.</p> * * @param msg the filtering request */ public void handleMessage(Message msg) { int what = msg.what; Message message; switch (what) { case FILTER_TOKEN: RequestArguments args = (RequestArguments) msg.obj; try { args.results = performFiltering(args.constraint); } catch (Exception e) { args.results = new FilterResults(); Log.w(LOG_TAG, "An exception occured during performFiltering()!", e); } finally { message = mResultHandler.obtainMessage(what); message.obj = args; message.sendToTarget(); } synchronized (mLock) { if (mThreadHandler != null) { Message finishMessage = mThreadHandler.obtainMessage(FINISH_TOKEN); mThreadHandler.sendMessageDelayed(finishMessage, 3000); } } break; case FINISH_TOKEN: synchronized (mLock) { if (mThreadHandler != null) { mThreadHandler.getLooper().quit(); mThreadHandler = null; } } break; } } }View Code
其實除了filter方法,主要的高端邏輯都在這裡,試想一個情境,如果我連續調用了五次filter,這5個msg的第1個已經被remove掉了,但是由它調用的performFiltering()耗時任務還在進行,2,3,4肯定排隊過程中就被第5個msg幹掉了,然後第1個msg過濾操作完成,給ResultsHandler發送訊息更新UI,然後給RequestHandler發一個三秒延時訊息,
接著執行msg5,這時候再調用一次filter,msg1和msg5就被幹掉了,然後同樣是執行完由msg5調用的performFiltering()再執行msg6,然後是msg5和msg6最終都產生一個延時訊息,msg5產生的延時訊息把mThreadHandler銷毀了,msg6產生的延時訊息到的時候,不做任何操作。
前面我們在filter()中看到過一次synchronized (mLock){},而在這個handler中有兩個同步代碼塊,case FINISH_TOKEN中的同步代碼塊保證了,如果剛剛在filter中建立了mThreadHandler(這是UI線程的操作),會立刻移除所有之前的延遲訊息,從而不會在此處去銷毀掉mThreadHandler導致null 指標異常。 case FILTER_TOKEN中的同步代碼塊的作用,目前還是想不出來到底有什麼用,因為case FILTER_TOKEN和case FINISH_TOKEN本來就是隊列執行,不存在爭用鎖的情況,所以不會存線上程間的時空差導致的null 指標,有高手能看出作用還望不吝賜教。
好了,來總結一下吧,其實作者就是利用了looper中messageQueue排隊隊的特性,又有兩處對象同步鎖的妙用,保證了UI線程和HandlerThread不衝突,而過濾過程中的一些冗餘msg都被新msg建立時候給幹掉了,一個線程有序進行,大哉大牛!
PS,如果你對handler和looper的工作機制不太明白,可以先看下這個文章:
http://www.cnblogs.com/codingmyworld/archive/2011/09/14/2174255.html
寫篇文章好難!
android 源碼角度全方位理解filter