詳解CursorAdapter中的filter機(jī)制

博文出處:詳解CursorAdapter中的filter機(jī)制,歡迎大家關(guān)注我的博客,謝謝!
前言
==========
目前在公司中仍處于認(rèn)知狀態(tài),因此沒有什么時(shí)間寫博客了,趁今天是周末就來更新一發(fā)。

關(guān)于今天為什么講 CursorAdapter 的原因,是因?yàn)橹霸诠ぷ鞯臅r(shí)候有遇到 CursorAdapter 中 filter 的相關(guān)問題,于是就想把 CursorAdapter 中的 filter 機(jī)制流程好好梳理一下。出于這樣的目的,本篇博文就誕生了。

在閱讀本文之前,最好已經(jīng)有寫過 CursorAdapter 中 filter 相關(guān)代碼的經(jīng)歷,這樣可以幫助你更好地理解其中的原理。如果你準(zhǔn)備好了,那么接下來就一起來看看吧。

CursorAdapter 類

首先我們來看一下 CursorAdapter 的繼承以及實(shí)現(xiàn)關(guān)系:

public abstract class CursorAdapter extends BaseAdapter implements Filterable, CursorFilter.CursorFilterClient {

}

CursorAdapter 繼承自 BaseAdapter ,相信大家都可以理解。之后又實(shí)現(xiàn)了 Filterable 和 CursorFilter.CursorFilterClient 接口。

Filterable 的接口很簡(jiǎn)單,只有一個(gè) getFilter() 方法,用來返回 filter 。

public interface Filterable {
    /**
     * <p>Returns a filter that can be used to constrain data with a filtering
     * pattern.</p>
     *
     * <p>This method is usually implemented by {@link android.widget.Adapter}
     * classes.</p>
     *
     * @return a filter used to constrain data
     */
    Filter getFilter();
}

而 CursorFilter.CursorFilterClient 的接口是定義在 CursorFilter 類里面的。而 CursorFilter 類是默認(rèn)修飾符,也就是說我們?cè)谕獠繜o法訪問到它。

interface CursorFilterClient {
    CharSequence convertToString(Cursor cursor);
    Cursor runQueryOnBackgroundThread(CharSequence constraint);
    Cursor getCursor();
    void changeCursor(Cursor cursor);
}

我們來看看 CursorFilterClient 接口中的抽象方法。根據(jù)方法名我們大概都能猜出該方法需要做的事情。 convertToString(Cursor cursor) 方法主要的功能就是根據(jù)傳入的 cursor 參數(shù)返回某個(gè)字段;runQueryOnBackgroundThread(CharSequence constraint) 方法的意思就是根據(jù)傳入的 constraint 字符序列去搜索得到 cursor;而 getCursor()就是返回 cursor;changeCursor(Cursor cursor) 就是根據(jù)傳入的新的 cursor 去替換舊的 cursor 。

filter 的用法

好了,我們來想想平時(shí)我們是怎么樣使用 CursorAdapter 中的 filter ?

第一步,我們會(huì)使用自定義的 adapter 繼承自 CursorAdapter ,并且實(shí)現(xiàn) FilterQueryProvider 和 FilterListener 接口。最后別忘了調(diào)用 setFilterQueryProvider(FilterQueryProvider filterQueryProvider) 方法。

然后,第二步我們會(huì)使用CursorAdapter的 getFilter() 方法來得到 filter 。對(duì),沒錯(cuò),就是實(shí)現(xiàn) Filterable 接口的那個(gè) getFilter 方法。

public Filter getFilter() {
    if (mCursorFilter == null) {
        mCursorFilter = new CursorFilter(this);
    }
    return mCursorFilter;
}

在 CursorAdapter 的源碼中,判斷了 mCursorFilter 是否為空。若為空,則創(chuàng)建一個(gè)新的 CursorFilter 對(duì)象。否則直接返回 mCursorFilter 。在這里要說明一下 CursorFilter 是 Filter 的子類。

而在 CursorFilter 的構(gòu)造器中,主要是設(shè)置了 client (CursorAdapter 實(shí)現(xiàn)了 CursorFilterClient 接口)。

CursorFilter(CursorFilterClient client) {
    mClient = client;
}

在第二步得到了 filter 之后,第三步就可以使用 filter.filter(CharSequence constraint) 或者 filter.filter(CharSequence constraint, FilterListener listener) 方法了。constraint 參數(shù)就是要過濾的關(guān)鍵詞;而 FilterListener 是一個(gè) Filter 類的內(nèi)部接口,會(huì)在過濾完成之后回調(diào)其中的 onFilterComplete(int count) 方法。

filter 的原理

大致使用 filter 的步驟就是像上面這樣的了。下面我們就來揭開這其中神秘的面紗吧!

我們的入手點(diǎn)就是 Filter 的 filter 方法了。其中的 filter.filter(CharSequence constraint) 方法內(nèi)部會(huì)調(diào)用 filter.filter(CharSequence constraint, FilterListener listener) 方法。所以我們只需要看下filter.filter(CharSequence constraint, FilterListener listener) 的源碼:

/**
 * <p>Starts an asynchronous filtering operation. Calling this method
 * cancels all previous non-executed filtering requests and posts a new
 * filtering request that will be executed later.</p>
 *
 * <p>Upon completion, the listener is notified.</p>
 *
 * @param constraint the constraint used to filter the data
 * @param listener a listener notified upon completion of the operation
 *
 * @see #filter(CharSequence)
 * @see #performFiltering(CharSequence)
 * @see #publishResults(CharSequence, android.widget.Filter.FilterResults)
 */
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);
    }
}

從源碼中我們可以看到,主要做的就是在一開始創(chuàng)建一個(gè) HandlerThread 線程,并且創(chuàng)建了一個(gè) RequestHandler 的對(duì)象 mThreadHandler 。之后創(chuàng)建了一個(gè) RequestArguments 的對(duì)象 args,然后把 constraint 和 listener 傳到 args 中去,而 RequestArguments 類還有一個(gè)成員變量就是 results ,主要用于存儲(chǔ) filter 過濾之后的結(jié)果,這會(huì)在下面的代碼中用到。然后用 mThreadHandler 將該消息發(fā)送出去。

那么我們接下來就要來看看 RequestHandler 的源碼:

/**
 * <p>Worker thread handler. When a new filtering request is posted from
 * {@link android.widget.Filter#filter(CharSequence, android.widget.Filter.FilterListener)},
 * it is sent to this handler.</p>
 */
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;
        }
    }
}

在 case FILTER_TOKEN 中我們可以看到,會(huì)先去調(diào)用 performFiltering(CharSequence constraint) 方法。而該方法在 Filter 類中是抽象方法,需要在子類中去實(shí)現(xiàn)。那么我們就來看看 CursorFilter 的 performFiltering(CharSequence constraint) 方法吧:

@Override
protected FilterResults performFiltering(CharSequence constraint) {
    Cursor cursor = mClient.runQueryOnBackgroundThread(constraint);

    FilterResults results = new FilterResults();
    if (cursor != null) {
        results.count = cursor.getCount();
        results.values = cursor;
    } else {
        results.count = 0;
        results.values = null;
    }
    return results;
}

performFiltering(CharSequence constraint) 方法中又會(huì)去調(diào)用 mClient 的 runQueryOnBackgroundThread(CharSequence constraint) 方法,而 mClient 就是之前的 CursorAdapter ,所以我們又要跳到 CursorAdapter 類去看相關(guān)的代碼:

/**
 * Runs a query with the specified constraint. This query is requested
 * by the filter attached to this adapter.
 *
 * The query is provided by a
 * {@link android.widget.FilterQueryProvider}.
 * If no provider is specified, the current cursor is not filtered and returned.
 *
 * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)}
 * and the previous cursor is closed.
 *
 * This method is always executed on a background thread, not on the
 * application's main thread (or UI thread.)
 * 
 * Contract: when constraint is null or empty, the original results,
 * prior to any filtering, must be returned.
 *
 * @param constraint the constraint with which the query must be filtered
 *
 * @return a Cursor representing the results of the new query
 *
 * @see #getFilter()
 * @see #getFilterQueryProvider()
 * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
 */
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
    if (mFilterQueryProvider != null) {
        return mFilterQueryProvider.runQuery(constraint);
    }

    return mCursor;
}

我們可以看到會(huì)去調(diào)用 mFilterQueryProvider 的 runQuery(CharSequence constraint) 方法。 FilterQueryProvider 其實(shí)就是一個(gè)接口而已,當(dāng)我們需要使用 filter 時(shí)就要實(shí)現(xiàn)該接口。在上面的 filter 用法中已經(jīng)提到過了。其中的 runQuery(CharSequence constraint) 方法就是需要我們自己去實(shí)現(xiàn)的。當(dāng)然,這里還有另外一種方法,就是不用實(shí)現(xiàn) FilterQueryProvider 接口。而是在子類中去重寫 runQueryOnBackgroundThread(CharSequence constraint) 方法,也是達(dá)到了一樣的效果。

假定我們已經(jīng)在 runQuery(CharSequence constraint) 實(shí)現(xiàn)了相關(guān)的操作,并且返回了查詢出來的 cursor 。那樣我們又要跳回到 RequestHandler 的源碼中了(這里只截取部分代碼,完整代碼請(qǐng)查看上面):

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();
}

可以看到,這里把返回的 cursor 傳給了 args.results 。并且又使用了 mResultHandler 發(fā)送了消息。這樣我們又要來看一下 ResultHandler 的源碼了:

/**
 * <p>Handles the results of a filtering operation. The results are
 * handled in the UI thread.</p>
 */
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);
        }
    }
}

handleMessage(Message msg) 中,調(diào)用了 publishResults(CharSequence constraint, FilterResults results) 方法。在 Filter 類中 publishResults(CharSequence constraint, FilterResults results) 又是抽象的,所以還得去 CursorFilter 類中查看相關(guān)的源碼:

@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
    Cursor oldCursor = mClient.getCursor();
    
    if (results.values != null && results.values != oldCursor) {
        mClient.changeCursor((Cursor) results.values);
    }
}

源碼里表示了會(huì)去調(diào)用 CursorAdapter 的 changeCursor(Cursor cursor) :

/**
 * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
 * closed.
 * 
 * @param cursor The new cursor to be used
 */
public void changeCursor(Cursor cursor) {
    Cursor old = swapCursor(cursor);
    if (old != null) {
        old.close();
    }
}

changeCursor(Cursor cursor) 中,又調(diào)用了 swapCursor(Cursor newCursor) :

/**
 * Swap in a new Cursor, returning the old Cursor.  Unlike
 * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
 * closed.
 *
 * @param newCursor The new cursor to be used.
 * @return Returns the previously set Cursor, or null if there wasa not one.
 * If the given new Cursor is the same instance is the previously set
 * Cursor, null is also returned.
 */
public Cursor swapCursor(Cursor newCursor) {
    if (newCursor == mCursor) {
        return null;
    }
    Cursor oldCursor = mCursor;
    if (oldCursor != null) {
        if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
        if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
    }
    mCursor = newCursor;
    if (newCursor != null) {
        if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
        if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
        mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
        mDataValid = true;
        // notify the observers about the new cursor
        notifyDataSetChanged();
    } else {
        mRowIDColumn = -1;
        mDataValid = false;
        // notify the observers about the lack of a data set
        notifyDataSetInvalidated();
    }
    return oldCursor;
}

swapCursor(Cursor newCursor) 中主要的工作就是把 oldCursor 替換成 newCursor ,并且調(diào)用了 notifyDataSetChanged(); 來更新 ListView 。從上面的源碼中還可以看到, swapCursor(Cursor newCursor) 方法中返回的 oldCursor 是沒有關(guān)閉的。

完成了替換 Cursor 的工作后,我們還要回過頭來看看 ResultsHandler 剩余部分的代碼(只截取了部分代碼):

if (args.listener != null) {
    int count = args.results != null ? args.results.count : -1;
    args.listener.onFilterComplete(count);
}

可以看到,在最后回調(diào)了 FilterListener 的 onFilterComplete(int count) 方法。其中的 count 參數(shù)是查詢出來結(jié)果的總數(shù)。

至此,一個(gè)完整的 filter 流程終于走完了。這其中雖然看似很繞,其實(shí)原理還是比較簡(jiǎn)單的。

尾語

看完上面分析,相信大家對(duì) CursorAdapter 的 filter 機(jī)制已經(jīng)有了一個(gè)大致的了解了吧。主要原理基本上還是 Handler 異步消息機(jī)制以及各個(gè)接口回調(diào)等。從中可以發(fā)現(xiàn)其實(shí)源碼并不難,只要有耐心慢慢分析,一定會(huì)有所突破的。如果對(duì)這整個(gè)流程有問題的童鞋可以在下面留言。

那么,今天就到這了。Goodbye!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容