前言
==========
目前在公司中仍處于認知狀态,是以沒有什麼時間寫部落格了,趁今天是周末就來更新一發。
關于今天為什麼講 CursorAdapter 的原因,是因為之前在工作的時候有遇到 CursorAdapter 中 filter 的相關問題,于是就想把 CursorAdapter 中的 filter 機制流程好好梳理一下。出于這樣的目的,本篇博文就誕生了。
在閱讀本文之前,最好已經有寫過 CursorAdapter 中 filter 相關代碼的經曆,這樣可以幫助你更好地了解其中的原理。如果你準備好了,那麼接下來就一起來看看吧。
CursorAdapter 類
首先我們來看一下 CursorAdapter 的繼承以及實作關系:
public abstract class CursorAdapter extends BaseAdapter implements Filterable, CursorFilter.CursorFilterClient {
}
複制
CursorAdapter 繼承自 BaseAdapter ,相信大家都可以了解。之後又實作了 Filterable 和 CursorFilter.CursorFilterClient 接口。
Filterable 的接口很簡單,隻有一個
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 類是預設修飾符,也就是說我們在外部無法通路到它。
interface CursorFilterClient {
CharSequence convertToString(Cursor cursor);
Cursor runQueryOnBackgroundThread(CharSequence constraint);
Cursor getCursor();
void changeCursor(Cursor cursor);
}
複制
我們來看看 CursorFilterClient 接口中的抽象方法。根據方法名我們大概都能猜出該方法需要做的事情。
convertToString(Cursor cursor)
方法主要的功能就是根據傳入的 cursor 參數傳回某個字段;
runQueryOnBackgroundThread(CharSequence constraint)
方法的意思就是根據傳入的 constraint 字元序列去搜尋得到 cursor;而
getCursor()
就是傳回 cursor;
changeCursor(Cursor cursor)
就是根據傳入的新的 cursor 去替換舊的 cursor 。
filter 的用法
好了,我們來想想平時我們是怎麼樣使用 CursorAdapter 中的 filter ?
第一步,我們會使用自定義的 adapter 繼承自 CursorAdapter ,并且實作 FilterQueryProvider 和 FilterListener 接口。最後别忘了調用
setFilterQueryProvider(FilterQueryProvider filterQueryProvider)
方法。
然後,第二步我們會使用CursorAdapter的
getFilter()
方法來得到 filter 。對,沒錯,就是實作 Filterable 接口的那個
getFilter
方法。
public Filter getFilter() {
if (mCursorFilter == null) {
mCursorFilter = new CursorFilter(this);
}
return mCursorFilter;
}
複制
在 CursorAdapter 的源碼中,判斷了 mCursorFilter 是否為空。若為空,則建立一個新的 CursorFilter 對象。否則直接傳回 mCursorFilter 。在這裡要說明一下 CursorFilter 是 Filter 的子類。
而在 CursorFilter 的構造器中,主要是設定了 client (CursorAdapter 實作了 CursorFilterClient 接口)。
CursorFilter(CursorFilterClient client) {
mClient = client;
}
複制
在第二步得到了 filter 之後,第三步就可以使用
filter.filter(CharSequence constraint)
或者
filter.filter(CharSequence constraint, FilterListener listener)
方法了。constraint 參數就是要過濾的關鍵詞;而 FilterListener 是一個 Filter 類的内部接口,會在過濾完成之後回調其中的
onFilterComplete(int count)
方法。
filter 的原理
大緻使用 filter 的步驟就是像上面這樣的了。下面我們就來揭開這其中神秘的面紗吧!
我們的入手點就是 Filter 的 filter 方法了。其中的
filter.filter(CharSequence constraint)
方法内部會調用
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);
}
}
複制
從源碼中我們可以看到,主要做的就是在一開始建立一個 HandlerThread 線程,并且建立了一個 RequestHandler 的對象 mThreadHandler 。之後建立了一個 RequestArguments 的對象 args,然後把 constraint 和 listener 傳到 args 中去,而 RequestArguments 類還有一個成員變量就是 results ,主要用于存儲 filter 過濾之後的結果,這會在下面的代碼中用到。然後用 mThreadHandler 将該消息發送出去。
那麼我們接下來就要來看看 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 中我們可以看到,會先去調用
performFiltering(CharSequence constraint)
方法。而該方法在 Filter 類中是抽象方法,需要在子類中去實作。那麼我們就來看看 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)
方法中又會去調用 mClient 的
runQueryOnBackgroundThread(CharSequence constraint)
方法,而 mClient 就是之前的 CursorAdapter ,是以我們又要跳到 CursorAdapter 類去看相關的代碼:
/**
* 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;
}
複制
我們可以看到會去調用 mFilterQueryProvider 的
runQuery(CharSequence constraint)
方法。 FilterQueryProvider 其實就是一個接口而已,當我們需要使用 filter 時就要實作該接口。在上面的 filter 用法中已經提到過了。其中的
runQuery(CharSequence constraint)
方法就是需要我們自己去實作的。當然,這裡還有另外一種方法,就是不用實作 FilterQueryProvider 接口。而是在子類中去重寫
runQueryOnBackgroundThread(CharSequence constraint)
方法,也是達到了一樣的效果。
假定我們已經在
runQuery(CharSequence constraint)
實作了相關的操作,并且傳回了查詢出來的 cursor 。那樣我們又要跳回到 RequestHandler 的源碼中了(這裡隻截取部分代碼,完整代碼請檢視上面):
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 發送了消息。這樣我們又要來看一下 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)
中,調用了
publishResults(CharSequence constraint, FilterResults results)
方法。在 Filter 類中
publishResults(CharSequence constraint, FilterResults results)
又是抽象的,是以還得去 CursorFilter 類中檢視相關的源碼:
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
Cursor oldCursor = mClient.getCursor();
if (results.values != null && results.values != oldCursor) {
mClient.changeCursor((Cursor) results.values);
}
}
複制
源碼裡表示了會去調用 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)
中,又調用了
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 ,并且調用了
notifyDataSetChanged();
來更新 ListView 。從上面的源碼中還可以看到,
swapCursor(Cursor newCursor)
方法中傳回的 oldCursor 是沒有關閉的。
完成了替換 Cursor 的工作後,我們還要回過頭來看看 ResultsHandler 剩餘部分的代碼(隻截取了部分代碼):
if (args.listener != null) {
int count = args.results != null ? args.results.count : -1;
args.listener.onFilterComplete(count);
}
複制
可以看到,在最後回調了 FilterListener 的
onFilterComplete(int count)
方法。其中的 count 參數是查詢出來結果的總數。
至此,一個完整的 filter 流程終于走完了。這其中雖然看似很繞,其實原理還是比較簡單的。
尾語
看完上面分析,相信大家對 CursorAdapter 的 filter 機制已經有了一個大緻的了解了吧。主要原理基本上還是 Handler 異步消息機制以及各個接口回調等。從中可以發現其實源碼并不難,隻要有耐心慢慢分析,一定會有所突破的。如果對這整個流程有問題的童鞋可以在下面留言。
那麼,今天就到這了。Goodbye!