Harmony OS应用数据管理不仅支持单设备的各种结构化数据的持久化,还支持跨设备之间数据的同步、共享及搜索功能,因此,开发者基于Harmony OS应用数据管理功能,能实现应用程序数据在不同终端设备之间的无缝衔接,从而保证用户在跨设备使用数据时所用数据的一致性。
在正式讲解HarmonyOS中的分布式文件服务之前,先简单介绍一下相关概念。
01、分布式文件系统(Distributed File System,DFS)
通过计算机网络将分布在不同地点的节点相连,利用网络进行节点间的通信和数据传输,从而将固定于某个地点的某个文件系统,扩展到任意多个地点/文件系统,众多的节点组成的文件系统网络即为分布式系统。简单来讲,DFS为分布在网络上任意位置的资源提供一个逻辑上的树形文件系统结构,从而使用户访问分布在网络上的共享文件更加简便。分布式文件系统的突出优点就是可以屏蔽文件系统的物理位置,人们在使用分布式文件系统时,无须关心数据是存储在哪个节点上或者是从哪个节点获取的,而只需像使用本地文件系统一样管理和存储文件系统中的数据。
此外,分布式文件系统还包括以下特点:
冗余性: 分布式文件系统可以提供冗余备份,当系统中某些节点出错时,整体文件服务不会停止,还能继续为用户提供服务,具有较高的容错性。
安全性: 分布式文件系统的安全性离不开其冗余性,当出现故障的节点存储的数据损坏时可以由其他节点进行数据恢复。此外,在分布式文件系统中,大量数据被分散到不同的节点上进行存储,数据丢失的风险大大减小。
扩展性: 分布式文件系统可以通过网络连接将大量的计算机连接到一起,任何计算机只需经过简单的配置就可以加入分布式文件系统中。
02、分布式文件
分布式文件是指依赖于分布式文件系统并分散存储在多个用户设备上的文件,应用间的分布式文件目录互相隔离,不同应用的文件不能互相访问。
03、文件元数据
文件元数据是用于描述文件特征的数据,包含文件名、文件大小、创建、访问、修改时间等信息。
在HarmonyOS中,分布式文件服务支持用户设备中的应用程序在同一账号下多设备之间进行文件共享。即应用程序可以屏蔽文件具体的存储位置,在多个设备之间无障碍访问文件。HarmonyOS中分布式文件服务的运作机制如图1所示。
■ 图1 Harmony OS中分布式文件服务的运作机制
从图1可以看出,在HarmonyOS中,分布式文件服务采用无中心节点的设计,每个设备都存储一份全量的文件元数据和本设备上产生的分布式文件,元数据在多台设备间互相同步,当应用需要访问分布式文件时,分布式文件服务首先查询本设备上的文件元数据,获取文件所在的存储设备,然后对存储设备上的分布式文件服务发起文件访问请求,将文件内容读取到本地。
实际上,在HarmonyOS中实现分布式文件服务前,需要满足以下条件:
(1) 应用程序如需使用分布式文件服务完整功能,需要申请分布式数据管理权限,具体地,申请ohos.permission.DISTRIBUTED_DATASYNC权限,从而允许不同设备间的数据交换。
(2) 要实现分布式共享文件,则多个设备需登录同一个华为账号,打开蓝牙设备,连接同一个WLAN 局域网。
(3) 存在多设备并写的场景,为了避免冲突,开发者需要对文件加锁保护,保证文件独享。非持锁情况下,并发写冲突时,后一次会覆盖前一次。
(4) 应用访问分布式文件时,文件所在设备不能离线,否则文件不能访问。
(5) 当网络情况较差时,访问存储在远端的分布式文件可能会长时间得不到响应甚至响应失败,因此需要应用考虑到对这种场景的处理。
(6) 当两台设备有同名文件时,如果元数据进行同步则会产生冲突,分布式文件服务会根据时间戳将文件按创建的先后顺序重命名,因此,为避免此类场景出现,应用在文件名上可以进行相应设备区分,例如,deviceID+时间戳。
下面通过一个实例学习如何在HarmonyOS中具体实现多设备间的文件共享。实例实现了在两个手机设备上进行分布式时间读写的功能,即在手机A 上单击写入Button,将当前时间写入分布式文档。在手机B上单击读取Button,可以从分布式文档中获取手机A 中写入的时间,反之亦然。需要注意的是,两个手机设备需要登录同一个华为账号,故需要开启多设备协同权限。
首先,创建Phone设备下的Java模板新项目,打开项目目录下的MainAbilitySclice.java文件,在onStart()方法中声明布局,代码如下:
DirectionalLayout directionLayout= new DirectionalLayout(this); directionLayout.setWidth(ComponentContainer.LayoutConfig.MATCH_PARENT); directionLayout.setHeight(ComponentContainer.LayoutConfig.MATCH_PARENT); directionLayout.setOrientation(Component.VERTICAL); directionLayout.setPadding(32, 32, 32, 32);
在布局中添加Text组件用以显示提示信息及读取到的时间,代码如下:
Text text= new Text(this); text.setText("初始文本"); //设置初始显示文本 text.setTextSize(50); DirectionalLayout.LayoutConfig layoutConfig = newDirectionalLayout.LayoutConfig (ComponentContainer.LayoutConfig.MATCH_CONTENT,ComponentContainer.LayoutConfig.MATCH_CONTENT); layoutConfig.alignment = LayoutAlignment.HORIZONTAL_CENTER; text.setLayoutConfig(layoutConfig); directionLayout.addComponent(text);
在本例中两个设备通过各自的Button组件实现时间的读写,因此,需要添加两个Button组件,首先添加写入时间的Button组件,代码如下:
//实现写入功能的Button,用来读取当前时间,并写入分布式文档中 Button button1 = new Button(this); layoutConfig.setMargins(0, 50,0,0); button1.setLayoutConfig(layoutConfig); button1.setText("写入现在时间"); button1.setTextSize(50); ShapeElement background1 = new ShapeElement(); background1.setRgbColor(new RgbColor(0xFF51A8DD)); background1.setCornerRadius(25); button1.setBackground(background1); button1.setPadding(10, 10, 10, 10); button1.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component Component) { goWrite(text); //单击Button,实现写入功能 } }); directionLayout.addComponent(button1);
添加读取时间的Button组件,代码如下:
//实现读取功能的Button,从分布式文档中读取已经写入的时间 Button button2 = new Button(this); layoutConfig.setMargins(0, 50,0,0); button2.setLayoutConfig(layoutConfig); button2.setText("读取上一个时间"); button2.setTextSize(50); ShapeElement background2 = new ShapeElement(); background2.setRgbColor(new RgbColor(0xFF5100DD)); background2.setCornerRadius(25); button2.setBackground(background1); button2.setPadding(10, 10, 10, 10); button2.setClickedListener(new Component.ClickedListener() { @Override //单击Button,实现读取功能 public void onClick(Component Component) { goRead(text); } }); directionLayout.addComponent(button2);
写入功能是由goWrite()方法实现的,分析其实现过程,代码如下:
//goWrite():写入button1的onClick事件执行的方法 private void goWrite(Text text) { String sharedFileName = sharedFileName(this); //获取分布式文件路径 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String str=simpleDateFormat.format(new Date().getTime()); //获取时间戳并转换成标准形式 //将时间写入分布式文件 try{ FileWriter fileWriter = new FileWriter(sharedFileName,false); fileWriter.write(str); fileWriter.close(); } catch (IOException e) { e.printStackTrace(); } text.setText("写入的时间:"+str); text.invalidate(); }
读取功能是由goRead()方法实现的,其实现过程代码如下:
//goRead():读取button2的onClick事件执行的方法 private void goRead(Text text) { String sharedFileName = sharedFileName(this); //获取分布式文件路径 //读取分布式文件中的数据,若读到则输出,若没读到则输出没读到 try{ FileReader fileReader = new FileReader(sharedFileName); BufferedReader br = new BufferedReader(fileReader); String b = br.readLine(); text.setText("读取到的上一个写入的时间:"+b); text.invalidate(); fileReader.close(); } catch (IOException e) { e.printStackTrace(); text.setText("没读到"); text.invalidate(); } }
二者都是通过shareFileName()方法获取分布式文件路径的,代码如下:
File distDir = context.getDistributedDir(); //获取分布式文件目录 //在分布式文件目录下新建一个名为note.txt的文件,读写都在这个文件中进行 String filePath = distDir+File.separator+"note.txt"; return filePath; //返回新建文件的路径 }
需要说明的是,利用Context.getDistributedDir()接口可以获取属于自己的分布式目录,然后通过libc或JDK 接口,可以在该目录下创建、删除、读写文件或目录。本例中在所获取的分布式目录下创建了一个读写文件note.txt。
基于两个手机设备进行验证,在开始功能验证之前,两个手机需要登录同一个华为账号,并且需要开启多设备协同权限且开启多设备协同连接。具体的设置过程可参考前面对多设备协同权限设置的介绍。
设置完成后进行功能验证。首先,在未写入的情况下直接进行读取,读取结果如图2所示,未写入的情况下读取内容为空。
■ 图2 未写入情况下直接进行读取
由Phone A 写入当前时间,Phone B读取当前时间,读写效果如图3所示。
■ 图3 Phone A写入当前时间,Phone B读取A写入的时间
由Phone B 写入当前时间,Phone A 进行读取时,读写效果如图4 所示。
■ 图4 Phone B写入当前时间,Phone A读取B写入的时间
至此,分布式文件服务功能成功实现。