zoukankan      html  css  js  c++  java
  • 【Android】Android中的搜索过滤操作实现

    我在【Android】搜索框架中转载了一篇文章,这篇文章讲述了如何使用Android平台自带的搜索框架从而实现搜索的原生展现。这篇文章则要叙述如何实现搜索功能。

    搜索在很多应用中都非常常见,比如联系人应用,Google Play中,能够有让我们快速找到需要寻找的内容。

    在手机端,搜索需要注意搜索结果的展示,我们希望用户可以尽可能看到更多的搜索结果,所以我们一般使用ListView展现搜索结果,信息简单而且有价值。下面以联系人应用、ListView展示搜索结果为例,讲述如何搜索功能。

    一、简介

    一般来说,我们可以使用以下几种方式实现搜索:

    1)暴力搜索——直接使用数据库提供的功能,每次都从数据库中读取搜索的结果,存进一个数据结构用于Adapter显示,调用notifyDataSetChanged()刷新数据;

    2)利用filter进行搜索。这块涉及到Filterable接口。推荐文章:Android实现Filterable通过输入文本框实现联系人自动筛选。有两点值得注意:a)Android原生组件AutoCompleteTextview就是使用该方法实现的;b)这个方法本质上还是调用notifyDataSetChanged()方法,并且还是要自己去实现搜索部分,只是整个方法看上去比较优雅,而且不用再去搜索数据库,最重要的一点是这个时候搜索过程被自动移到另外一个线程之中,搜索完毕之后才会刷新UI;

    二、CursorAdapter

    以上两种方法都比较简单,接下来要讲的方法并非有什么新奇之处,只是利用Android已有的API去实现该功能,避免自己去实现已有的实现。方法没有好坏之分,能实现功能的情况下越简单越好,暴力搜索也很OK。首先,我们认识一下一个类:CursorAdapter(一看就知道是干嘛的了吧),上源码:

      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.support.v4.widget;
     18 
     19 import android.content.Context;
     20 import android.database.ContentObserver;
     21 import android.database.Cursor;
     22 import android.database.DataSetObserver;
     23 import android.os.Handler;
     24 import android.util.Log;
     25 import android.view.View;
     26 import android.view.ViewGroup;
     27 import android.widget.BaseAdapter;
     28 import android.widget.Filter;
     29 import android.widget.FilterQueryProvider;
     30 import android.widget.Filterable;
     31 
     32 /**
     33  * Static library support version of the framework's {@link android.widget.CursorAdapter}.
     34  * Used to write apps that run on platforms prior to Android 3.0.  When running
     35  * on Android 3.0 or above, this implementation is still used; it does not try
     36  * to switch to the framework's implementation.  See the framework SDK
     37  * documentation for a class overview.
     38  */
     39 public abstract class CursorAdapter extends BaseAdapter implements Filterable,
     40         CursorFilter.CursorFilterClient {
     41     /**
     42      * This field should be made private, so it is hidden from the SDK.
     43      * {@hide}
     44      */
     45     protected boolean mDataValid;
     46     /**
     47      * This field should be made private, so it is hidden from the SDK.
     48      * {@hide}
     49      */
     50     protected boolean mAutoRequery;
     51     /**
     52      * This field should be made private, so it is hidden from the SDK.
     53      * {@hide}
     54      */
     55     protected Cursor mCursor;
     56     /**
     57      * This field should be made private, so it is hidden from the SDK.
     58      * {@hide}
     59      */
     60     protected Context mContext;
     61     /**
     62      * This field should be made private, so it is hidden from the SDK.
     63      * {@hide}
     64      */
     65     protected int mRowIDColumn;
     66     /**
     67      * This field should be made private, so it is hidden from the SDK.
     68      * {@hide}
     69      */
     70     protected ChangeObserver mChangeObserver;
     71     /**
     72      * This field should be made private, so it is hidden from the SDK.
     73      * {@hide}
     74      */
     75     protected DataSetObserver mDataSetObserver;
     76     /**
     77      * This field should be made private, so it is hidden from the SDK.
     78      * {@hide}
     79      */
     80     protected CursorFilter mCursorFilter;
     81     /**
     82      * This field should be made private, so it is hidden from the SDK.
     83      * {@hide}
     84      */
     85     protected FilterQueryProvider mFilterQueryProvider;
     86 
     87     /**
     88      * If set the adapter will call requery() on the cursor whenever a content change
     89      * notification is delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
     90      *
     91      * @deprecated This option is discouraged, as it results in Cursor queries
     92      * being performed on the application's UI thread and thus can cause poor
     93      * responsiveness or even Application Not Responding errors.  As an alternative,
     94      * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
     95      */
     96     @Deprecated
     97     public static final int FLAG_AUTO_REQUERY = 0x01;
     98 
     99     /**
    100      * If set the adapter will register a content observer on the cursor and will call
    101      * {@link #onContentChanged()} when a notification comes in.  Be careful when
    102      * using this flag: you will need to unset the current Cursor from the adapter
    103      * to avoid leaks due to its registered observers.  This flag is not needed
    104      * when using a CursorAdapter with a
    105      * {@link android.content.CursorLoader}.
    106      */
    107     public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
    108 
    109     /**
    110      * Constructor that always enables auto-requery.
    111      *
    112      * @deprecated This option is discouraged, as it results in Cursor queries
    113      * being performed on the application's UI thread and thus can cause poor
    114      * responsiveness or even Application Not Responding errors.  As an alternative,
    115      * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
    116      *
    117      * @param c The cursor from which to get the data.
    118      * @param context The context
    119      */
    120     @Deprecated
    121     public CursorAdapter(Context context, Cursor c) {
    122         init(context, c, FLAG_AUTO_REQUERY);
    123     }
    124 
    125     /**
    126      * Constructor that allows control over auto-requery.  It is recommended
    127      * you not use this, but instead {@link #CursorAdapter(Context, Cursor, int)}.
    128      * When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER}
    129      * will always be set.
    130      *
    131      * @param c The cursor from which to get the data.
    132      * @param context The context
    133      * @param autoRequery If true the adapter will call requery() on the
    134      *                    cursor whenever it changes so the most recent
    135      *                    data is always displayed.  Using true here is discouraged.
    136      */
    137     public CursorAdapter(Context context, Cursor c, boolean autoRequery) {
    138         init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
    139     }
    140 
    141     /**
    142      * Recommended constructor.
    143      *
    144      * @param c The cursor from which to get the data.
    145      * @param context The context
    146      * @param flags Flags used to determine the behavior of the adapter; may
    147      * be any combination of {@link #FLAG_AUTO_REQUERY} and
    148      * {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
    149      */
    150     public CursorAdapter(Context context, Cursor c, int flags) {
    151         init(context, c, flags);
    152     }
    153 
    154     /**
    155      * @deprecated Don't use this, use the normal constructor.  This will
    156      * be removed in the future.
    157      */
    158     @Deprecated
    159     protected void init(Context context, Cursor c, boolean autoRequery) {
    160         init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
    161     }
    162 
    163     void init(Context context, Cursor c, int flags) {
    164         if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) {
    165             flags |= FLAG_REGISTER_CONTENT_OBSERVER;
    166             mAutoRequery = true;
    167         } else {
    168             mAutoRequery = false;
    169         }
    170         boolean cursorPresent = c != null;
    171         mCursor = c;
    172         mDataValid = cursorPresent;
    173         mContext = context;
    174         mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
    175         if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
    176             mChangeObserver = new ChangeObserver();
    177             mDataSetObserver = new MyDataSetObserver();
    178         } else {
    179             mChangeObserver = null;
    180             mDataSetObserver = null;
    181         }
    182 
    183         if (cursorPresent) {
    184             if (mChangeObserver != null) c.registerContentObserver(mChangeObserver);
    185             if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver);
    186         }
    187     }
    188 
    189     /**
    190      * Returns the cursor.
    191      * @return the cursor.
    192      */
    193     public Cursor getCursor() {
    194         return mCursor;
    195     }
    196 
    197     /**
    198      * @see android.widget.ListAdapter#getCount()
    199      */
    200     public int getCount() {
    201         if (mDataValid && mCursor != null) {
    202             return mCursor.getCount();
    203         } else {
    204             return 0;
    205         }
    206     }
    207     
    208     /**
    209      * @see android.widget.ListAdapter#getItem(int)
    210      */
    211     public Object getItem(int position) {
    212         if (mDataValid && mCursor != null) {
    213             mCursor.moveToPosition(position);
    214             return mCursor;
    215         } else {
    216             return null;
    217         }
    218     }
    219 
    220     /**
    221      * @see android.widget.ListAdapter#getItemId(int)
    222      */
    223     public long getItemId(int position) {
    224         if (mDataValid && mCursor != null) {
    225             if (mCursor.moveToPosition(position)) {
    226                 return mCursor.getLong(mRowIDColumn);
    227             } else {
    228                 return 0;
    229             }
    230         } else {
    231             return 0;
    232         }
    233     }
    234     
    235     @Override
    236     public boolean hasStableIds() {
    237         return true;
    238     }
    239 
    240     /**
    241      * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
    242      */
    243     public View getView(int position, View convertView, ViewGroup parent) {
    244         if (!mDataValid) {
    245             throw new IllegalStateException("this should only be called when the cursor is valid");
    246         }
    247         if (!mCursor.moveToPosition(position)) {
    248             throw new IllegalStateException("couldn't move cursor to position " + position);
    249         }
    250         View v;
    251         if (convertView == null) {
    252             v = newView(mContext, mCursor, parent);
    253         } else {
    254             v = convertView;
    255         }
    256         bindView(v, mContext, mCursor);
    257         return v;
    258     }
    259 
    260     @Override
    261     public View getDropDownView(int position, View convertView, ViewGroup parent) {
    262         if (mDataValid) {
    263             mCursor.moveToPosition(position);
    264             View v;
    265             if (convertView == null) {
    266                 v = newDropDownView(mContext, mCursor, parent);
    267             } else {
    268                 v = convertView;
    269             }
    270             bindView(v, mContext, mCursor);
    271             return v;
    272         } else {
    273             return null;
    274         }
    275     }
    276     
    277     /**
    278      * Makes a new view to hold the data pointed to by cursor.
    279      * @param context Interface to application's global information
    280      * @param cursor The cursor from which to get the data. The cursor is already
    281      * moved to the correct position.
    282      * @param parent The parent to which the new view is attached to
    283      * @return the newly created view.
    284      */
    285     public abstract View newView(Context context, Cursor cursor, ViewGroup parent);
    286 
    287     /**
    288      * Makes a new drop down view to hold the data pointed to by cursor.
    289      * @param context Interface to application's global information
    290      * @param cursor The cursor from which to get the data. The cursor is already
    291      * moved to the correct position.
    292      * @param parent The parent to which the new view is attached to
    293      * @return the newly created view.
    294      */
    295     public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
    296         return newView(context, cursor, parent);
    297     }
    298 
    299     /**
    300      * Bind an existing view to the data pointed to by cursor
    301      * @param view Existing view, returned earlier by newView
    302      * @param context Interface to application's global information
    303      * @param cursor The cursor from which to get the data. The cursor is already
    304      * moved to the correct position.
    305      */
    306     public abstract void bindView(View view, Context context, Cursor cursor);
    307     
    308     /**
    309      * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
    310      * closed.
    311      * 
    312      * @param cursor The new cursor to be used
    313      */
    314     public void changeCursor(Cursor cursor) {
    315         Cursor old = swapCursor(cursor);
    316         if (old != null) {
    317             old.close();
    318         }
    319     }
    320 
    321     /**
    322      * Swap in a new Cursor, returning the old Cursor.  Unlike
    323      * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
    324      * closed.
    325      *
    326      * @param newCursor The new cursor to be used.
    327      * @return Returns the previously set Cursor, or null if there wasa not one.
    328      * If the given new Cursor is the same instance is the previously set
    329      * Cursor, null is also returned.
    330      */
    331     public Cursor swapCursor(Cursor newCursor) {
    332         if (newCursor == mCursor) {
    333             return null;
    334         }
    335         Cursor oldCursor = mCursor;
    336         if (oldCursor != null) {
    337             if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
    338             if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
    339         }
    340         mCursor = newCursor;
    341         if (newCursor != null) {
    342             if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
    343             if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
    344             mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
    345             mDataValid = true;
    346             // notify the observers about the new cursor
    347             notifyDataSetChanged();
    348         } else {
    349             mRowIDColumn = -1;
    350             mDataValid = false;
    351             // notify the observers about the lack of a data set
    352             notifyDataSetInvalidated();
    353         }
    354         return oldCursor;
    355     }
    356 
    357     /**
    358      * <p>Converts the cursor into a CharSequence. Subclasses should override this
    359      * method to convert their results. The default implementation returns an
    360      * empty String for null values or the default String representation of
    361      * the value.</p>
    362      *
    363      * @param cursor the cursor to convert to a CharSequence
    364      * @return a CharSequence representing the value
    365      */
    366     public CharSequence convertToString(Cursor cursor) {
    367         return cursor == null ? "" : cursor.toString();
    368     }
    369 
    370     /**
    371      * Runs a query with the specified constraint. This query is requested
    372      * by the filter attached to this adapter.
    373      *
    374      * The query is provided by a
    375      * {@link android.widget.FilterQueryProvider}.
    376      * If no provider is specified, the current cursor is not filtered and returned.
    377      *
    378      * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)}
    379      * and the previous cursor is closed.
    380      *
    381      * This method is always executed on a background thread, not on the
    382      * application's main thread (or UI thread.)
    383      * 
    384      * Contract: when constraint is null or empty, the original results,
    385      * prior to any filtering, must be returned.
    386      *
    387      * @param constraint the constraint with which the query must be filtered
    388      *
    389      * @return a Cursor representing the results of the new query
    390      *
    391      * @see #getFilter()
    392      * @see #getFilterQueryProvider()
    393      * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
    394      */
    395     public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
    396         if (mFilterQueryProvider != null) {
    397             return mFilterQueryProvider.runQuery(constraint);
    398         }
    399 
    400         return mCursor;
    401     }
    402 
    403     public Filter getFilter() {
    404         if (mCursorFilter == null) {
    405             mCursorFilter = new CursorFilter(this);
    406         }
    407         return mCursorFilter;
    408     }
    409 
    410     /**
    411      * Returns the query filter provider used for filtering. When the
    412      * provider is null, no filtering occurs.
    413      *
    414      * @return the current filter query provider or null if it does not exist
    415      *
    416      * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
    417      * @see #runQueryOnBackgroundThread(CharSequence)
    418      */
    419     public FilterQueryProvider getFilterQueryProvider() {
    420         return mFilterQueryProvider;
    421     }
    422 
    423     /**
    424      * Sets the query filter provider used to filter the current Cursor.
    425      * The provider's
    426      * {@link android.widget.FilterQueryProvider#runQuery(CharSequence)}
    427      * method is invoked when filtering is requested by a client of
    428      * this adapter.
    429      *
    430      * @param filterQueryProvider the filter query provider or null to remove it
    431      *
    432      * @see #getFilterQueryProvider()
    433      * @see #runQueryOnBackgroundThread(CharSequence)
    434      */
    435     public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) {
    436         mFilterQueryProvider = filterQueryProvider;
    437     }
    438 
    439     /**
    440      * Called when the {@link ContentObserver} on the cursor receives a change notification.
    441      * The default implementation provides the auto-requery logic, but may be overridden by
    442      * sub classes.
    443      * 
    444      * @see ContentObserver#onChange(boolean)
    445      */
    446     protected void onContentChanged() {
    447         if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
    448             if (false) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
    449             mDataValid = mCursor.requery();
    450         }
    451     }
    452 
    453     private class ChangeObserver extends ContentObserver {
    454         public ChangeObserver() {
    455             super(new Handler());
    456         }
    457 
    458         @Override
    459         public boolean deliverSelfNotifications() {
    460             return true;
    461         }
    462 
    463         @Override
    464         public void onChange(boolean selfChange) {
    465             onContentChanged();
    466         }
    467     }
    468 
    469     private class MyDataSetObserver extends DataSetObserver {
    470         @Override
    471         public void onChanged() {
    472             mDataValid = true;
    473             notifyDataSetChanged();
    474         }
    475 
    476         @Override
    477         public void onInvalidated() {
    478             mDataValid = false;
    479             notifyDataSetInvalidated();
    480         }
    481     }
    482 
    483 }
    View Code

    这边有几点比较有意思,值得注意:

    1)这个类实现了Filterable接口和CursorFilter.CursorFilterClient接口,第一个接口已经有所认识,第二个接口暂时不提,后面论述;

    2)下面这个方法:

     1 /**
     2      * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
     3      */
     4     public View getView(int position, View convertView, ViewGroup parent) {
     5         if (!mDataValid) {
     6             throw new IllegalStateException("this should only be called when the cursor is valid");
     7         }
     8         if (!mCursor.moveToPosition(position)) {
     9             throw new IllegalStateException("couldn't move cursor to position " + position);
    10         }
    11         View v;
    12         if (convertView == null) {
    13             v = newView(mContext, mCursor, parent);
    14         } else {
    15             v = convertView;
    16         }
    17         bindView(v, mContext, mCursor);
    18         return v;
    19     }

    我们知道ListView,GridView之类的ViewGroup子类都自带组件复用机制,View可以重复显示在界面上而不需要重新去实例化它,而通常这个都是需要我们在getView()方法里面自己实现,而实现机制就非常类似上面这段代码,这里,CursorAdapter已经帮我们实现了,如果View为空则执行newView操作,最后执行bindView()方法,最后返回v。而在CurosrAdapter类里面,bindView和newView方法都是抽象方法,需要开发者自己实现的,而根据这边的源码,我们很容易知道这两个方法应该如何实现:1)newView中应该重新生成一个View;2)bindView中应该重新对View中的组件进行“赋值”操作;完全不需要去考虑复用组件。

    下面回到第1)点,第1)点中还有半点不明白的地方:CursorFilter.CursorFilterClient。首先了解一下CursorFilter这个类,上源码:

     1 /*
     2  * Copyright (C) 2011 The Android Open Source Project
     3  *
     4  * Licensed under the Apache License, Version 2.0 (the "License");
     5  * you may not use this file except in compliance with the License.
     6  * You may obtain a copy of the License at
     7  *
     8  *      http://www.apache.org/licenses/LICENSE-2.0
     9  *
    10  * Unless required by applicable law or agreed to in writing, software
    11  * distributed under the License is distributed on an "AS IS" BASIS,
    12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  * See the License for the specific language governing permissions and
    14  * limitations under the License.
    15  */
    16 
    17 package android.support.v4.widget;
    18 
    19 import android.database.Cursor;
    20 import android.widget.Filter;
    21 
    22 /**
    23  * <p>The CursorFilter delegates most of the work to the CursorAdapter.
    24  * Subclasses should override these delegate methods to run the queries
    25  * and convert the results into String that can be used by auto-completion
    26  * widgets.</p>
    27  */
    28 class CursorFilter extends Filter {
    29     
    30     CursorFilterClient mClient;
    31     
    32     interface CursorFilterClient {
    33         CharSequence convertToString(Cursor cursor);
    34         Cursor runQueryOnBackgroundThread(CharSequence constraint);
    35         Cursor getCursor();
    36         void changeCursor(Cursor cursor);
    37     }
    38 
    39     CursorFilter(CursorFilterClient client) {
    40         mClient = client;
    41     }
    42     
    43     @Override
    44     public CharSequence convertResultToString(Object resultValue) {
    45         return mClient.convertToString((Cursor) resultValue);
    46     }
    47 
    48     @Override
    49     protected FilterResults performFiltering(CharSequence constraint) {
    50         Cursor cursor = mClient.runQueryOnBackgroundThread(constraint);
    51 
    52         FilterResults results = new FilterResults();
    53         if (cursor != null) {
    54             results.count = cursor.getCount();
    55             results.values = cursor;
    56         } else {
    57             results.count = 0;
    58             results.values = null;
    59         }
    60         return results;
    61     }
    62 
    63     @Override
    64     protected void publishResults(CharSequence constraint, FilterResults results) {
    65         Cursor oldCursor = mClient.getCursor();
    66         
    67         if (results.values != null && results.values != oldCursor) {
    68             mClient.changeCursor((Cursor) results.values);
    69         }
    70     }
    71 }
    View Code

    这个类继承了Filter,如果你仔细看过前面推荐了解Filterable的那篇文章(Android实现Filterable通过输入文本框实现联系人自动筛选),对这个一定看上去非常眼熟,OK,这边几乎没什么不同,唯一的不同就是将查询操作交给了一个叫做CursorFilterClient的接口对象,这货长的是这副样子的:

    1 interface CursorFilterClient {
    2         CharSequence convertToString(Cursor cursor);
    3         Cursor runQueryOnBackgroundThread(CharSequence constraint);
    4         Cursor getCursor();
    5         void changeCursor(Cursor cursor);
    6     }

    OKOK,CursorAdapter是实现了这个接口的,我们来看看最重要的两个接口具体在这里面是怎么实现的吧:

     1 /**
     2      * Runs a query with the specified constraint. This query is requested
     3      * by the filter attached to this adapter.
     4      *
     5      * The query is provided by a
     6      * {@link android.widget.FilterQueryProvider}.
     7      * If no provider is specified, the current cursor is not filtered and returned.
     8      *
     9      * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)}
    10      * and the previous cursor is closed.
    11      *
    12      * This method is always executed on a background thread, not on the
    13      * application's main thread (or UI thread.)
    14      * 
    15      * Contract: when constraint is null or empty, the original results,
    16      * prior to any filtering, must be returned.
    17      *
    18      * @param constraint the constraint with which the query must be filtered
    19      *
    20      * @return a Cursor representing the results of the new query
    21      *
    22      * @see #getFilter()
    23      * @see #getFilterQueryProvider()
    24      * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
    25      */
    26     public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
    27         if (mFilterQueryProvider != null) {
    28             return mFilterQueryProvider.runQuery(constraint);
    29         }
    30 
    31         return mCursor;
    32     }
    View Code

    查询操作又交给了另外一个对象:FilterQueryProvider。

     1 /**
     2      * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
     3      * closed.
     4      * 
     5      * @param cursor The new cursor to be used
     6      */
     7     public void changeCursor(Cursor cursor) {
     8         Cursor old = swapCursor(cursor);
     9         if (old != null) {
    10             old.close();
    11         }
    12     }
    13 
    14     /**
    15      * Swap in a new Cursor, returning the old Cursor.  Unlike
    16      * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
    17      * closed.
    18      *
    19      * @param newCursor The new cursor to be used.
    20      * @return Returns the previously set Cursor, or null if there wasa not one.
    21      * If the given new Cursor is the same instance is the previously set
    22      * Cursor, null is also returned.
    23      */
    24     public Cursor swapCursor(Cursor newCursor) {
    25         if (newCursor == mCursor) {
    26             return null;
    27         }
    28         Cursor oldCursor = mCursor;
    29         if (oldCursor != null) {
    30             if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
    31             if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
    32         }
    33         mCursor = newCursor;
    34         if (newCursor != null) {
    35             if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
    36             if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
    37             mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
    38             mDataValid = true;
    39             // notify the observers about the new cursor
    40             notifyDataSetChanged();
    41         } else {
    42             mRowIDColumn = -1;
    43             mDataValid = false;
    44             // notify the observers about the lack of a data set
    45             notifyDataSetInvalidated();
    46         }
    47         return oldCursor;
    48     }
    View Code

    更新完毕以后调用该方法刷新列表。

    现在假设你要去使用这样CursorAdapter,我们来看看如何进行:

    第一步,首先根据上面的说明去实现bindView和newView两个方法,然后传递一个Cursor进来实例化CursorAdapter,这样子首先实现了一个基本的Adapter的功能;

    第二步,我们要实现搜索过滤功能。我们在使用Filterable接口的时候,调用方法如下:

    1 adapter.getFilter().filter(et_filter.getText().toString());  

    在这里一样的。这里CursorAdapter的getFileter()方法实现如下:

    1 public Filter getFilter() {
    2         if (mCursorFilter == null) {
    3             mCursorFilter = new CursorFilter(this);
    4         }
    5         return mCursorFilter;
    6     }

    它返回的是一个CursorFilter,实例化的时候需要一个CursorFilter.CursorFilterClient参数,这里正好CursorAdapter就实现了这个接口。所以直接用this即可,这里还缺一点,我们缺少一个最最核心的对象:FilterQueryProvider,最后的查询操作就交给了它。它很简单:

     1 /*
     2  * Copyright (C) 2007 The Android Open Source Project
     3  *
     4  * Licensed under the Apache License, Version 2.0 (the "License");
     5  * you may not use this file except in compliance with the License.
     6  * You may obtain a copy of the License at
     7  *
     8  *      http://www.apache.org/licenses/LICENSE-2.0
     9  *
    10  * Unless required by applicable law or agreed to in writing, software
    11  * distributed under the License is distributed on an "AS IS" BASIS,
    12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  * See the License for the specific language governing permissions and
    14  * limitations under the License.
    15  */
    16 
    17 package android.widget;
    18 
    19 import android.database.Cursor;
    20 
    21 /**
    22  * This class can be used by external clients of CursorAdapter and
    23  * CursorTreeAdapter to define how the content of the adapter should be
    24  * filtered.
    25  * 
    26  * @see #runQuery(CharSequence)
    27  */
    28 public interface FilterQueryProvider {
    29     /**
    30      * Runs a query with the specified constraint. This query is requested
    31      * by the filter attached to this adapter.
    32      *
    33      * Contract: when constraint is null or empty, the original results,
    34      * prior to any filtering, must be returned.
    35      *
    36      * @param constraint the constraint with which the query must
    37      *        be filtered
    38      *
    39      * @return a Cursor representing the results of the new query
    40      */
    41     Cursor runQuery(CharSequence constraint);
    42 }
    View Code

    就一个接口,所以我们需要在外面继承这个接口实现它,然后通过CursorAdapter的

     1 /**
     2      * Sets the query filter provider used to filter the current Cursor.
     3      * The provider's
     4      * {@link android.widget.FilterQueryProvider#runQuery(CharSequence)}
     5      * method is invoked when filtering is requested by a client of
     6      * this adapter.
     7      *
     8      * @param filterQueryProvider the filter query provider or null to remove it
     9      *
    10      * @see #getFilterQueryProvider()
    11      * @see #runQueryOnBackgroundThread(CharSequence)
    12      */
    13     public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) {
    14         mFilterQueryProvider = filterQueryProvider;
    15     }
    View Code

    方法设置,之后整个机制完全串联起来,在外调用adapter.getFilter().filter(et_filter.getText().toString());  即可启动整个过滤机制。

    其实上面说的和第2)种方法差不多,只不过,假如你已经决定使用CursorAdapter了,建议你使用以上方法实现更新,因为,它已经被实现好了,浑然天成。

    三、AsyncQueryHandler

    从单词表义来看,就是异步查询处理器。在Android中,异步线程似乎是天生的,基本上所有写Android的都会碰到Handler这个类,它就是异步的,用来通知主线程做事情。这个类和AsyncTask很像,都封装了一个操作的几个部分,操作完成之后,只不过,它局限于对ContentProvider进行操作。仔细研究一下这个类,上源码:

      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.content;
     18 
     19 import android.database.Cursor;
     20 import android.net.Uri;
     21 import android.os.Handler;
     22 import android.os.HandlerThread;
     23 import android.os.Looper;
     24 import android.os.Message;
     25 import android.util.Log;
     26 
     27 import java.lang.ref.WeakReference;
     28 
     29 /**
     30  * A helper class to help make handling asynchronous {@link ContentResolver}
     31  * queries easier.
     32  */
     33 public abstract class AsyncQueryHandler extends Handler {
     34     private static final String TAG = "AsyncQuery";
     35     private static final boolean localLOGV = false;
     36 
     37     private static final int EVENT_ARG_QUERY = 1;
     38     private static final int EVENT_ARG_INSERT = 2;
     39     private static final int EVENT_ARG_UPDATE = 3;
     40     private static final int EVENT_ARG_DELETE = 4;
     41 
     42     /* package */ final WeakReference<ContentResolver> mResolver;
     43 
     44     private static Looper sLooper = null;
     45 
     46     private Handler mWorkerThreadHandler;
     47 
     48     protected static final class WorkerArgs {
     49         public Uri uri;
     50         public Handler handler;
     51         public String[] projection;
     52         public String selection;
     53         public String[] selectionArgs;
     54         public String orderBy;
     55         public Object result;
     56         public Object cookie;
     57         public ContentValues values;
     58     }
     59 
     60     protected class WorkerHandler extends Handler {
     61         public WorkerHandler(Looper looper) {
     62             super(looper);
     63         }
     64 
     65         @Override
     66         public void handleMessage(Message msg) {
     67             final ContentResolver resolver = mResolver.get();
     68             if (resolver == null) return;
     69 
     70             WorkerArgs args = (WorkerArgs) msg.obj;
     71 
     72             int token = msg.what;
     73             int event = msg.arg1;
     74 
     75             switch (event) {
     76                 case EVENT_ARG_QUERY:
     77                     Cursor cursor;
     78                     try {
     79                         cursor = resolver.query(args.uri, args.projection,
     80                                 args.selection, args.selectionArgs,
     81                                 args.orderBy);
     82                         // Calling getCount() causes the cursor window to be filled,
     83                         // which will make the first access on the main thread a lot faster.
     84                         if (cursor != null) {
     85                             cursor.getCount();
     86                         }
     87                     } catch (Exception e) {
     88                         Log.w(TAG, e.toString());
     89                         cursor = null;
     90                     }
     91 
     92                     args.result = cursor;
     93                     break;
     94 
     95                 case EVENT_ARG_INSERT:
     96                     args.result = resolver.insert(args.uri, args.values);
     97                     break;
     98 
     99                 case EVENT_ARG_UPDATE:
    100                     args.result = resolver.update(args.uri, args.values, args.selection,
    101                             args.selectionArgs);
    102                     break;
    103 
    104                 case EVENT_ARG_DELETE:
    105                     args.result = resolver.delete(args.uri, args.selection, args.selectionArgs);
    106                     break;
    107             }
    108 
    109             // passing the original token value back to the caller
    110             // on top of the event values in arg1.
    111             Message reply = args.handler.obtainMessage(token);
    112             reply.obj = args;
    113             reply.arg1 = msg.arg1;
    114 
    115             if (localLOGV) {
    116                 Log.d(TAG, "WorkerHandler.handleMsg: msg.arg1=" + msg.arg1
    117                         + ", reply.what=" + reply.what);
    118             }
    119 
    120             reply.sendToTarget();
    121         }
    122     }
    123 
    124     public AsyncQueryHandler(ContentResolver cr) {
    125         super();
    126         mResolver = new WeakReference<ContentResolver>(cr);
    127         synchronized (AsyncQueryHandler.class) {
    128             if (sLooper == null) {
    129                 HandlerThread thread = new HandlerThread("AsyncQueryWorker");
    130                 thread.start();
    131 
    132                 sLooper = thread.getLooper();
    133             }
    134         }
    135         mWorkerThreadHandler = createHandler(sLooper);
    136     }
    137 
    138     protected Handler createHandler(Looper looper) {
    139         return new WorkerHandler(looper);
    140     }
    141 
    142     /**
    143      * This method begins an asynchronous query. When the query is done
    144      * {@link #onQueryComplete} is called.
    145      *
    146      * @param token A token passed into {@link #onQueryComplete} to identify
    147      *  the query.
    148      * @param cookie An object that gets passed into {@link #onQueryComplete}
    149      * @param uri The URI, using the content:// scheme, for the content to
    150      *         retrieve.
    151      * @param projection A list of which columns to return. Passing null will
    152      *         return all columns, which is discouraged to prevent reading data
    153      *         from storage that isn't going to be used.
    154      * @param selection A filter declaring which rows to return, formatted as an
    155      *         SQL WHERE clause (excluding the WHERE itself). Passing null will
    156      *         return all rows for the given URI.
    157      * @param selectionArgs You may include ?s in selection, which will be
    158      *         replaced by the values from selectionArgs, in the order that they
    159      *         appear in the selection. The values will be bound as Strings.
    160      * @param orderBy How to order the rows, formatted as an SQL ORDER BY
    161      *         clause (excluding the ORDER BY itself). Passing null will use the
    162      *         default sort order, which may be unordered.
    163      */
    164     public void startQuery(int token, Object cookie, Uri uri,
    165             String[] projection, String selection, String[] selectionArgs,
    166             String orderBy) {
    167         // Use the token as what so cancelOperations works properly
    168         Message msg = mWorkerThreadHandler.obtainMessage(token);
    169         msg.arg1 = EVENT_ARG_QUERY;
    170 
    171         WorkerArgs args = new WorkerArgs();
    172         args.handler = this;
    173         args.uri = uri;
    174         args.projection = projection;
    175         args.selection = selection;
    176         args.selectionArgs = selectionArgs;
    177         args.orderBy = orderBy;
    178         args.cookie = cookie;
    179         msg.obj = args;
    180 
    181         mWorkerThreadHandler.sendMessage(msg);
    182     }
    183 
    184     /**
    185      * Attempts to cancel operation that has not already started. Note that
    186      * there is no guarantee that the operation will be canceled. They still may
    187      * result in a call to on[Query/Insert/Update/Delete]Complete after this
    188      * call has completed.
    189      *
    190      * @param token The token representing the operation to be canceled.
    191      *  If multiple operations have the same token they will all be canceled.
    192      */
    193     public final void cancelOperation(int token) {
    194         mWorkerThreadHandler.removeMessages(token);
    195     }
    196 
    197     /**
    198      * This method begins an asynchronous insert. When the insert operation is
    199      * done {@link #onInsertComplete} is called.
    200      *
    201      * @param token A token passed into {@link #onInsertComplete} to identify
    202      *  the insert operation.
    203      * @param cookie An object that gets passed into {@link #onInsertComplete}
    204      * @param uri the Uri passed to the insert operation.
    205      * @param initialValues the ContentValues parameter passed to the insert operation.
    206      */
    207     public final void startInsert(int token, Object cookie, Uri uri,
    208             ContentValues initialValues) {
    209         // Use the token as what so cancelOperations works properly
    210         Message msg = mWorkerThreadHandler.obtainMessage(token);
    211         msg.arg1 = EVENT_ARG_INSERT;
    212 
    213         WorkerArgs args = new WorkerArgs();
    214         args.handler = this;
    215         args.uri = uri;
    216         args.cookie = cookie;
    217         args.values = initialValues;
    218         msg.obj = args;
    219 
    220         mWorkerThreadHandler.sendMessage(msg);
    221     }
    222 
    223     /**
    224      * This method begins an asynchronous update. When the update operation is
    225      * done {@link #onUpdateComplete} is called.
    226      *
    227      * @param token A token passed into {@link #onUpdateComplete} to identify
    228      *  the update operation.
    229      * @param cookie An object that gets passed into {@link #onUpdateComplete}
    230      * @param uri the Uri passed to the update operation.
    231      * @param values the ContentValues parameter passed to the update operation.
    232      */
    233     public final void startUpdate(int token, Object cookie, Uri uri,
    234             ContentValues values, String selection, String[] selectionArgs) {
    235         // Use the token as what so cancelOperations works properly
    236         Message msg = mWorkerThreadHandler.obtainMessage(token);
    237         msg.arg1 = EVENT_ARG_UPDATE;
    238 
    239         WorkerArgs args = new WorkerArgs();
    240         args.handler = this;
    241         args.uri = uri;
    242         args.cookie = cookie;
    243         args.values = values;
    244         args.selection = selection;
    245         args.selectionArgs = selectionArgs;
    246         msg.obj = args;
    247 
    248         mWorkerThreadHandler.sendMessage(msg);
    249     }
    250 
    251     /**
    252      * This method begins an asynchronous delete. When the delete operation is
    253      * done {@link #onDeleteComplete} is called.
    254      *
    255      * @param token A token passed into {@link #onDeleteComplete} to identify
    256      *  the delete operation.
    257      * @param cookie An object that gets passed into {@link #onDeleteComplete}
    258      * @param uri the Uri passed to the delete operation.
    259      * @param selection the where clause.
    260      */
    261     public final void startDelete(int token, Object cookie, Uri uri,
    262             String selection, String[] selectionArgs) {
    263         // Use the token as what so cancelOperations works properly
    264         Message msg = mWorkerThreadHandler.obtainMessage(token);
    265         msg.arg1 = EVENT_ARG_DELETE;
    266 
    267         WorkerArgs args = new WorkerArgs();
    268         args.handler = this;
    269         args.uri = uri;
    270         args.cookie = cookie;
    271         args.selection = selection;
    272         args.selectionArgs = selectionArgs;
    273         msg.obj = args;
    274 
    275         mWorkerThreadHandler.sendMessage(msg);
    276     }
    277 
    278     /**
    279      * Called when an asynchronous query is completed.
    280      *
    281      * @param token the token to identify the query, passed in from
    282      *            {@link #startQuery}.
    283      * @param cookie the cookie object passed in from {@link #startQuery}.
    284      * @param cursor The cursor holding the results from the query.
    285      */
    286     protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
    287         // Empty
    288     }
    289 
    290     /**
    291      * Called when an asynchronous insert is completed.
    292      *
    293      * @param token the token to identify the query, passed in from
    294      *        {@link #startInsert}.
    295      * @param cookie the cookie object that's passed in from
    296      *        {@link #startInsert}.
    297      * @param uri the uri returned from the insert operation.
    298      */
    299     protected void onInsertComplete(int token, Object cookie, Uri uri) {
    300         // Empty
    301     }
    302 
    303     /**
    304      * Called when an asynchronous update is completed.
    305      *
    306      * @param token the token to identify the query, passed in from
    307      *        {@link #startUpdate}.
    308      * @param cookie the cookie object that's passed in from
    309      *        {@link #startUpdate}.
    310      * @param result the result returned from the update operation
    311      */
    312     protected void onUpdateComplete(int token, Object cookie, int result) {
    313         // Empty
    314     }
    315 
    316     /**
    317      * Called when an asynchronous delete is completed.
    318      *
    319      * @param token the token to identify the query, passed in from
    320      *        {@link #startDelete}.
    321      * @param cookie the cookie object that's passed in from
    322      *        {@link #startDelete}.
    323      * @param result the result returned from the delete operation
    324      */
    325     protected void onDeleteComplete(int token, Object cookie, int result) {
    326         // Empty
    327     }
    328 
    329     @Override
    330     public void handleMessage(Message msg) {
    331         WorkerArgs args = (WorkerArgs) msg.obj;
    332 
    333         if (localLOGV) {
    334             Log.d(TAG, "AsyncQueryHandler.handleMessage: msg.what=" + msg.what
    335                     + ", msg.arg1=" + msg.arg1);
    336         }
    337 
    338         int token = msg.what;
    339         int event = msg.arg1;
    340 
    341         // pass token back to caller on each callback.
    342         switch (event) {
    343             case EVENT_ARG_QUERY:
    344                 onQueryComplete(token, args.cookie, (Cursor) args.result);
    345                 break;
    346 
    347             case EVENT_ARG_INSERT:
    348                 onInsertComplete(token, args.cookie, (Uri) args.result);
    349                 break;
    350 
    351             case EVENT_ARG_UPDATE:
    352                 onUpdateComplete(token, args.cookie, (Integer) args.result);
    353                 break;
    354 
    355             case EVENT_ARG_DELETE:
    356                 onDeleteComplete(token, args.cookie, (Integer) args.result);
    357                 break;
    358         }
    359     }
    360 }
    View Code

    这里面实际上整个对ContentProvider的增删改查都是交给Handler来实现的,你只需要实现四个接口,告诉AsyncQueryHandler操作完毕之后需要干什么,然后调用相关的接口即可。所以查询的时候也可以通过它实现。

    注意:根据项目经验,如果用户向搜索框中输入文字进行搜索,假设连续快速输入sz两个字母,那么你会先搜索s,然后搜索sz,我们假设一种极端情况:因为含有s的搜索结果很大概率上比sz多,假设搜索s的时间为10s,而搜索sz的时间只需要3s,你去更新界面,其实是3s后搜索sz的结果出现,再过7s搜索s的结果出现,而此时搜索框中的输入内容是sz,出现不正常的情况。所以我们在搜索sz之前要做的就是取消搜索s的任务,AsyncQueryHandler提供了该接口:

     1 /**
     2      * Attempts to cancel operation that has not already started. Note that
     3      * there is no guarantee that the operation will be canceled. They still may
     4      * result in a call to on[Query/Insert/Update/Delete]Complete after this
     5      * call has completed.
     6      *
     7      * @param token The token representing the operation to be canceled.
     8      *  If multiple operations have the same token they will all be canceled.
     9      */
    10     public final void cancelOperation(int token) {
    11         mWorkerThreadHandler.removeMessages(token);
    12     }
    View Code

    来实现该功能,每一个操作都由一个Token进行标记,取消的时候也需要用这个标记进行。

    四、总结

    总体来说,除了暴力搜索方法,其余的方法大同小异,不论设计的多么复杂,无非是将搜索的过程放置在异步线程里面,搜索完毕之后更新界面,我们只要记住这一点即可。之所以写文章介绍后面几种方法,是因为既然Android已经帮我们设计好了使用异步线程查询的代码架构,我们直接用即可,肯定更加健壮,也省去重复造轮子的力气。

  • 相关阅读:
    获取指定位置的颜色值
    《QTP自动化测试进阶》读书笔记
    Delphi自动化测试
    QTP中DOM的递归调用取html页面值
    PowerShell
    Dictionary对象与XML文件之间的导入导出
    BPT业务流程自动化测试
    VisualStudio 自动化测试框架 Coded UI Test
    使用QTP查找邮件
    QTP11视频
  • 原文地址:https://www.cnblogs.com/lqminn/p/3093012.html
Copyright © 2011-2022 走看看