android 源码角度全方位理解filter

写一个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

写篇帖子好难!

时间: 2024-10-06 09:02:35

android 源码角度全方位理解filter的相关文章

从源码角度深入理解Toast

Toast这个东西我们在开发中经常用到,使用也很简单,一行代码就能搞定: 1: Toast.makeText(this, "333", Toast.LENGTH_LONG).show(); 但是我们经常会遇到这样一种情况,比如说我们有两个按钮,每次点击之后就会弹出一个Toast,但是如果这两个按钮快速点击,我们看到的效果是这样的:   但实际上我们想要的效果应该是这样的:   源码解读 看了上面两张效果图的对比之后,我们应该明白了,第一种情况是在我们点击完之后,Toast还在不断的显示

Activity生命周期的回调,你应该知道得更多!--Android源码剖析(上)

转载请标明原文地址:http://blog.csdn.net/yalinfendou/article/details/46909173[yalinfendou的博客] 学习Android近一年,最近几天总算把Activity启动的生命周期回调流程走通了,因为所涉及的知识点太多,赶快做了笔记,不然过几天就忘了.强烈推荐<Android内核剖析>这本书,虽然随着Android版本的不断迭代更新,代码变化了不少,仍具有很强的参考价值. 本篇所涉及知识点: Android 从开机到第一个Activit

Android 源码系列之&lt;十一&gt;从源码的角度深入理解AccessibilityService,打造自己的APP小外挂(下)

转载请注明出处:http://blog.csdn.net/llew2011/article/details/52843637 在上篇文章Android 源码系列之<十>从源码的角度深入理解AccessibilityService,打造自己的APP小外挂(上)中我们讲解了通过AccessibilityService实现自动安装APK小外挂的操作流程,如果你还没有看过上篇文章请点击这里.在这篇文章中我将带领小伙伴从源码的角度来深入学习一下AccessibilityServie的技术实现原理,希望这

Android 源码系列之&lt;十四&gt;从源码的角度深入理解LeakCanary的内存泄露检测机制(下)

转载请注明出处:http://blog.csdn.net/llew2011/article/details/52958567 在上边文章Android 源码系列之<十三>从源码的角度深入理解LeakCanary的内存泄露检测机制(中)由于篇幅原因仅仅向小伙伴们讲述了在Android开发中如何使用LeakCanary来检测应用中出现的内存泄露,并简单的介绍了LeakCanary的相关配置信息.根据上篇文章的介绍我们知道LeakCanary为了不给APP进程造成影响所以新开启了一个进程,在新开启的

Android 源码系列之&lt;十三&gt;从源码的角度深入理解LeakCanary的内存泄露检测机制(中)

转载请注明出处:http://blog.csdn.net/llew2011/article/details/52958563 在上篇文章Android 源码系列之<十二>从源码的角度深入理解LeakCanary的内存泄露检测机制(上)中主要介绍了Java内存分配相关的知识以及在Android开发中可能遇见的各种内存泄露情况并给出了相对应的解决方案,如果你还没有看过上篇文章,建议点击这里阅读一下,这篇文章我将要向大家介绍如何在我们的应用中使用square开源的LeakCanary库来检测应用中出

Android 源码系列之&lt;十&gt;从源码的角度深入理解AccessibilityService,打造自己的APP小外挂(上)

转载请注明出处:http://blog.csdn.net/llew2011/article/details/52822148 说起外挂特别是玩游戏的小伙伴估计对它很熟悉,肯定有部分小伙伴使用过,至于为什么使用它,你懂得(*^__^*) --我最早接触外挂是在大二的时候,那时候盛行玩QQ农场,早上一睁眼就是打开电脑先把自己的菜收了,收完之后再去偷别人的:后来童靴说非凡软件上有一个偷菜外挂,于是赶紧整了一个,有了外挂之后就告别了体力时代,省时又省力--既然在PC上有外挂,那在智能手机上可以做外挂呢?

Android布局性能优化—从源码角度看ViewStub延迟加载技术

在项目中,难免会遇到这种需求,在程序运行时需要动态根据条件来决定显示哪个View或某个布局,最通常的想法就是把需要动态显示的View都先写在布局中,然后把它们的可见性设为View.GONE,最后在代码中通过控制View.VISIABLE动态的更改它的可见性.这样的做法的优点是逻辑简单而且控制起来比较灵活.但是它的缺点就是,耗费资源,虽然把View的初始可见View.GONE但是在Inflate布局的时候View仍然会被Inflate,也就是说仍然会创建对象,会被实例化,会被设置属性.也就是说,会

从源码角度看Android系统SystemServer进程启动过程

copy frome :https://blog.csdn.net/salmon_zhang/article/details/93208135 SystemServer进程是由Zygote进程fork生成,进程名为system_server,主要用于创建系统服务. 备注:本文将结合Android8.0的源码看SystemServer进程的启动过程以及SystemServer进程做了哪些重要工作. 1. SystemServer进程启动的起点从<从源码角度看Android系统Zygote进程启动过

【原创】源码角度分析Android的消息机制系列(三)——ThreadLocal的工作原理

ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 先看Android源码(API24)中对ThreadLocal的定义: public class ThreadLocal<T> 即ThreadLoca是一个泛型类,再看对该类的注释: /** * This class provides thread-local variables. These variables differ from * their normal counterparts in that each thread th