Android非mtk平台T9的实现

开发MTK平台的机器一般比较幸福,非常友好的支持(对比国外大厂),N多国内运营商的功能支持早有实现,比如T9。但是开发高大上的高通咋办?木有T9啊。不过目前有多种方案可选则。

实现mtk的sqlite方法

mtk的Contact和Dialer添加了很多功能,例如多选联系人号码,群组等,所以不少厂商高通平台的机器照样是移植mtk的代码。如果T9也能实现,那么不同平台基本就是一套代码了,维护就容易多了。 但是mtk未开放sqlite自定义函数DIALER_SEARCH_MATCH_FILTER和 DIALER_SEARCH_MATCH的源码,虽然mtk的eservice中会有不少单子上提及dialer search的实现,不过mtk的网站是反网络爬虫的,靠百度google搜索引擎是无用的。
在MTK的T9搜索流程分析中我已经详细分析了mtk T9的流程,看懂的话可以实现这两个sqlite方法。
自定义的方法见 SQLite 加入自定义函数,有C++和java两种方法(链接文章中举的java例子是错误的,这个要注意)。不过java层的SQLiteDatabase.CustomFunction是行不通的,原因是CustomFunction没有返回值。 其实java的原理实际最后还是和C++一样,只不过多了jni的使用:
frameworks/base/core/jni/android_database_SQLiteConnection.cpp
static void nativeRegisterCustomFunction(JNIEnv* env, jclass clazz, jlong connectionPtr,
        jobject functionObj) {
    ...
    int err = sqlite3_create_function_v2(connection->db, name, numArgs, SQLITE_UTF16,
            reinterpret_cast(functionObjGlobal),
            &sqliteCustomFunctionCallback, NULL, NULL, &sqliteCustomFunctionDestructor);
    ...
}
注册方法中使用sqlite3_create_function_v2方法注册custom方法,这个和C++代码的添加方式是基本一样的。
static void sqliteCustomFunctionCallback(sqlite3_context *context,
        int argc, sqlite3_value **argv) {
    JNIEnv* env = AndroidRuntime::getJNIEnv();

    // Get the callback function object.
    // Create a new local reference to it in case the callback tries to do something
    // dumb like unregister the function (thereby destroying the global ref) while it is running.
    jobject functionObjGlobal = reinterpret_cast(sqlite3_user_data(context));
    jobject functionObj = env->NewLocalRef(functionObjGlobal);

    jobjectArray argsArray = env->NewObjectArray(argc, gStringClassInfo.clazz, NULL);
    if (argsArray) {
        for (int i = 0; i < argc; i++) {
            const jchar* arg = static_cast(sqlite3_value_text16(argv[i]));
            if (!arg) {
                ALOGW("NULL argument in custom_function_callback.  This should not happen.");
            } else {
                size_t argLen = sqlite3_value_bytes16(argv[i]) / sizeof(jchar);
                jstring argStr = env->NewString(arg, argLen);
                if (!argStr) {
                    goto error; // out of memory error
                }
                env->SetObjectArrayElement(argsArray, i, argStr);
                env->DeleteLocalRef(argStr);
            }
        }

        // TODO: Support functions that return values.
        env->CallVoidMethod(functionObj,
                gSQLiteCustomFunctionClassInfo.dispatchCallback, argsArray);

error:
        env->DeleteLocalRef(argsArray);
    }

    env->DeleteLocalRef(functionObj);

    if (env->ExceptionCheck()) {
        ALOGE("An exception was thrown by custom SQLite function.");
        LOGE_EX(env);
        env->ExceptionClear();
    }
}
上面是java层custom方法回调的代码,可以看出完全没有返回值的相关代码(sqlite中的返回值是通过sqlite3_result_int等方法返回的,并不是通过return语句,sqlite定义的custom方法返回值都是void )。这样java层的代码只能是干些无返回值的活,例如MediaProvider中是在删除数据库的时候删除对应项目的文件。
所以只能是通过C++的方式实现这两个方法。

开源


handsomezhou

https://github.com/handsomezhou/T9Search
csdn上关于Android流传最广的好像就是android T9 搜索联系人分析与实现(支持多音字),这个有完整的代码和demo例子,简直太好用了。看了下作者是90年的,吾辈好羞愧 。虽然原理是读取全部联系人,然后for循环一条一条处理的,但是效率比我预想好得多,在2000个联系人的情况下搜索还是很快。

kewenya

https://github.com/kewenya/SearchCoreTest
虽然作者说了Android也能用,不过没有Android的demo,代码中我看到也只有ios的实现。大概看了下代码,联系人数据用了自定义SearchTree的结构,插入数据后可以用二分查找来寻找要匹配的对象,效率理论上要比handsomezhou的好一些。这个拿来给Android用修改的工作就要大一些了,有两种思路:二分查找的思想挪到handsomezhou的项目上;代码是Objective-C,要转成标准的C++,然后实现上节中介绍的sqlite函数,这个初始化数据源的工作很简单,因为就只有一条数据。

oasisfeng

https://github.com/oasisfeng/android-smartdial-chinese
这个并不是完整的T9实现,肯定不能作为商用,但是对爱刷Android原生或者cm版本rom的朋友来说是福音。 oasisfeng是开发绿色守护的大神,稍微修改几十行代码就完成了T9搜索80%的功能,是用 Xposed插桩往Dialer中添加运行代码的。对Android代码的理解和直觉真是比我等甩了n条街(人家不是专职搞Dialer的呀)。


自己实现

熟悉T9的匹配规则,然后自己实现。这个不少厂商都是这么干的呀,之前遇见过前辈写的,不过那个实现真是反面教材,j、jj、jjj变量命名到处是,for循环好多层,2000个联系人查一次得5秒以上的时间。关键是我看不懂代码流程和算法,不过这个实现还在好多机器上用了........自己查看不少文章后有几个小想法写下供大家参考:

Trie树

百度百科:又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
数据结构和算法学的好同学一下就会想到这个,不过网上我没有搜索到使用Trie树的T9实现。扫描所有联系人生成Trie树,这样查询的效率就几乎是常数了。

数据源的更新

不论是什么方法,基本都会生成一份初始的数据集合以便查询,那么就会面临一个问题,如果联系人有变动怎么办。最简单的就是重新生成一份数据集合,但是如果数据量很大那么效率就会很慢,最好是只针对变化的数据做修改就成了。其实这种思想在android源码中已有实现了,见 Android拨号搜索机制源码分析(原)。这篇文章中讲解了原生代码中拨号盘搜索的流程(原生的只能匹配号码和英文名称,没有考虑中文),其中2.2小节“数据库的更新”就介绍了数据库是怎么做局部更新的,这个思想可以借鉴到其它地方,即使那种实现方法不使用数据库。

数据源的保存

数据集的建立是比较花时间的,手机用户修改联系人数据库也不是高频的操作,那么可以把数据源以某种方式保存,不用每次都从零开始。

结合这几种想法,建立trie树,trie用于搜索;同时建立联系人列表(每个联系人又包含了trie树中相关节点所组成的集合),用于更新trie树。更新使用局部更新。数据可以序列化存储在内部存储中。不过我等懒人直接用handsomezhou的就可以了




你可能感兴趣的:(android,Android,T9搜索,非MTK)