写一个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();
其实用到的全局变量只有8个,并且有一半是常量:两个handler,一个delayer,一个对象锁。google开发大牛用这几个变量加上为数不多的几个局部变量就做出来了一个拓展性极佳的过滤器,不得不让人钦佩。
首先看构造方法:
public Filter() { mResultHandler = new ResultsHandler(); }
构造方法中二小强之一——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); }
注意到主要的操作就是: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); } }
官方文档介绍这个每次调用这个方法将会开启一个异步的过滤操作,调用这个方法会取消之前没有执行的过滤操作,让我们分析一下他们是如何来做的。
首先整个方法都在同步块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; } } }
其实除了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
写篇帖子好难!