android里Filter的研究

一、概述:

过滤器通过过滤模式来约束数据,过滤操作是通过调用filter(CharSequence)或者filter(CharSequence, android.widget.Filter.FilterListener)这些异步方法来完成的。以上方法一旦被调用,过滤请求就会被递交到请求队列中等待处理,同时该操作会取消那些之前递交的但是还没有被处理的请求。

其工作过程可以简单理解为:有一个工作队列在处理任务,但每次只能处理一个任务。每次提交一个任务的时候,如果工作队列中没有正在执行的任务,则立马执行该任务。如果有执行的任务,则该任务等待。如果队列中有正在执行的任务,并且有等待的任务,则取消所有等待的任务,把该任务放入等待的过程中。所以在任何时候,最多只能有一个任务在执行,一个任务在等待。

二、主要方法:

public void final filter(CharSequence constraint, FilterListener listener);

执行一个异步的过滤操作请求。

protected abstract FilterResults performFiltering(CharSequence constraint);

根据约束条件调用一个工作线程来过滤数据,子类必须实现该方法来执行过滤操作。过滤结果以Filter.FilterResults的形式返回,然后在UI线程中通过publishResults(CharSequence, android.widget.Filter.FilterResults)方法来发布。

protected abstract void publishResults(CharSequence constraint, FilterResults results);

通过调用UI线程在用户界面发布过滤结果。

三、实现原理:

在一个新开的工作线程里执行过滤的操作,过滤的结果在UI线程里publish。

1.在Filter类里使用了2个Handler:RequestHandler是用来处理过滤操作的,ResultHandler是用来发布过滤结果的。

private Handler mThreadHandler;

private Handler mResultHandler;

public Filter() {

mResultHandler = new ResultHandler();

}

可见,ResultHandler在构造方法里创建,一般说来是与UI线程进行绑定的,最后过滤出结果后,publishResults是通过ResultHandler来进行分发消息的,所以最后的回调过程是在UI线程执行的,这里实现的很巧妙,可以学习之。

疑问:如果Filter类是在一个子线程里进行创建的,则在publishResults时,就不是在UI线程里执行了?实际结果是,如果在子线程里 new一个Filter对象,则会直接报错,原因是子线程里还没有构造Looper对象,需要在new Handler()之前调用Looper.prepare()方法。

2.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);

}

}

可以看到,在这里才会创建一个HandlerThread对象,这是一个子线程,随后会创建一个RequestHandler,该Handler会与该子线程绑定。以后所有的过滤请求,都通过RequestHandler进行分发,所有的过滤操作,都会在子线程中执行。

在发起本次请求之前,会通过RequestHandler取消掉所有还未执行的消息。

再看一下RequestHandler的内部实现,

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

}

//在这里可以看到,会调用performFiltering()方法进行过滤,接着会通过ResultHandler进行结果发布。

synchronized (mLock) {

if (mThreadHandler != null) {

Message finishMessage = mThreadHandler.obtainMessage(FINISH_TOKEN);

mThreadHandler.sendMessageDelayed(finishMessage, 3000);

}

}

//在所有操作执行完毕后,会延迟3秒发送一个结束的消息,该消息会终止掉HandlerThread线程里的消息循环。这个地方很巧妙:1.如果连续的发起过滤请求,则会一直使用之前创建的HandlerThread线程来处理过滤操作,不会频繁的创建线程,过多的损耗性能;2.如果长时间没有过滤请求,比如超过3s后,则会终止点该线程,因为该线程实际上是个空闲的线程,可以终止掉,同样的减少性能损耗。注意要加锁操作。

break;

case FINISH_TOKEN:           //这里处理终止线程的操作

synchronized (mLock) {

if (mThreadHandler != null) {

mThreadHandler.getLooper().quit();

mThreadHandler = null;

}

}

break;

四、总结:

1.巧妙的使用了HandlerThread来创建一个带Looper的Thread。

2.通过与不同线程绑定的Handler,来实现异步的过滤查询操作。

3.类似AutoCompleteTextView的实现,通过Filter来实现。

4.参考Filter的实现方式,可以在需要频繁异步查询的地方,采用同样的设计方法。比如输入框里的联网自动提示功能。

5.AsyncQueryHandler实现原理与之类似,都是采用了HandlerThread,使用一个工作线程来处理事情。

时间: 2024-10-07 14:02:50

android里Filter的研究的相关文章

android 1.6 launcher研究之自定义ViewGroup (转 2011.06.03(二)——— android 1.6 launcher研究之自定义ViewGroup )

2011.06.03(2)——— android 1.6 launcher研究之自定义ViewGroup2011.06.03(2)——— android 1.6 launcher研究之自定义ViewGroup 1.用xml来作为ViewGroup里面的View参考:http://www.eoeandroid.com/thread-30888-1-1.html MyViewGroup.java package com.lp; import android.content.Context; impo

android 5 HOOK 技术研究之 ADBI 项目

简介 adbi 是一个android平台的二进制注入框架,源码开放在github上 :  ADBI 项目 ,从hook技术的分类来说,其属于so注入+inline hook, 这种方式的套路是:基于linux系统的ptrace机制,attach一个目标进程,注入一个动态链接库进入目标进程的地址空间,然后用so里边的函数地址替换目标进程地址空间里原有的函数地址(老的函数地址一般也需要保存起来). 源码目录 hijack:  可执行程序,用于注入一个so到目标进程 libbase:  注入库,提供h

android 5 HOOK 技术研究之 ADBI 项目 02

源码分析 hijack.c 这个文件实现了一个注入工具,可以向 -p 参数指定的进程注入一个so. 要实现这个效果,首先,需要得到目标进程若干函数如dlopen函数的地址,其次,需要能影响目标进程的正常执行流,让其中间某个时候执行dlopen加载指定的库,最后,还要能用动态加载的so里的函数覆盖原有内存里的函数. 下面开始研究,如何得到目标进程指定函数的地址,首先要得到的是dlopen函数的地址,adbi是这么做的: void *ldl = dlopen("libdl.so", RTL

android里单例对象和某些数据被释放的问题

接触正式的android开发已经有一段时间了,项目的第一个版本终于快完成了.有一次自己在测试的时候,把自己的android项目切到后台,同时打开了几个应用之后重新切回到自己的app,发现报错了.经过排查,发现是自己的单例对象中的数据被释放掉了,也就是int变量的值 变成了0,string变量的值变成了null. 我的单例一开始是这样的(举例); public class UserInfo { private static UserInfo userInfo = null; private int

Android与Unity交互研究

Android与Unity交互研究 转载请注明出处:http://blog.csdn.net/crazy1235/article/details/46733221 Android与Unity交互研究 unity与android交互的由来 unity简单介绍 unity与android交互介绍 unity调用android的方法 android调用untiy的方法 unity与android交互的由来 本人在项目开发过程中,遇到这样一个需求,把unity的场景放到android中去显示.刚开始做的

欠揍的ffmpeg,调试android里的ffmpeg (1)

目标问题 对于jpeg的编码为什么不用libjpeg而是用mpeg(没错就是这电影老古董格式)里代码? 其实发现就连wmv等等一些平时用不着的破东西,都是和mpeg老古董共用一套代码.jpeg和mpeg没什么关系吧?overhead不大吗? 为什么mjpeg(就是连续的jpeg)编码输出时,编码第N+1副图时才真正的输出第N幅图的jpeg编码结果? 每得到一副原图输入数据,那么内存就似乎被copy到新内存,然后废弃,巨大的图片的时候,这种copy很浪费CPU,如何让他不要copy而是直接用原始内

Android利用Filter过滤数据

MainActivity如下: package cc.testfilterable; import java.util.ArrayList; import java.util.HashMap; import android.os.Bundle; import android.widget.ListView; import android.app.Activity; import android.content.Context; /** * Demo描述: * 利用Filter过滤数据 * 可用于

Android event logcat的研究

经常有这样的需求:在程序A启动之后,在这个程序上覆盖一个界面,让用户输入密码后才能启动程序A,这类场景主要集中在安全软件中. 那应该怎样得知某某程序启动了,系统没有相应的广播,这很难知道程序启动了. 既然没有通知,那我们就只能用最笨的方法:主动去询问最近启动过哪些程序. 这需要用后台服务器一直跑,每隔一段时间就询问一次.程序启动的时候,会在Logcat中打印出一些日志信息,其中就有类似下面的日志,表明是启动了哪个程序 01-21 15:09:56.957: I/ActivityManager(2

Android里使用正則表達式

在Android里怎样使用正則表達式: 以验证username为例.username一般字母开头,同意字母数字下划线.5-16个字节: String regEx = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$"; Pattern pattern = Pattern.compile(regEx); Matcher matcher = pattern.matcher(userName); boolean b = matcher.matches(); 匹配的话b为true.否