【开发者来稿】HarmonyOS鸿蒙Next告别翻翻找找,分布式跨设备搜索让你一步到位!

【开发者来稿】HarmonyOS鸿蒙Next告别翻翻找找,分布式跨设备搜索让你一步到位! 你是否也有以下困扰?

远景自拍,每次都是一圈好友“争抢”一部设备查看拍摄效果?周末宅家,突发兴致想翻回某一段视频,却只能每个设备都翻翻找找一遍?工作需要,录音内容需要手机存一份,录音设备备一份,存储空间都被重复内容挤没了?

以上难题让用户头疼不已,所以我们设计了一款支持跨设备搜索的应用——具备分布式跨设备搜索能力,在一台设备上,通过键入关键词就能搜索到多台设备的媒体文件,并且以列表形式呈现在界面上。

经历漫长的求索之路后,开发者依靠HarmonyOS分布式能力,获取分布式设备列表,并通过分布式文件服务获取文件的分布式目录、名称及存储路径,从而实现分布式跨设备搜索。

我们一起来看一下效果吧。

你是否也迫不及待想知道这个Demo是如何实现?接下来让我们一起一探究竟。在看代码之前,需要大家先下载安装Huawei DevEco Studio。

Huawei DevEco Studio安装指南: https://developer.harmonyos.com/cn/docs/documentation/doc-guides/software_install-0000001053582415

一、代码结构

分布式搜索Demo代码结构如下图所示:

图1 代码结构图

  • DistributedFile:用来实现文件(分布式文件和本地文件)管理;
  • MainAbilitySlice:实现应用主界面。通过initUI()为用户呈现基本一个界面。再设置一系列事件监听器setEventListener(),对用户的点击行为进行更新;
  • FileItemViewHolder:用于存储文件名称,设备名称,对应的图标,文件路径。并通过FileListProvider类对其数据项进行访问;
  • WidgetHelper:用于使搜索结果中单个文件的详细信息(文件的路径和名称)以弹窗的形式展示。此外,错误信息也将通过这个弹窗部件来显示;
  • DeviceData:用于检查分布式组网设备和获取设备信息;
  • DeviceDataProvider:对DeviceData数据项进行访问;
  • ComponentViewHolder:使分布式设备列表信息以单行的形式呈现;
  • LogUtil:打印日志,帮助开发者快速定位出Demo中可能遇到的问题。

二、重点步骤解读

下面我们将对一些重点的步骤进行详解。由于分布式文件系统的写入和获取比较耗时,文件过多会一直占用线程,可能阻塞主线程,导致UI界面卡住,所以在Demo实现中,我们需要另起一个线程完成文件写入和读取。因此,无论从思路上还是使用上,这个Demo其实主要由两个部分组成:后台线程(管理本地文件以及分布式系统文件)和UI线程(处理相关事件及界面显示更新),具体流程图如下所示:

图2 启动应用流程图

1. 后台线程

在mainAbilitySlice中,通过mDistributedFile.start()开启后台线程任务。

public void onStart(Intent intent) {
    super.onStart(intent);
    super.setUIContent(ResourceTable.Layout_ability_everything);
    mDistributedFile = new DistributedFile(this);
    //start():此线程开始执行,Java虚拟机调用此线程的run方法。
    mDistributedFile.start();
    //初始化UI
    initUi();
    //设置事件侦听器
    setEventListener();
}

后台线程也就是实现本地文件以及分布式文件系统文件管理的DistributedFile。它通过一个独立的线程完成,主要分为以下步骤。

步骤一:分布式设备本地文件搜索

我们撰写一个initDistributedFiles()方法,通过AVStorage以及DataAbilityHelper来获取本地数据库的文件信息。由于当前HarmonyOS文件系统的数据库只能读取图片,音频以及视频等文件,所以我们也只读取这三类文件,代码如下所示:

private void initDistributedFiles() {
    // 搜索所有图片
    searchFiles(AVStorage.Images.Media.EXTERNAL_DATA_ABILITY_URI);
    // 搜索所有音频文件
    searchFiles(AVStorage.Audio.Media.EXTERNAL_DATA_ABILITY_URI);
    // 搜索所有视频文件
    searchFiles(AVStorage.Video.Media.EXTERNAL_DATA_ABILITY_URI);
}

private void searchFiles(Uri uri) {
    // 创建一个数据库观测者
    DataAbilityHelper helper = DataAbilityHelper.creator(mContext);
    // 获取ID、媒体项显示名称和数据
    String[] projections = new String[]{AVStorage.AVBaseColumns.ID, AVStorage.AVBaseColumns.DISPLAY_NAME, AVStorage.AVBaseColumns.DATA};
    try {
        // 根据提供的URI查询数据库中的数据。
        ResultSet resultSet = helper.query(uri, projections, null);
        if (resultSet == null) {
            LogUtil.info(TAG, "resultSet == null");
            return;
        }
        // 获取数据库数据
        while (resultSet.goToNextRow()) {
            int mediaId = resultSet.getInt(resultSet.getColumnIndexForName(AVStorage.AVBaseColumns.ID));
            String fullFileName = resultSet.getString(resultSet.getColumnIndexForName(AVStorage.AVBaseColumns.DATA));
            String fileName = fullFileName.substring(fullFileName.lastIndexOf(File.separator) + 1);
            Uri tmpUri = Uri.appendEncodedPathToUri(uri, "" + mediaId);
            writeToDistributedDir(mContext, helper, fileName, tmpUri);
            localList.add(fileName);
        }
    } catch (DataAbilityRemoteException e) {
        LogUtil.error(TAG, "query Files failed.");
    }
}
步骤二:将本地数据库文件信息写入到分布式文件系统中

我们将构建一个writeToDistributedDir()方法,传入元素,查询到的本地文件信息,文件名称、uri作为参数,通过HarmonyOS系统提供的Context. getDistributedDir()接口来获取分布式文件目录。这是我们使用分布式文件系统的重点,调用这个接口,我们可以实现像读写普通本地文件一样去读写分布式文件系统中的文件。代码如下所示:

// 将本地数据库文件写入到分布式文件系统中
private void writeToDistributedDir(Context context, DataAbilityHelper helper, String fileName, Uri uri) {
    // 判断是否可以正常获取分布式文件系统路径
    if (context.getDistributedDir() == null) {
        WidgetHelper.showOneSecondTips(context, "注意:分布式文件异常!");
        return;
    }
    // 获取当前设备的设备名
    String deviceName = KvManagerFactory.getInstance()
            .createKvManager(new KvManagerConfig(context))
            .getLocalDeviceInfo().getName();
    // 当前加入分布式系统的文件加上设备名前缀,用来区分不同设备同一文件名
    String uniqueFileName = deviceName + "+" + fileName;
    String distributedFilePath = context.getDistributedDir().getPath() + '/' + uniqueFileName;
    writeFile(distributedFilePath, helper, uri);
}

随后调用writeFile()方法进行I/O文件读写,由于这部分不是我们的重点,感兴趣的开发者可以直接获取源码看看。

步骤三:获取分布式文件列表

这里我们构建getDistributedFiles()方法,通过mContext.getDistributedDir().getPath()获取分布式目录下的路径并存储到文件数组中,遍历每一个文件数组中的文件,加入到分布式文件动态数组distributedList中。代码如下所示:

// 获取分布式文件列表
private void getDistributedFiles() {
    if (mContext.getDistributedDir() == null) {
        WidgetHelper.showOneSecondTips(mContext, "注意:分布式文件异常!");
        return;
    }
    // 获取分布式目录
    File file = new File(mContext.getDistributedDir().getPath());
    File[] files = file.listFiles();
    if (files == null) {
        LogUtil.error(TAG, "no distributed files!");
        return;
    }
    // 遍历每个文件,加入分布式列表
    for (File eachFile : files) {
        distributedList.add(eachFile.getPath());
    }
}

至此,我们通过后台线程,获取到分布式文件列表和本地文件列表。下面我们来介绍UI线程。

2. UI线程

UI界面是用户看得到并且能参与互动的部分,分为两个关键点,一个是初始化页面的实现,另一个是为界面元素设置监听器。

关键点一:初始化页面的实现

分布式搜索Demo的UI界面如下图,包含了搜索文本框、搜索按钮、文件点击按钮,设备列表等界面元素。

图3

在MainAbilitySlice中,通过initUI()方法,实现界面初始化,代码如下所示:

private void initUI() {
    // 列表容器
    listview = (ListContainer)this.findComponentById(ResourceTable.Id_listview);
    // 创建一个FileListProvider对象,传入FileItemViewHolde中的信息
    fileProvider = new FileListProvider(this, mFileList);
    // 传入fileProvider对象
    listview.setItemProvider(fileProvider);
    listview.setVisibility(Component.HIDE);//空列表不显示,仅显示图片
    // 无搜索结果显示内容
    result = (Image) findComponentById(ResourceTable.Id_result_info);
    result.setVisibility(Component.VISIBLE);
    // 单选按钮
    mRadioContainer=(RadioContainer)findComponentById(ResourceTable.Id_radio_container);
    // 一系列单选按钮
    mButtonImage = (RadioButton) this.findComponentById(ResourceTable.Id_sel_image);
    mButtonAudio = (RadioButton) this.findComponentById(ResourceTable.Id_sel_audio);
    mButtonVideo = (RadioButton) this.findComponentById(ResourceTable.Id_sel_video);
    mButtonText = (RadioButton) this.findComponentById(ResourceTable.Id_sel_text);
    mButtonFileAll = (RadioButton) this.findComponentById(ResourceTable.Id_sel_all);
    mRadioContainer.mark(0);
    // 不可重复点击
    mButtonImage.setClickable(false);
    gTextField = (TextField)this.findComponentById(ResourceTable.Id_textEntry);
    gTextField.setTextColor(Color.BLACK);
    searchImage = (Image)this.findComponentById(ResourceTable.Id_searchButton);
    mButton = (Image)findComponentById(ResourceTable.Id_AnimaBttn);
    mDevice = (Button)findComponentById(ResourceTable.Id_deviceBttn);
    mButton.setPosition(350, 700);
    mDevice.setPosition(320, 700);
    mButton.setVisibility(Component.VISIBLE);
    mDevice.setVisibility(Component.HIDE);
}
关键点二:设置监听器

针对需要与用户交互的组件进行点击监听,接收指令刷新页面或者调取数据更新到页面上。

在这个Demo中,我们主要监听搜索按钮的点击、文件分类系列单选按钮的点击(复选无效)、搜索结果列表上单个文件的点击,以及分布式设备列表的点击。

搜索按钮的点击

获取用户输入的关键词,判断是否为空,当为空时给予适当的提醒。不为空时获取搜索结果列表并将元素逐行呈现在搜索结果列表容器上。而搜索结果列表的显示,是setListFile()通过调用BaseItemProvider类中的notifyDataChanged方法来更新,如果获取列表失败,则显示无搜索结果,隐藏文件列表。实现代码如下所示:

searchImage.setClickedListener(component -> {
    mSearchKey = mTextField.getText();
    if (!availableKey(mSearchKey)) {
        WidgetHelper.showTips(this, getString(ResourceTable.String_input_notice_msg), MSG_SHOW_TIME);
        return;
    }
    setListFile();
});

private void setListFile() {
    if (getFileViewList()) {
        result.setVisibility(Component.HIDE);
        listview.setVisibility(Component.VISIBLE);
        // 刷新更新数据项的组件
        fileProvider.notifyDataChanged();
    } else {
        result.setVisibility(Component.VISIBLE);
        listview.setVisibility(Component.HIDE);
    }
}
文件分类系列单选按钮的点击

对于文件分类系列单选按钮,我们采取的是同一按钮不能复选的模式,实现代码如下所示:

private void setRadioContainerEvent() {
    // setMarkChangedListener:设置单选容器中单选按钮更改事件的侦听器。
    mRadioContainer.setMarkChangedListener((radioContainer, idx) -> {
        RadioButton[] mRadioButton = {
            mButtonImage,
            mButtonAudio,
            mButtonVideo,
            mButtonText,
            mButtonFileAll
        };
        if (mFileType == idx) {
            return;
        }
        if (mRadioButton[mFileType] != null) {
            mRadioButton[mFileType].setTextColor(Color.GRAY);
            mRadioButton[mFileType].setClickable(true);
        }
        mFileType = idx;
        mRadioButton[mFileType].setClickable(false);
        mSearchKey = mTextField.getText();
        if (availableKey(mSearchKey)) {
            setListFile();
        }
    });
}

同样的,我们会通过获取搜索结果列表和筛选分类将元素逐行呈现在搜索结果列表容器上。

搜索结果列表上单个文件的点击

FileItemViewHolder类用于封装的单个文件的信息。当列表上的单个文件被点击,且文件路径和文件名均不为空时,通过调用WidgetHelper.showTips()方法,显示弹框,并显示“文件路径:文件路径+文件名”的细节内容。效果如图所示:

图4

实现代码如下所示:

// 单个搜索结果文件信息展示,显示弹框(文件名+路径)
private void setListViewEvent() {
    listview.setItemClickedListener((container, component, position, id) -> {
        // 点击监听
        FileItemViewHolder item = (FileItemViewHolder) listview.getItemProvider().getItem(position);
        if (item != null && item.getFilepath() != null) {
            WidgetHelper.showTips(this,
                    "文件路径: " + item.getFilepath() + "/" + item.getFilename(),
                    2000);
        }
    });
}

public static void showTips(Context context, String msg, int durationTime) {
    Component rootView = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_widget_helper,
            null, false);
    Text text = (Text)rootView.findComponentById(ResourceTable.Id_helperText);
    text.setText(msg);
    ToastDialog toastDialog = new ToastDialog(context);
    toastDialog.setSize(MATCH_PARENT, MATCH_CONTENT);
    toastDialog.setDuration(durationTime);
    toastDialog.setAutoClosable(true);
    toastDialog.setTransparent(true);
    toastDialog.setAlignment(LayoutAlignment.CENTER);
    toastDialog.setComponent(rootView);
    toastDialog.show();
}
分布式设备列表点击

在分布式设备列表中,点击“设备”按钮后,将调用系统类BaseDialog中的show()方法,将获取到的分布式设备信息呈现在弹框中。

图5

实现代码如下所示:

// 分布式设备列表“展开”图标监听
mButton.setClickedListener(component -> {
    mButton.setVisibility(Component.HIDE);
    if (mDevice != null) {
        mDevice.setVisibility(Component.VISIBLE);
        mDevice.setFocusable(Component.FOCUS_ENABLE);// 设置可点击
    }
});

// 分布式设备列表“设备”按钮监听
mDevice.setClickedListener(component -> {
    mDevice.setVisibility(Component.HIDE);
    if (mButton != null) {
        mButton.setVisibility(Component.VISIBLE);
        mButton.setFocusable(Component.FOCUS_ENABLE);
    }
    DeviceSelectDialog mDialog = new DeviceSelectDialog(this);// 显示对话框的内容,参与共享的设备
    mDialog.show();
});

请注意:如果设备不在分布式组网内,那么将只呈现本机媒体文件的搜索结果。新增设备加入分布式组网,需要重启Demo实现。

以上就是分布式搜索Demo的设计思路、代码结构和重点步骤实现逻辑的解析,希望本期的内容能够给你的分布式应用探索之旅多一些启发。

同时也欢迎更多开发者与我们共享开发成果、技术解读与经验心得,说不定下一期的主角就是你哦!

点击下方链接,即可获取完整代码。

完整代码: https://gitee.com/openharmony/app_samples/tree/master/CompleteApps/DistributedSearch

6 回复

这上面的视频和图片都不能查看了,麻烦更新下。

更多关于【开发者来稿】HarmonyOS鸿蒙Next告别翻翻找找,分布式跨设备搜索让你一步到位!的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


感谢您的反馈,视频和图片已更换~

完整代码连接丢失

好文,应当公之于众,招人共赏

HarmonyOS鸿蒙Next的分布式跨设备搜索功能,通过分布式技术实现了跨设备的无缝搜索体验。该功能允许用户在一个设备上搜索到其他设备上的内容,无需手动切换设备或进行复杂的操作。具体来说,分布式跨设备搜索利用了鸿蒙系统的分布式数据管理能力,将不同设备上的数据统一索引,用户只需在任一设备上输入搜索关键词,系统会自动检索并展示所有相关设备上的匹配结果。

该功能的核心技术包括分布式数据同步、分布式任务调度和分布式搜索算法。分布式数据同步确保了各设备上的数据能够实时更新和共享;分布式任务调度则负责在不同设备间分配搜索任务,确保搜索效率;分布式搜索算法则优化了跨设备搜索的准确性和速度。

此外,鸿蒙Next还引入了智能推荐机制,根据用户的使用习惯和搜索历史,自动推荐最可能的结果,进一步提升搜索体验。这一功能不仅适用于文件、应用等常规搜索,还支持跨设备的媒体内容、联系人、消息等多种数据类型。

总的来说,HarmonyOS鸿蒙Next的分布式跨设备搜索功能,通过分布式技术和智能算法的结合,实现了高效、便捷的跨设备搜索体验,减少了用户在不同设备间切换的繁琐操作。

HarmonyOS鸿蒙Next的分布式跨设备搜索功能通过整合多设备资源,实现了高效、精准的信息检索。用户无需在不同设备间切换,只需一次搜索即可获取所有设备中的相关结果。该功能依托于鸿蒙系统的分布式架构,能够智能识别设备类型和内容,提供无缝的跨设备体验,极大提升了搜索效率和用户体验。

回到顶部
AI 助手
你好,我是IT营的 AI 助手
您可以尝试点击下方的快捷入口开启体验!