文件服务设计

1. 概述

1.1 应用模块

作为项目的基础模块为各个项目提供统一的文件操作能力,文件服务提供给基础模块调用,基础模块再将其封装对外开放接口。

1.2 开发背景

新的App提供统一简明的接口,尽量覆盖更多的业务场景。提供基本的文件存储相关的接口。解决多人多团队开发的情况下,造成的文件存储的问题,如目录管理混乱,不同业务资源存储混乱,代码重复等。解决文件存储需要考虑的问题,如不同账户需要管理不同文件的问题,文件操作等级不同的问题等。

1.3 术语

腾讯云Cos系统

虚拟文件

2.调研分析

2.1 基本目标

这里的文件服务有几个层面的含义,分别是管理文件服务,操作文件服务。

管理文件服务

(1)划分文件存储的目录

(2)管理文件存储的位置

(3)定义目录的操作性,如是否可删除等

操作文件服务

(1)提供文件的读、写、新建、修改、删除、拷贝等操作;

(2)提供目录的新建、删除、移动、拷贝,遍历等操作;

2.2 业务相关性分析

这里我们头脑风暴一下,列举一下可能会用到文件服务或者跟文件操作比较多的模块。以此作为参考,一方面作为接口设计的参考,一方面来用来验证我们的文件服务是否适应多变的业务场景。

(1)图片、视频等,各种文件选择器

(2)配置文件的保存、匹配、完整性检测

(3)文件缓存的清理

(4)网络文件结构显示及管理,如ftp、云盘等

2.2 开发相关性分析

这里列举一下文件存储需要考虑的问题或者开发遇到的问题,后面接口检查的时候看是否能解决到此类问题。

(1)FileUtil.java帮助类大量重复;IOS可能不需要

(2)资源文件或者配置文件的本地存储,目录比较混乱清理资源的时候清理不全

(3)特定的业务,如加解密,解压等,有公共的接口

(4)不同账户登陆管理的文件不同

(5)文件的可操作等级不同,临时文件,可删除文件,不可删除文件;

(6)做到对上层透明, 跨平台化,IOS 跟 android 不再有沙盒跟sd卡的区分。

2.3 Android存储分析

Android手机上的存储空间可做如下划分:

● 内存:RAM

● 内部存储:内部ROM

● 外部存储:外部ROM和SDCard

(1)文件目录含义

android目录结构

data文件夹:内部都是app的包名,存储着应用程序相关的数据,例如 data/data/包名/(shared_prefs、database、files、cache);

mnt文件夹: 是Unix/Linux系统下外部设备的专用目录,Linux默认挂载外部设备都会挂载到这个目录;如将SD卡挂载后,会生成目录:/mnt/sdcard/.

sdcard文件夹:这个文件夹中的文件又分为两类,一类是公有目录,还有一类是私有目录,其中的公有目录有九大类,比如DCIM、DOWNLOAD等这种系统为我们创建的文件夹,私有目录就是Android这个文件夹,这个文件夹打开之后里边有一个data文件夹,打开这个data文件夹,里边有许多包名组成的文件夹。

如果按照路径的特征,我们又可以将文件存储的路径分为两大类,一类是路径中含有包名的,一类是路径中不含有包名的,含有包名的路径,因为和某个App有关,所以对这些文件夹的访问都是调用Context里边的方法,而不含有包名的路径,和某一个App无关,我们可以通过Environment中的方法来访问。如下图:

目录描述

注意上述最后两个API:当app被卸载后,sdCard/Android/data/PackageName/下的所有文件都会被删除,不会留下垃圾信息。两个API对应的目录分别对应着 设置->应用->应用详情里面的“清除数据”与“清除缓存”选项。

2.4 IOS存储分析

IOS 沙盒提供不同的目录保存不同需求的文件,如是否备份云端,是否永久保存。

(1).Documents:

  用户生成的文件、其他数据及其他程序不能重新创建的文件,iTunes备份和恢复的时候会包括此目录。

(2).Library/Caches:

  可以重新下载或者重新生成的数据,数据库缓存文件和可下载内容应该保存到这个文件夹,iTunes不会备份此目录,此目录下文件不会在应用退出删除。

(3).tmp

  只是临时使用的数据,iTunes不会备份和恢复此目录,此目录下文件可能会在应用退出后删除。

2.5 跨平台上的考虑

Android和IOS的存储结构不一致,作为跨平台的接口,我们需要在目录结构分配的时候针对不同平台做相应的划分。根据两个平台存储的方式,我们在接口上应该做相应的抽象,将可操作的目录根据含义划分。

3.设计

3.1 设计目的

文件服务的根本目的是为APP提供统一、简洁、方便的文件操作的接口,为不同业务应用服务。

3.2 总体结构

这里看下本模块在整体构架中的位置,并简要描述本模块与周边系统的交互关系(如依赖方向、通信手段等);

● 物理文件。

分为本地文件和云端文件,不同平台上物理文件的权限不一样。

● 文件服务的设计。

(1)基本文件操作;

提供基本的文件的接口的实现,如创建、删除、移动、拷贝等。

(2)虚拟文件系统;

抽象出一个虚拟的文件系统的概念,即将一块空间或者某个目录结构当成一块完整的物理空间,对此我们进行管理划分,我们要做的比如,目录分配、目录映射、目录删除及对外提供接口。

(3)文件管理服务;

在这个层次上,我们还需要对文件管理服务做一个生命周期的管理、对现有虚拟文件的一些索引信息进行保存或者管理。

业务模块。

对外接入的一些业务。

结构描述

3.3 接口设计

(1)文件服务调用的基本流程

流程图

(2)接口类关系

类关系

这里IFileService使用了IFileSystem的接口,IFileSystem最为一个抽象的文件系统,可以是本地的文件,也可能是远程文件,还可能是zip或者jar这种一块物理存储区域。只要实现了我们的这些接口均可以通用。

(3)接口设计及含义

详见 https://git.code.oa.com/rabbit/IdlTools/tree/master/idl_file/File

(1)IRAFileService

接口名

含义

createFileSystem

创建虚拟文件系统

(1)IRAFileSystem

接口名

含义

init

*初始化文件系统*@path 初始化路径,具体由文件系统实现定义。*@return 初始化成功返回true, 否则返回false。

uninit

反初始化

delete

删除该文件系统

openFile

#打开一个文件 #@return 具体文件的接口,其实质是具体文件的句柄。

setExtraParam

#针对特殊文件系统的特殊参数设置,具体取值参考文件系统实现文档。 #网络文件系统,可以通过此参数设置认证用的token。 打包文件系统可以用此接口设置打开包所需要的密码。

(2)IRAFile

接口名

含义

create():bool;

#在此文件路径位置创建一个空文件。 #文件存在时候强制覆盖原来的文件。 #@return 如果成功则返回true, 失败则返回false。

delete():bool;

#删除此文件路径的文件。 #@return 成功则返回true, 失败则返回false。

exists():bool;

#测试此路径名表示的文件或目录是否存在。

makedir():bool;

#在当前位置创建文件夹。

copy(destPath:string):bool;

#将此文件copy到指定位置 #如果目标位置已经存在文件则直接覆盖。 #如果此是目录,则直接返回失败。 #@return 如果拷贝成功则返回true, 否则返回false

move(destPath:string):bool

#将此文件、目录移动到目标位置,如果目标位置已经存在文件则直接覆盖。 #@return 如果移动成功则返回true, 否则返回false

getPath():string;

#Path()文件的绝对路径。 #如果是目录,最后不包含斜杠。

getParent():string;

#返回此路径名的父路径名的抽象路径名 #返回值最后不包含斜杠 #如果此路径名没有指定父目录,则返回 null。

getName():string;

#返回由此文件或目录的名称。

size():i64;

#文件大小 #@return 返回文件的体积

listFiles():list<IRAFile>;

#返回一个文件数组, #这些路径名表示此抽象路径名所表示目录中的文件。 #如果是一个文件,则返回NULL。

isDirectory():bool;

#判断是一个文件还是一个目录。 #@return 如果是一个目录则返回True,否则返回False。

readAllBytes():binary;

#读取文件的二进制内容。 #打开文件,并将文件内容以二进制返回, 之后关闭文件。 #如果文件读取失败, 或此文件是一个目录则返回一个NULL。

writeAllBytes(data:binary):bool;

#将二进制数据写入文件。 #打开文件,并将data对应的二进制数据写入文件, 之后关闭文件。 #写入成功后,文件的原内容将被覆盖。 #如果写入文件失败,则返回false, 否则返回true。

getCreateTime():i64;

#获取文件的创建时间。(自1970年1月1日午夜起的毫秒数) #@return 文件的创建时间,如果没有创建时间则返回-1。

getLastModifiedTime():i64;

#获取文件的修改时间。(自1970年1月1日午夜起的毫秒数) #@return 文件的修改时间,如果没有修改时间则返回-1。

getLastAccessTime():i64;

#获取文件的访问时间。(自1970年1月1日午夜起的毫秒数) #@return 文件的访问时间,如果没有访问时间则返回-1。

openFileSteam():IRAFileStream;

#打开一个文件流 #@return 返回所打开的文件流,如果失败则返回NULL。

(3)IRAFileStream

接口名

含义

read(readBuffer: binary, readSize:i64):i64;

#从当前位置将文件的二进制内容读取到readBuffer中。 #@readBuffer 存储读取数据的buffer。 #@readSize 需要读取的二进制数据的大小。 #@return 实际读取到的大小, 如果文件到达末尾则返回-1

write(dataToWrite:binary, writeSize:i64):i64;

#从当前位置将data写入到文件中。 #@dataToWrite 存放要写入的数据缓存。 #@writeSize 需要写入的二进制数据大小。 #@return 实际写入的二进制数据的大小,如果无法写入则返回-1

seek(pos:i64, origin:RASeekOrigin):i64;

#移动当前文件指向的位置 #@pos 移动的位置 #@origin 相对于哪里开始移动。 #@return 相对于 origin 参数的字节偏移量

size():i64;

#@return 文件的大小

tell():i64;

#@return 返回当前文件指向的位置

close();

#关闭这个文件流

3.4 虚拟文件元数据探讨

一般虚拟文件系统主要分为两部分,一部分为元数据(metadata),另一部分为数据本身。元数据,是“包含了与数据有关信息的数据”,比如文件属性、文件时间戳等。这里我们操作的是实在的物理文件数据本身,所以元数据本身就存在这个文件的属性里面,我们不在额外设计。

我们用设计好的目录结构简单的来管理当前虚拟文件。

4.方案对比

4.1 Flutter文件系统设计

Flutter 文件系统只要分为两部分, path_provider.dart 管理App的目录,file.dart实现对文件的读写,下面以IOS为例:

目录接口名

含义

getTemporaryDirectory()

对应沙盒Tmp目录

getApplicationDocumentsDirectory()

对应沙盒Document目录

主要文件操作接口名

含义

open/openRead/openWrite

打开一个文件, 读写, 只读, 写

writeAsByte/writeAsString

写文件

readAsByte/readAsString

读文件

create

创建一个文件

rename

重命名一个文件

copy

拷贝一个文件

length

获取文件的大小

get/set_LastAccessed

获取或设置文件的最后访问时间

get/set_LastModified

获取或设置文件的最后访修改时间

特点

1、跨平台,使用方不需要考虑具体设备系统。

2、文件操作接口都实现了同步跟异步。

https://docs.flutter.io/flutter/dart-io/File-class.html

FileMode

https://docs.flutter.io/flutter/dart-io/FileMode-class.html

4.2 Unity3D文件管理相关的接口设计

https://docs.unity3d.com/ScriptReference/30_search.html?q=file

https://docs.unity3d.com/ScriptReference/Windows.File.html

引擎的API中File类的接口

Directory类

脚本编辑器用的C#

https://docs.microsoft.com/zh-cn/dotnet/api/system.io.file?redirectedfrom=MSDN&view=netframework-4.7.2

特点:

(1)引擎公开的api接口简单,包括读、写和删除,接口封装成了“文件”和“目录”两个类抽象类来管理,都是同步操作;

(2)编辑器用的是.NET Framework的文件管理,文件管理接口非常丰富

读、写、拷贝、移动、加解密等,同步操作;用属性的目录映射不同平台的文件夹,几乎是跨平台接口的标准做法;

Android平台

Application.dataPath : /data/app/xxx.xxx.xxx.apk

Application.streamingAssetsPath : jar:file:///data/app/xxx.xxx.xxx.apk/!/assets

Application.persistentDataPath : /data/data/xxx.xxx.xxx/files

Application.temporaryCachePath : /data/data/xxx.xxx.xxx/cache

IOS平台

Application.dataPath : Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxx.app/Data

Application.streamingAssetsPath : Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxx.app/Data/Raw

Application.persistentDataPath : Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/Documents

Application.temporaryCachePath : Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/Library/Caches

4.3 Android源码中文件管理相关的接口设计

Context.java中的文件相关接口https://developer.android.com/reference/android/content/Contex

接口

特点:

(1)在Context中的文件操作类基本是读出,得到目录或者文件列表两个操作;

(2)基本上都是以文件为对象返回;

在看下JDK中文件对象的接口设计

http://tool.oschina.net/apidocs/apidoc?api=jdk-zh

特点:

(1)文件对象类只有文件新建删除,列出文件,新建目录等操作,读写放到其他的类中,都是同步,异步接口放到NIO的包中;

4.3 小结

(1)我们这里虚拟文件基本上只是用来管理目录,复杂的读写可以交给不同平台具体去操作;我们保证新建和获取文件系统的接口,帮助分配目录空间;

(2)是否要做异步的接口,我们这里可将其作为后续先扩展;

(3)用属性的目录映射不同平台的文件夹,几乎是跨平台接口的标准做法;

(4)是否需要listRoot这个操作可以交给各个系统获取到了目录之后自己操作;

5.对性能需求的实现分析

(1)同步操作,交给上层进行异步处理;

(2)整个操作属于代理的工作无效率和内存问题

6.异常处理

这里的文件操作接口,不涉及具体的读写的话,可以不用抛出异常

7.参考资料

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券