HarmonyOS之数据管理·融合搜索的应用

一、简介
① 基本概念
  • HarmonyOS 融合搜索为开发者提供搜索引擎级的全文搜索能力,可支持应用内搜索和系统全局搜索,为用户提供更加准确、高效的搜索体验。
  • 全文索引:记录字或词的位置和次数等属性,建立的倒排索引。
  • 全文搜索:通过全文索引进行匹配查找结果的一种搜索引擎技术。
  • 全局搜索:可以在系统全局统一的入口进行的搜索行为。
  • 全局搜索应用:HarmonyOS 上提供全局搜索入口的应用,一般为桌面下拉框或悬浮搜索框。
  • 索引源应用:通过融合搜索索引接口对其数据建立索引的应用。
  • 可搜索配置:每个索引源应用应该提供一个包括应用包名、是否支持全局搜索等信息的可搜索实体,以便全局搜索应用发起搜索。
  • 群组:经过认证的可信设备圈,可从账号模块获取群组 ID。
  • 索引库:一种搜索引擎的倒排索引库,包含多个索引文件的整个目录构成一个索引库。
  • 索引域:索引数据的字段名,比如一张图片有文件名、存储路径、大小、拍摄时间等,文件名就是其中的一个索引域。
  • 索引属性:描述索引域的信息,包括索引类型、是否为主键、是否存储、是否支持分词等。
② 运作机制
  • 索引源应用通过融合搜索接口设置可搜索实体,并为其数据内容构建全文索引。
  • 全局搜索应用接收用户发起的搜索请求,遍历支持全局搜索的可搜索实体,解析用户输入并构造查询条件,最后通过融合搜索接口获取各应用搜索结果。
  • 融合搜索运作如下图所示:

HarmonyOS之数据管理·融合搜索的应用_第1张图片

③ 权限与限制
  • 构建索引或者发起搜索前,索引源应用必须先设置索引属性,并且必须有且仅有一个索引域设置为主键,且主键索引域不能分词,索引和搜索都会使用到索引属性。
  • 索引源应用的数据发生变动时,开发者应同步通过融合搜索索引接口更新索引,以保证索引和应用原始数据的一致性。
  • 批量创建、更新、删除索引时,应控制单次待索引内容大小,建议分批创建索引,防止内存溢出。
  • 分页搜索和分组搜索应控制每页返回结果数量,防止内存溢出。
  • 构建和搜索本机索引时,应该使用提供的 SearchParameter.DEFAULT_GROUP 作为群组 ID,分布式索引使用通过账号模块获取的群组 ID。
  • 搜索时需先创建搜索会话,并务必在搜索结束时关闭搜索会话,释放内存资源。
    使用融合搜索服务接口需要在“config.json”配置文件中添加“ohos.permission.ACCESS_SEARCH_SERVICE”权限。
  • 搜索时的 SearchParameter.DEVICE_ID_LIST 必须与创建索引时的 deviceId 一致。
④ 应用场景
  • 索引源应用,一般为有持久化数据的应用,可以通过融合搜索接口为其应用数据建立索引,并配置全局搜索可搜索实体,帮助用户通过全局搜索应用查找本应用内的数据。
  • 应用本身提供搜索框时,也可直接在应用内部通过融合搜索接口实现全文搜索功能。
二、融合搜索 API
  • 融合搜索接口功能如下表所示:
类名 接口名 描述
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) 分组搜索
三、融合搜索流程
  • 实例化 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());
	}
	// 连接失败可重试
  • 设置索引属性:
	// 构造索引属性
	List<IndexForm> indexFormList = new ArrayList<IndexForm>() { {
	    add(new IndexForm("id", IndexType.NO_ANALYZED, true, true, false)); // 主键,不分词
	    add(new IndexForm("title", IndexType.ANALYZED, false, true, true)); // 分词
	    add(new IndexForm("tag", IndexType.SORTED, false, true, false)); // 分词,同时支持排序、分组
	    add(new IndexForm("ocr_text", IndexType.SORTED_NO_ANALYZED, false, true, false)); // 支持排序、分组,不分词,所以也支持范围搜索。
	    add(new IndexForm("datetaken", IndexType.LONG, 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)); // 支持范围搜索
	} };
	
	// 设置索引属性
	int result = searchAbility.setIndexForm(bundleName, 1, indexFormList);
	// 设置失败可重试
  • 插入索引:
	// 构建索引数据
	List<IndexData> indexDataList = new ArrayList<>();
	for (int i = 0; i < 5; i++) {
	    IndexData indexData = new IndexData();
	    indexData.put("id", "id" + i);
	    indexData.put("title", "title" + i);
	    indexData.put("tag", "tag" + i);
	    indexData.put("ocr_text", "ocr_text" + i);
	    indexData.put("datetaken", System.currentTimeMillis());
	    indexData.put("bucket_id", i);
	    indexData.put("latitude", i / 5.0 * 180);
	    indexData.put("longitude", i / 5.0 * 360);
	    indexDataList.add(indexData);
	}
	
	// 插入索引
	List<IndexData> failedList = searchAbility.insert(SearchParameter.DEFAULT_GROUP, bundleName, indexDataList);
	// 失败的记录可以持久化,稍后重试
  • 构建查询:
	// 构建查询
	JSONObject jsonObject = new JSONObject();
	
	// SearchParameter.QUERY对应用户输入,建议搜索域分词。
	// 这里假设用户输入是“天空”,要在"title", "tag"这两个域上发起搜索。
	JSONObject query = new JSONObject();
	query.put("天空", new JSONArray(Arrays.asList("title", "tag")));
	jsonObject.put(SearchParameter.QUERY, query);
	
	// SearchParameter.FILTER_CONDITION对应的JSONArray里可以添加搜索条件。
	// 对于索引库里的一条索引,JSONArray下的每个JSONObject指定的条件都必须满足才会命中,JSONObject里的条件组合满足其中一个,这个JSONObject指定的条件即可满足。
	JSONArray filterCondition = new JSONArray();
	// 第一个条件,一个域上可能取多个值。
	JSONObject filter1 = new JSONObject();
	filter1.put("bucket_id", new JSONArray(Arrays.asList(0, 1, 2))); // 一条索引在"bucket_id"的取值为0或1或2就能命中。
	filter1.put("id", new JSONArray(Arrays.asList(0, 1))); // 或者在"id"的取值为0或者1也可以命中。
	filterCondition.put(filter1);
	
	JSONObject filter2 = new JSONObject();
	filter2.put("tag", new JSONArray(Arrays.asList("白云")));
	filter2.put("ocr_text", new JSONArray(Arrays.asList("白云"))); // 一条索引只要在"tag"或者"ocr_text"上命中"白云"就能命中。
	filterCondition.put(filter2);
	jsonObject.put(SearchParameter.FILTER_CONDITION, filterCondition); // 一条索引要同时满足第一和第二个条件才能命中。
	
	// SearchParameter.DEVICE_ID_LIST对应设备ID,匹配指定设备ID的索引才会命中。
	JSONObject deviceId = new JSONObject();
	deviceId.put("device_id", new JSONArray(Arrays.asList("localDeviceId"))); // 指定本机设备。
	jsonObject.put(SearchParameter.DEVICE_ID_LIST, deviceId);
	
	// 可以在支持范围搜索的索引域上发起范围搜索,一条索引在指定域的值落在对应的指定范围才会命中。
	JSONObject latitudeObject = new JSONObject();
	latitudeObject.put(SearchParameter.LOWER, -40.0f);
	latitudeObject.put(SearchParameter.UPPER, 40.0f);
	jsonObject.put("latitude", latitudeObject); // 纬度必须在[-40.0f, 40.0f]
	
	JSONObject longitudeObject = new JSONObject();
	longitudeObject.put(SearchParameter.LOWER, -90.0);
	longitudeObject.put(SearchParameter.UPPER, 90.0);
	jsonObject.put("longitude", longitudeObject); // 经度必须在[-90.0, 90.0]
	
	// SearchParameter.ORDER_BY对应搜索结果的排序,排序字段通过SearchParameter.ASC和SearchParameter.DESC指定搜索结果在这个字段上按照升序、降序排序。
	// 这里填充字段的顺序是重要的,比如这里两个索引之间会先在"id"字段上升序排序,只有在"id"上相同时,才会继续在"datetaken"上降序排序,以此类推。
	JSONObject order = new JSONObject();
	order.put("id", SearchParameter.ASC);
	order.put("datetaken", SearchParameter.DESC);
	jsonObject.put(SearchParameter.ORDER_BY, order);
	
	// SearchParameter.GROUP_FIELD_LIST对应分组搜索的域,调用groupSearch接口需要指定。
	jsonObject.put(SearchParameter.GROUP_FIELD_LIST, new JSONArray(Arrays.asList("tag", "ocr_text")));
	
	// 得到查询字符串。
	String queryJsonStr = jsonObject.toString();
  • 上述代码构建的 JSON 字符串如下:
	// 构建的json字符串如下:
	/**
	{
	    "SearchParameter.QUERY": {
	        "天空": [
	            "title",
	            "tag"
	        ]
	    },
	    "SearchParameter.FILTER_CONDITION": [
	        {
	            "bucket_id": [
	                0,
	                1,
	                2
	            ],
	            "id": [
	                0,
	                1
	            ]
	        },
	        {
	            "tag": [
	                "白云"
	            ],
	            "ocr_text": [
	                "白云"
	            ]
	        }
	    ],
	    "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": {
	        "id": "ASC",
	        "datetaken": "DESC"
	    },
	    "SearchParameter.GROUP_FIELD_LIST": [
	        "tag",
	        "ocr_text"
	    ]
	}
	**/
  • 开始搜索会话,发起搜索:
	// 开始搜索会话
	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);
	}

你可能感兴趣的:(HarmonyOS,融合搜索的概念和运作机制分析,融合搜索的权限分配和使用限制,融合搜索的API说明,融合搜索的使用开流程)