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

融合搜索概述

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

基本概念

  • 全文索引

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

  • 全文搜索

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

  • 全局搜索

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

  • 全局搜索应用

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

  • 索引源应用

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

  • 可搜索配置

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

  • 群组

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

  • 索引库

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

  • 索引域

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

  • 索引属性

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

运作机制

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

图1 融合搜索运作示意图

HarmonyOS学习路之开发篇—数据管理(融合搜索)_第1张图片

 

约束与限制

  • 构建索引或者发起搜索前,索引源应用必须先设置索引属性,并且必须有且仅有一个索引域设置为主键,且主键索引域不能分词,索引和搜索都会使用到索引属性。
  • 索引源应用的数据发生变动时,开发者应同步通过融合搜索索引接口更新索引,以保证索引和应用原始数据的一致性。
  • 批量创建、更新、删除索引时,应控制单次待索引内容大小,建议分批创建索引,防止内存溢出。
  • 分页搜索和分组搜索应控制每页返回结果数量,防止内存溢出。
  • 构建和搜索本机索引时,应该使用提供的SearchParameter.DEFAULT_GROUP作为群组ID,分布式索引使用通过账号模块获取的群组ID。
  • 搜索时需先创建搜索会话,并务必在搜索结束时关闭搜索会话,释放内存资源。
  • 使用融合搜索服务接口需要在“config.json”配置文件中添加“ohos.permission.ACCESS_SEARCH_SERVICE”权限。
  • 搜索时的SearchParamter.DEVICE_ID_LIST必须与创建索引时的deviceId一致。

融合搜索开发

场景介绍

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

接口说明

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

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

类名

接口名

描述

SearchAbility

public List insert(String groupId, String bundleName, List indexDataList)

索引插入

public List update(String groupId, String bundleName, List indexDataList)

索引更新

public List delete(String groupId, String bundleName, List indexDataList)

索引删除

SearchSession

public int getSearchHitCount(String queryJsonStr)

搜索命中结果数量

public List search(String queryJsonStr, int start, int limit)

分页搜索

public List groupSearch(String queryJsonStr, int groupLimit)

分组搜索

开发步骤

在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 indexFormList = new ArrayList() { {
    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 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 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 searchResult = searchSession.search(queryJsonStr, i, batch);
        // 处理IndexData
    }
    int groupLimit = 10; // 每个分组域上最多返回10个分组结果
    List recommendationResult = searchSession.groupSearch(queryJsonStr, groupLimit);
    // 处理Recommendation
} finally {
    // 结束搜索,释放资源
    searchAbility.endSearch(SearchParameter.DEFAULT_GROUP, bundleName, searchSession);
}

你可能感兴趣的:(HarmonyOS,学习(java开发篇),学习,搜索融合,会话管理,harmonyos,Network)