android 源碼角度全方位理解filter

     寫一個listview容易,寫一個adapter容易,本身new一個線程過濾數據也容易,可是如何將過濾的效率發揮到最大化,不得不提一下android自帶的filter類。html

     有同窗確定要問,過濾數據本身寫一個徹底沒問題,爲何要用android自帶的filter類?我原來也是本身寫線程過濾,然而最近項目中遇到一個低配機,雙核0.8GCPU,過濾效果實在是卡頓厲害,優化起見,使用了android內部filter試一下效果,結果然是比本身寫的好用,因而認真學習了下源碼,從頭到尾備忘以下:android

 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開發大牛用這幾個變量加上爲數很少的幾個局部變量就作出來了一個拓展性極佳的過濾器,不得不讓人欽佩。異步

      首先看構造方法:ide

    public Filter() {
        mResultHandler = new ResultsHandler();
    }
View Code

     構造方法中二小強之一——ResultsHandler已經被建立了,顧名思義處理過濾操做結果。咱們看看這個類的定義:oop

  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線程更新畫面。post

      接下來直接看主力的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在傳遞了ui

      這個方法的妙處在,方法自己很簡單,幾乎不耗時,即便你不斷地調用filter()方法,程序始終能保證只有一個message在作過濾操做。google

      接下來看看這個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致使空指針異常。 case FILTER_TOKEN中的同步代碼塊的做用,目前仍是想不出來到底有什麼用,由於case FILTER_TOKEN和case FINISH_TOKEN原本就是隊列執行,不存在爭用鎖的狀況,因此不會存在線程間的時空差致使的空指針,有高手能看出做用還望不吝賜教。

     好了,來總結一下吧,其實做者就是利用了looper中messageQueue排隊隊的特性,又有兩處對象同步鎖的妙用,保證了UI線程和HandlerThread不衝突,而過濾過程當中的一些冗餘msg都被新msg建立時候給幹掉了,一個線程有序進行,大哉大牛!

    PS,若是你對handler和looper的工做機制不太明白,能夠先看下這個帖子:

    http://www.cnblogs.com/codingmyworld/archive/2011/09/14/2174255.html

    寫篇帖子好難!

相關文章
相關標籤/搜索