HarmonyOS学习路之开发篇—数据管理(融合搜索)

简介: HarmonyOS融合搜索为开发者提供搜索引擎级的全文搜索能力,可支持应用内搜索和系统全局搜索,为用户提供更加准确、高效的搜索体验。

融合搜索概述

HarmonyOS融合搜索为开发者提供搜索引擎级的全文搜索能力,可支持应用内搜索和系统全局搜索,为用户提供更加准确、高效的搜索体验。


基本概念

全文索引

记录字或词的位置和次数等属性,建立的倒排索引。


全文搜索

通过全文索引进行匹配查找结果的一种搜索引擎技术。


全局搜索

可以在系统全局统一的入口进行的搜索行为。


全局搜索应用

HarmonyOS上提供全局搜索入口的应用,一般为桌面下拉框或悬浮搜索框。


索引源应用

通过融合搜索索引接口对其数据建立索引的应用。


可搜索配置

每个索引源应用应该提供一个包括应用包名、是否支持全局搜索等信息的可搜索实体,以便全局搜索应用发起搜索。


群组

经过认证的可信设备圈,可从账号模块获取群组ID。


索引库

一种搜索引擎的倒排索引库,包含多个索引文件的整个目录构成一个索引库。


索引域

索引数据的字段名,比如一张图片有文件名、存储路径、大小、拍摄时间等,文件名就是其中的一个索引域。


索引属性

描述索引域的信息,包括索引类型、是否为主键、是否存储、是否支持分词等。


运作机制

索引源应用通过融合搜索接口设置可搜索实体,并为其数据内容构建全文索引。全局搜索应用接收用户发起的搜索请求,遍历支持全局搜索的可搜索实体,解析用户输入并构造查询条件,最后通过融合搜索接口获取各应用搜索结果。


图1 融合搜索运作示意图




约束与限制

构建索引或者发起搜索前,索引源应用必须先设置索引属性,并且必须有且仅有一个索引域设置为主键,且主键索引域不能分词,索引和搜索都会使用到索引属性。

索引源应用的数据发生变动时,开发者应同步通过融合搜索索引接口更新索引,以保证索引和应用原始数据的一致性。

批量创建、更新、删除索引时,应控制单次待索引内容大小,建议分批创建索引,防止内存溢出。

分页搜索和分组搜索应控制每页返回结果数量,防止内存溢出。

构建和搜索本机索引时,应该使用提供的SearchParameter.DEFAULT_GROUP作为群组ID,分布式索引使用通过账号模块获取的群组ID。

搜索时需先创建搜索会话,并务必在搜索结束时关闭搜索会话,释放内存资源。

使用融合搜索服务接口需要在“config.json”配置文件中添加“ohos.permission.ACCESS_SEARCH_SERVICE”权限。

搜索时的SearchParamter.DEVICE_ID_LIST必须与创建索引时的deviceId一致。

融合搜索开发

场景介绍

索引源应用,一般为有持久化数据的应用,可以通过融合搜索接口为其应用数据建立索引,并配置全局搜索可搜索实体,帮助用户通过全局搜索应用查找本应用内的数据。应用本身提供搜索框时,也可直接在应用内部通过融合搜索接口实现全文搜索功能。


接口说明

HarmonyOS中的融合搜索为开发者提供以下几种能力,详见API参考。


表1 融合搜索接口功能介绍

image.png

开发步骤

在config.json中添加permisssion权限。


// 添加在abilities同一目录层级
"reqPermissions": [
    {
        "name": "ohos.permission.ACCESS_SEARCH_SERVICE"
    }
]
实例化SearchAbility, 连接融合搜索服务。
SearchAbility searchAbility = new SearchAbility(context);
String bundleName = context.getBundleName();
CountDownLatch lock = new CountDownLatch(1);
// 连接服务
searchAbility.connect(new ServiceConnectCallback() {
    @Override
    public void onConnect() {
        lock.countDown();
    }
    @Override
    public void onDisconnect() {
    }
});
// 等待回调,最长等待时间可自定义。
try {
    lock.await(3000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
    HiLog.error(LABEL, "await failed, %{public}s", e.getMessage());
}
if (searchAbility.hasConnected()) {
    // 连接成功
} else {
    // 连接失败,可重试。
}


设置索引属性。

// 构造自定义索引属性
List<IndexForm> indexFormList = new ArrayList<IndexForm>() { {
    add(new IndexForm("tag", IndexType.SORTED, false, true, false)); // 分词,同时支持排序、分组
    add(new IndexForm("bucket_id", IndexType.INTEGER, false, true, false)); // 支持排序和范围查询
    add(new IndexForm("latitude", IndexType.FLOAT, false, true, false)); // 支持范围搜索
    add(new IndexForm("longitude", IndexType.DOUBLE, false, true, false)); // 支持范围搜索
    add(new IndexForm("device_id", IndexType.NO_ANALYZED, false, true, false)); // 支持搜索
} };
// 使用通用模板设置索引属性
int result = searchAbility.setIndexForm(bundleName, 1, indexFormList, IndexSchemaType.COMMON);
if (result == 1) {
    // 设置索引属性成功
} else {
    // 设置索引属性失败,可重试
}


插入索引。

// 构建索引数据
List<IndexData> indexDataList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
    CommonItem commonItem = new CommonItem()
 .setIdentifier(LOCAL_DEVICE_ID + i) // identifier为主键        
        .setTitle("白云")
        .setSubtitle("subtitle")        
        .setCategory("things")        
        .setDescription("is description")        
        .setName("name")
        .setAlternateName("othername")
        .setDateCreate(System.currentTimeMillis())
        .setKeywords("key")
        .setPotentialAction("com.sample.search.TestAbility")
        .setThumbnailUrl(FILE_PATH)        
        .setUrl(FILE_PATH)
        .setReserved1(REVERSE_VALUE)
        .setReserved2("reserved");
    commonItem.put("tag", "天空" + i);
    commonItem.put("bucket_id", i);
    commonItem.put("latitude", i / 5.0 * 180);
    commonItem.put("longitude", i / 5.0 * 360);
    commonItem.put("device_id", "localDeviceId");
    indexDataList.add(commonItem);
}
// 插入索引
List<IndexData> failedList = searchAbility.insert(SearchParameter.DEFAULT_GROUP, bundleName, indexDataList);
// 失败的记录可以持久化,稍后重试。


构建查询。


// 构建查询
ZSONObject zsonObject = new ZSONObject();
// SearchParameter.QUERY对应用户输入,建议搜索域分词。
// 这里假设用户输入是“天空”,要在"title", "tag"这两个域上发起搜索。
ZSONObject query = new ZSONObject();
query.put("天空", new ZSONArray(Arrays.asList(CommonItem.TITLE, "tag")));
zsonObject.put(SearchParameter.QUERY, query);
// SearchParameter.FILTER_CONDITION对应的ZSONArray里可以添加搜索条件。
// 对于索引库里的一条索引,ZSONArray下的每个ZSONObject指定的条件都必须满足才会命中,ZSONObject里的条件组合满足其中一个,这个ZSONObject指定的条件即可满足。
ZSONArray filterCondition = new ZSONArray();
// 第一个条件,一个域上可能取多个值。
ZSONObject filter1 = new ZSONObject();
filter1.put("bucket_id", new ZSONArray(Arrays.asList(0, 1, 2))); // 一条索引在"bucket_id"的取值为0或1或2就能命中。
filter1.put(CommonItem.IDENTIFIER, new ZSONArray(Arrays.asList(0, 1))); // 或者在CommonItem.IDENTIFIER的取值为0或者1也可以命中。
filterCondition.add(filter1);
ZSONObject filter2 = new ZSONObject();
filter2.put("tag", new ZSONArray(Arrays.asList("白云")));
filter2.put(CommonItem.TITLE, new ZSONArray(Arrays.asList("白云"))); // 一条索引只要在"tag"或者CommonItem.TITLE上命中"白云"就能命中。
filterCondition.add(filter2);
zsonObject.put(SearchParameter.FILTER_CONDITION, filterCondition); // 一条索引要同时满足第一和第二个条件才能命中。
// SearchParameter.DEVICE_ID_LIST对应设备ID,匹配指定设备ID的索引才会命中。
ZSONObject deviceId = new ZSONObject();
deviceId.put("device_id", new ZSONArray(Arrays.asList("localDeviceId"))); // 指定本机设备。
zsonObject.put(SearchParameter.DEVICE_ID_LIST, deviceId);
// 可以在支持范围搜索的索引域上发起范围搜索,一条索引在指定域的值落在对应的指定范围才会命中。
ZSONObject latitudeObject = new ZSONObject();
latitudeObject.put(SearchParameter.LOWER, -40.0f);
latitudeObject.put(SearchParameter.UPPER, 40.0f);
zsonObject.put("latitude", latitudeObject); // 纬度必须在[-40.0f, 40.0f]
ZSONObject longitudeObject = new ZSONObject();
longitudeObject.put(SearchParameter.LOWER, -90.0);
longitudeObject.put(SearchParameter.UPPER, 90.0);
zsonObject.put("longitude", longitudeObject); // 经度必须在[-90.0, 90.0]
// SearchParameter.ORDER_BY对应搜索结果的排序,排序字段通过SearchParameter.ASC和SearchParameter.DESC指定搜索结果在这个字段上按照升序、降序排序。
// 这里填充字段的顺序是重要的,比如这里两个索引之间会先在CommonItem.CATEGORY字段上升序排序,只有在CommonItem.CATEGORY上相同时,才会继续在"tag"上降序排序,以此类推。
ZSONObject order = new ZSONObject();
order.put(CommonItem.CATEGORY, SearchParameter.ASC);
order.put("tag", SearchParameter.DESC);
zsonObject.put(SearchParameter.ORDER_BY, order);
// SearchParameter.GROUP_FIELD_LIST对应分组搜索的域,调用groupSearch接口需要指定。
zsonObject.put(SearchParameter.GROUP_FIELD_LIST, new ZSONArray(Arrays.asList("tag", CommonItem.CATEGORY)));
// 得到查询字符串。
String queryZsonStr = zsonObject.toString();
// 构建的json字符串如下:
/**
{
    "SearchParameter.QUERY": {
        "天空": [
            "title",
            "tag"
        ]
    },
    "SearchParameter.FILTER_CONDITION": [
        {
            "bucket_id": [
                0,
                1,
                2
            ],
            "identifier": [
                0,
                1
            ]
        },
        {
            "tag": [
                "白云"
            ],
            "title": [
                "白云"
            ]
        }
    ],
    "SearchParameter.DEVICE_ID_LIST": {
        "device_id": [
            "localDeviceId"
        ]
    },
    "latitude": {
        "SearchParameter.LOWER": -40.0,
        "SearchParameter.UPPER": 40.0
    },
    "longitude": {
        "SearchParameter.LOWER": -90.0,
        "SearchParameter.UPPER": 90.0
    },
    "SearchParameter.ORDER_BY": {
        "category": "ASC",
        "tag": "DESC"
    },
    "SearchParameter.GROUP_FIELD_LIST": [
        "tag",
        "category"
    ]
}
**/


开始搜索会话,发起搜索。


// 开始搜索会话
SearchSession searchSession = searchAbility.beginSearch(SearchParameter.DEFAULT_GROUP, bundleName);
if (searchSession == null) {
    return;
}
try {
    int hit = searchSession.getSearchHitCount(queryJsonStr); // 获取总命中数
    int batch = 50; // 每页最多返回50个结果
    for (int i = 0; i < hit; i += batch) {
        List<IndexData> searchResult = searchSession.search(queryJsonStr, i, batch);
        // 处理IndexData
    }
    int groupLimit = 10; // 每个分组域上最多返回10个分组结果
    List<Recommendation> recommendationResult = searchSession.groupSearch(queryJsonStr, groupLimit);
    // 处理Recommendation
} finally {
    // 结束搜索,释放资源
    searchAbility.endSearch(SearchParameter.DEFAULT_GROUP, bundleName, searchSession);
}


相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
相关文章
|
3月前
|
开发者
鸿蒙开发:资讯项目实战之项目初始化搭建
目前来说,我们的资讯项目只是往前迈了很小的一步,仅仅实现了项目创建,步虽小,但概念性的知识很多,这也是这个项目的初衷,让大家不仅仅可以掌握日常的技术开发,也能让大家理解实际的项目开发知识。
鸿蒙开发:资讯项目实战之项目初始化搭建
|
3月前
|
容器
HarmonyOS NEXT仓颉开发语言实战案例:外卖App
仓颉语言实战分享,教你如何用仓颉开发外卖App界面。内容包括页面布局、导航栏自定义、搜索框实现、列表模块构建等,附完整代码示例。轻松掌握Scroll、List等组件使用技巧,提升HarmonyOS应用开发能力。
|
3月前
|
缓存 JavaScript IDE
鸿蒙开发:基于最新API,如何实现组件化运行
手动只是让大家了解切换的原理,在实际开发中,可不推荐手动,下篇文章,我们将通过脚本或者插件,快速实现组件化模块之间的切换,实现独立运行,敬请期待!
111 0
鸿蒙开发:基于最新API,如何实现组件化运行
|
3月前
|
SQL 弹性计算 数据库
鸿蒙5开发宝藏案例分享---优化应用时延问题
鸿蒙性能优化指南来了!从UI渲染到数据库操作,6大实战案例助你提升应用流畅度。布局层级优化、数据加载并发、数据库查询提速、相机资源延迟释放、手势识别灵敏调整及转场动画精调,全面覆盖性能痛点。附赠性能自检清单,帮助开发者高效定位问题,让应用运行如飞!来自华为官方文档的精华内容,建议收藏并反复研读,共同探讨更多优化技巧。
|
3月前
|
存储 IDE 定位技术
【HarmonyOS 5】鸿蒙组件&模板服务详解 - 助力高效开发的利器
在移动应用开发领域,效率与质量始终是开发者追求的核心目标。鸿蒙系统作为新兴的操作系统,为开发者提供了丰富且强大的开发资源,其中鸿蒙组件&模板服务更是成为开发者快速构建高质量应用的得力助手。
120 0
|
3月前
|
容器
HarmonyOS NEXT仓颉开发语言实战案例:健身App
本期分享一个健身App首页的布局实现,顶部采用Stack容器实现重叠背景与偏移效果,列表部分使用List结合Scroll实现可滚动内容。代码结构清晰,适合学习HarmonyOS布局技巧。
|
3月前
|
容器
HarmonyOS NEXT仓颉开发语言实战案例:银行App
仓颉语言银行App项目分享,页面布局采用List容器,实现沉浸式体验与模块化设计。顶部资产模块结合Stack与Row布局,背景图与内容分离,代码清晰易懂;功能按钮部分通过负边距实现上移效果,圆角仅保留顶部;热门推荐使用header组件,结构更规范。整体代码风格与ArkTS相似,但细节更灵活,适合金融类应用开发。
|
3月前
|
缓存
鸿蒙5开发宝藏案例分享---Swiper组件性能优化实战
本文分享了鸿蒙系统中Swiper组件的性能优化技巧,包括:1) 使用`LazyForEach`替代`ForEach`实现懒加载,显著降低内存占用;2) 通过`cachedCount`精准控制缓存数量,平衡流畅度与内存消耗;3) 利用`onAnimationStart`在抛滑时提前加载资源,提升构建效率;4) 添加`@Reusable`装饰器复用组件实例,减少创建开销。实际应用后,图库页帧率从45fps提升至58fps,效果显著。适合处理复杂列表或轮播场景,欢迎交流经验!
|
2月前
|
存储 SQL 前端开发
跟老卫学HarmonyOS开发:ArkTS关系型数据库开发
本节以“账本”为例,使用关系型数据库接口实现账单的增、删、改、查操作。通过创建ArkTSRdb应用,演示如何操作RdbStore进行数据管理,并结合界面按钮实现交互功能。
90 0
跟老卫学HarmonyOS开发:ArkTS关系型数据库开发
|
2月前
|
人工智能 关系型数据库 API
快速部署Dify on DMS,一站式开发智能体应用
Dify 是一款开源的 Agent 应用开发平台,结合后端即服务与 Agentic 工作流,支持快速构建生产级 AI 应用。无论技术背景如何,用户都能轻松参与 AI 项目与数据管理,开发企业级应用。

热门文章

最新文章