65.9K
CodeProject 正在变化。 阅读更多。
Home

口袋里的搜索引擎 – 介绍 Android 上的 dtSearch

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2014年9月29日

CPOL

9分钟阅读

viewsIcon

40311

downloadIcon

322

正在为 Android 寻找全文搜索功能并想用 dtSearch 进行查找?.NET 开发者,请同时参阅本文作者的《使用 dtSearch 进行分面搜索》(可从文章链接获取)。

.NET 开发者,请同时参阅本文作者的 《使用 dtSearch 进行分面搜索》

第一部分:使用 dtSearch 进行分面搜索(使用 SQL 和 .NET)
第二部分:使用 dtSearch 和 Telerik UI for ASP.NET 极大地增强您的搜索体验

引言

我就是那种喜欢尝试使用不同手机,并学习如何在每种手机上编程的人之一。最近,我正在进行一个简单的 Android 项目,发现我的应用需要支持全文搜索。我想能够搜索我的内容,并像在网站上一样提供一个很酷的结果列表。我知道……这说明我在开发手机项目时,骨子里还是个 Web 开发者。尽管如此,当我开始研究这个问题时,我发现我的朋友 dtSearch 推出了他们搜索库的全新 Android 版本。我抓住了机会,在新环境中使用了一个熟悉的工具。

以前,我曾使用 dtSearch 为网页上的产品信息建立索引并提供高亮显示的结果。这次,我将向您展示一个简单的记事本示例应用的演示,该应用将允许我搜索我存储的笔记,并在我的 Android 设备上返回结果列表。

入门 – 引用库

为了在 Android 中使用 dtSearch 库,您的应用必须满足以下要求:

  • Android API 9 或更高版本(Gingerbread)
  • Intel 或 ARM 处理器

接下来,您需要将 dtSearch.jar 和原生库文件放入项目中的 libs/ 文件夹。在我的情况下,由于我使用 Gradle 和 Android Studio,我将 jar 文件放在了 libs 文件夹中,并将原生库放在了我的 src/main/jniLibs 文件夹中。该库也支持 Eclipse,您只需将 jar 和原生库放入项目的 libs/ 文件夹中即可。

Eclipse 的文件夹位置
Android Studio 的文件夹位置

放置完文件后,我将以下条目添加到 app/build.gradle 的 dependencies 部分:

dependencies {
   compile files('libs/dtSearchEngine.jar')
}

这会告诉 Java 编译器在构建应用程序时包含搜索引擎引用。

附加资源 – Font Awesome

对于这个示例项目,我还包含了 Font Awesome,以便我能轻松创建一些外观不错的按钮。我将 fontawesome-webfont.ttf 文件添加到 assets 文件夹,将 font_awesome.xml 资源添加到 res 文件夹,并将两个带有 FontAwesome 视图定义的 Java 文件添加到我的包路径中。

现在,我就可以使用如下 XML 语法创建外观精美的按钮了:

<com.FontAwesome.Example.ButtonAwesome

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:text="@string/fa_plus"

            android:id="@+id/addButton"

            android:clickable="true" />

        <com.FontAwesome.Example.ButtonAwesome

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:text="@string/fa_search"

            android:id="@+id/findButton"/>

我可以使用熟悉的 fa_* 语法来引用图标,就像在 CSS 中一样。我的按钮外观如下:

我的笔记对象

为了使这个笔记对象尽可能简单,便于此次演示,我包含了 IdTitleContentCreatedModified 字段。我的 Note.java 文件内容如下:

import android.database.Cursor;
import java.util.Date;

public class Note {

    private int id;
    private String title;
    private String content;
    private Date created;
    private Date modified;

    public Note() {}

    public Note(int id, String title, String content, Date created, Date modified) {
/** Omitted for brevity **/
    }

    public Note(Cursor c) {

/** Omitted for brevity **/

    }

    public int get_ID() {
        return id;
    }

    public void set_ID(int id) {
        this.id = id;
    }

    public String get_Title() {
        return title;
    }

    public void set_Title(String title) {
        this.title = title;
    }

    public String get_Content() {
        return content;
    }

    public void set_Content(String content) {
        this.content = content;
    }

    public Date get_Created() {
        return created;
    }

    public void set_Created(Date created) {
        this.created = created;
    }

    public Date get_Modified() {
        return modified;
    }

    public void set_Modified(Date modified) {
        this.modified = modified;
    }

    @Override
    public String toString() {
        return this.title;
    }

}

配置搜索索引

对于这个示例,我希望在笔记保存操作发生时构建和索引记事本中的笔记。与 .NET 和 Java 版本的 dtSearch 引擎一样,您需要从 DataSource 开始配置搜索。DataSource 是一个类,负责获取和格式化内容供 dtSearch 引擎索引。

我创建了一个实现 com.dtsearch.engine.DataSource 接口的 NoteDataSource。在该类中,我创建了一个指向我的 NoteRepository 对象的引用,该对象管理与存储我的笔记的 SQLite 数据库的简单交互。

public class NoteDataSource implements DataSource {

    private final Cursor _NotesCursor;
    private NoteRepository _NoteRepository;
    private int _RecordNumber = 0;
    private Note _CurrentNote;
    private int _TotalRecords;

    public NoteDataSource(Context ctx) {

        _NoteRepository = new NoteRepository(ctx);

        try {
            _NoteRepository.open();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        _NotesCursor = _NoteRepository.SelectAllNotes();

    }

}

DataSource 提供了搜索索引器能够导航和使用您希望其索引的数据的方法。

要配置的第一个方法是 rewind,它在搜索索引器准备开始索引我的数据时被调用。它期望返回一个布尔值来指示成功。在本例中,我将记录号重置为零,将数据库游标移动到第一条记录,并计算 Note 表中的总记录数。

    @Override
    public boolean rewind() {

        _RecordNumber = 0;
        _NotesCursor.moveToFirst();
        _TotalRecords = _NoteRepository.SelectAllNotes().getCount();

        return true;
    }

下一个要设置的方法是 getNextDoc() – 此方法是搜索索引器如何迭代数据集合以进行呈现。每次索引器准备好处理新记录时都会调用此方法。我的方法实现非常简单:

    @Override
    public boolean getNextDoc() {

        if (_RecordNumber == _TotalRecords) return false;

        Log.w("GetNextDoc", "Totalrecords: " + String.valueOf(_TotalRecords) + " RecordNumber: " + String.valueOf(_RecordNumber));

        // Get the current note
        _RecordNumber++;
        _CurrentNote = new Note(_NotesCursor);
        _NotesCursor.move(1);

        return true;

    }

在此方法中,索引器会检查返回的布尔值,以确定它是否还有更多记录要遍历数据源。当所有记录都处理完毕后,我返回 false 表示没有更多记录。_CurrentNote 是一个我使用自定义构造方法从 SQLiteCursor 加载的对象,该构造方法知道如何解析 SQLite 后备表。

这些方法很容易配置,因为它们只是管理与我的后备数据库的连接。下一个要配置的方法与我正在索引的 Note 对象中的字段有关。

    @Override
    public String getDocText() {

        // The text of the note to index
        return _CurrentNote.get_Content();

    }

    @Override
    public String getDocFields() {

        // Fields and content of the note to index for faceted search 
        return "";
    }

    @Override
    public String getDocName() {
        return String.valueOf(_CurrentNote.get_ID());
    }

    @Override
    public String getDocDisplayName() {
        return _CurrentNote.get_Title();
    }

    @Override
    public Calendar getDocModifiedDate() {

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(_CurrentNote.get_Modified());
        return calendar;

    }

    @Override
    public Calendar getDocCreatedDate() {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(_CurrentNote.get_Created());
        return calendar;
    }

这些字段 getter 应该非常简单,只需获取数据并将其格式化给索引器即可。DocFields 是一个属性,允许我(可选地)启用分面搜索。如果我为此准备索引,我会输出 title 和 content 字段,并在字段和它们的值之间添加必需的制表符分隔符。DocName 字段是一个机会,可以存储有关 Note 的唯一信息,以便我以后可以引用它。在这种情况下,索引我的笔记表的 ID 主键是最有意义的。最后,DocDisplayName 是一个“人类友好”的术语,可以存储并作为搜索结果集的一部分显示。

构建搜索索引

配置好 DataSource 后,我可以编写一个简单的方法来重建搜索索引。我可以做得更花哨,编写方法来处理笔记更新、添加和删除时的增量更新。然而,这个记事本太小了,每次重建索引都微不足道。因此,我将在我的应用中每次执行保存操作时调用此 Rebuild 方法。

我向我的 NoteDataSource 类添加了一个公共静态方法来集中构建索引的逻辑。关于索引需要知道的第一件事,也是我这个进入 Android 世界的 Web 开发者遇到的第一个棘手问题是,我需要在设备上分配一个文件夹来存储我的搜索索引。我在一个名为 getIndexLocation 的静态 getter 方法中识别并创建了我的文件夹:

    public static File getIndexLocation(Context ctx) {

        File thisDir = null;
        try {
            thisDir = ctx.getDir("index", Context.MODE_PRIVATE);
             } catch (Exception ex) {
            ex.printStackTrace();
        }
        return thisDir;

    }

很简单!这段代码只是配置了一个名为 index 的私有文件夹,该文件夹位于当前应用的 data 文件夹下。然后,我配置了一个 IndexJob 来使用我的 NoteDataSource,并将索引写入我的新索引文件夹。

public static void Rebuild(Context ctx) {

        IndexJob job = new IndexJob();
        NoteDataSource source = new NoteDataSource(ctx);
        job.setDataSourceToIndex(source);
        job.setIndexPath(getIndexLocation(ctx).getAbsolutePath());
        job.setActionCreate(true);
        job.setActionAdd(true);
        job.setCreateRelativePaths(true);
        job.setIndexingFlags(IndexingFlags.dtsIndexCacheTextWithoutFields | IndexingFlags.dtsIndexCacheText);

        job.execute();

        if (job.getErrors() != null && job.getErrors().getCount() > 0) {
            JobErrorInfo errors = job.getErrors();
            Log.w("Index Build", errors.getMessage(0));
        }

    }

在此方法中需要注意的是,我配置了 IndexJob 来创建索引并向其中添加数据。笔记在不使用 DocFields 方法的情况下被索引,并且 DocText 返回的文本被索引。最后,执行了作业。我在末尾添加了一些代码来捕获和报告过程中的任何错误。

搜索索引

索引构建完成后,我用一个 TextView、另一个 FontAwesome 按钮和一个用于存放结果的 ListView 设计了一个简单的搜索活动。

我将按钮的点击事件连接到 NoteDataSource 的一个名为 DoSearch 的方法,该方法会搜索索引并将结果绑定到 ListView。此方法如下所示:

    public static List<Note> DoSearch() {

        SearchJob job = new SearchJob();
        job.setIndexesToSearch(NoteDataSource.getIndexLocation(this).getAbsolutePath());
        job.setMaxFilesToRetrieve(10);
        job.setRequest(get_SearchText());
        job.setTimeoutSeconds(3);
        job.execute();

        return TransformResultsToNotes(job.getResults());

    }

创建了一个 SearchJob,并配置它搜索 NoteDataSource 中定义的索引位置文件夹。最多检索 10 条记录,要搜索的文本通过 setRequest 方法设置。我将搜索超时时间配置为 3 秒,这对于移动应用来说已经是很长的时间了,然后执行了搜索。

搜索结果通过 TransformResultsToNotes 方法转换为 Note 对象。

    private static List<Note> TransformResultsToNotes(SearchResults results) {
        
        List<Note> foundNotes = new ArrayList<Note>(results.getCount());
        Log.w("Search", "Result count: " + results.getCount());
        
        for (int i=0; i<results.getCount(); i++) {
            results.getNthDoc(i);
              foundNotes.add(new Note(results.getDocId(), results.getDocDisplayName(), "DOC TEXT", results.getDocDate(), results.getDocDate() ));
        }
        
        return foundNotes;
        
    }

此方法通过迭代提交的 SearchResults 并调用各种 getter 方法来检索存储在我们的 NoteDataSource 中的索引属性,从而创建并返回一个笔记列表。请注意,我将“DOC TEXT”作为笔记的内容返回。这是一种快捷方式,因为我不会在 ListView 中显示笔记的实际文本。相反,我只显示标题,并保留 ID 以便以后导航到笔记的全文。

最后,在我的 SearchNotes 活动类的 BindResultsToListView 方法中,我使用标准的 ArrayAdapter 将笔记列表绑定到我的 ListView。

    private void BindResultsToListView(List<Note> foundNotes) {
        
        try {
            ArrayAdapter<Note> dataAdapter = new ArrayAdapter<Note>(
                    this, android.R.layout.simple_list_item_1, android.R.id.text1, foundNotes
            );
            ListView lv = (ListView) findViewById(R.id.searchNoteList);
            lv.setAdapter(dataAdapter);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        
    }

在示例代码中,我使用了标准的 Android simple_list_item_1 布局,并将 Note.toString() 方法的结果绑定到布局内的 text1 元素。

摘要

为我的记事本应用添加一个丰富的搜索引擎库就是这么简单。现在,我的手机上拥有了一个功能强大的搜索实用工具,它不需要连接到 Google、Bing 或其他服务来查询我的数据。NoteDataSource 是完整的,并处理所有搜索逻辑。我可以将搜索操作委托给该类,它会相应地处理我的笔记。您可以随意提取它,根据您的需求进行配置,并在您的应用中重新使用它。应用程序的完整源代码(不包括 dtSearch 库)可作为本文的一部分下载。

更多关于 dtSearch
dtSearch.com
口袋里的搜索引擎 – 介绍 Android 上的 dtSearch
云端疾速源代码搜索
使用 Azure 文件、RemoteApp 和 dtSearch,从任何计算机或设备跨越 PB 级数据的各种数据类型进行安全即时搜索
使用 dtSearch 引擎进行 Windows Azure SQL 数据库开发
使用 dtSearch 进行分面搜索 - 不是普通的搜索过滤器
使用 dtSearch® ASP.NET Core WebDemo 示例应用程序极速提升您的搜索体验
在您的 Windows 10 通用 (UWP) 应用程序中嵌入搜索引擎
使用 dtSearch Engine DataSource API 索引 SharePoint 网站集
使用 dtSearch® ASP.NET Core WebDemo 示例应用程序
在 AWS 上使用 dtSearch(EC2 & EBS)
使用 dtSearch 和 AWS Aurora 进行全文搜索

© . All rights reserved.