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





0/5 (0投票)
正在为 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 中一样。我的按钮外观如下:
我的笔记对象
为了使这个笔记对象尽可能简单,便于此次演示,我包含了 Id
、Title
、Content
、Created
和 Modified
字段。我的 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(
使用 dtSearch 和 AWS Aurora 进行全文搜索