作为项目的基础模块为各个项目提供统一的文件操作能力,文件服务提供给基础模块调用,基础模块再将其封装对外开放接口。
为新的App提供统一简明的接口,尽量覆盖更多的业务场景。提供基本的文件存储相关的接口。解决多人多团队开发的情况下,造成的文件存储的问题,如目录管理混乱,不同业务资源存储混乱,代码重复等。解决文件存储需要考虑的问题,如不同账户需要管理不同文件的问题,文件操作等级不同的问题等。
腾讯云Cos系统
虚拟文件
这里的文件服务有几个层面的含义,分别是管理文件服务,操作文件服务。
管理文件服务
(1)划分文件存储的目录
(2)管理文件存储的位置
(3)定义目录的操作性,如是否可删除等
操作文件服务
(1)提供文件的读、写、新建、修改、删除、拷贝等操作;
(2)提供目录的新建、删除、移动、拷贝,遍历等操作;
这里我们头脑风暴一下,列举一下可能会用到文件服务或者跟文件操作比较多的模块。以此作为参考,一方面作为接口设计的参考,一方面来用来验证我们的文件服务是否适应多变的业务场景。
(1)图片、视频等,各种文件选择器
(2)配置文件的保存、匹配、完整性检测
(3)文件缓存的清理
(4)网络文件结构显示及管理,如ftp、云盘等
这里列举一下文件存储需要考虑的问题或者开发遇到的问题,后面接口检查的时候看是否能解决到此类问题。
(1)FileUtil.java帮助类大量重复;IOS可能不需要
(2)资源文件或者配置文件的本地存储,目录比较混乱,清理资源的时候清理不全;
(3)特定的业务,如加解密,解压等,没有公共的接口;
(4)不同账户登陆管理的文件不同;
(5)文件的可操作等级不同,临时文件,可删除文件,不可删除文件;
(6)做到对上层透明, 跨平台化,IOS 跟 android 不再有沙盒跟sd卡的区分。
Android手机上的存储空间可做如下划分:
● 内存:RAM
● 内部存储:内部ROM
● 外部存储:外部ROM和SDCard
(1)文件目录含义
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对应的目录分别对应着 设置->应用->应用详情里面的“清除数据”与“清除缓存”选项。
IOS 沙盒提供不同的目录保存不同需求的文件,如是否备份云端,是否永久保存。
用户生成的文件、其他数据及其他程序不能重新创建的文件,iTunes备份和恢复的时候会包括此目录。
可以重新下载或者重新生成的数据,数据库缓存文件和可下载内容应该保存到这个文件夹,iTunes不会备份此目录,此目录下文件不会在应用退出删除。
只是临时使用的数据,iTunes不会备份和恢复此目录,此目录下文件可能会在应用退出后删除。
Android和IOS的存储结构不一致,作为跨平台的接口,我们需要在目录结构分配的时候针对不同平台做相应的划分。根据两个平台存储的方式,我们在接口上应该做相应的抽象,将可操作的目录根据含义划分。
文件服务的根本目的是为APP提供统一、简洁、方便的文件操作的接口,为不同业务应用服务。
这里看下本模块在整体构架中的位置,并简要描述本模块与周边系统的交互关系(如依赖方向、通信手段等);
分为本地文件和云端文件,不同平台上物理文件的权限不一样。
提供基本的文件的接口的实现,如创建、删除、移动、拷贝等。
抽象出一个虚拟的文件系统的概念,即将一块空间或者某个目录结构当成一块完整的物理空间,对此我们进行管理划分,我们要做的比如,目录分配、目录映射、目录删除及对外提供接口。
在这个层次上,我们还需要对文件管理服务做一个生命周期的管理、对现有虚拟文件的一些索引信息进行保存或者管理。
业务模块。
对外接入的一些业务。
(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(); | #关闭这个文件流 |
一般虚拟文件系统主要分为两部分,一部分为元数据(metadata),另一部分为数据本身。元数据,是“包含了与数据有关信息的数据”,比如文件属性、文件时间戳等。这里我们操作的是实在的物理文件数据本身,所以元数据本身就存在这个文件的属性里面,我们不在额外设计。
我们用设计好的目录结构简单的来管理当前虚拟文件。
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
https://docs.unity3d.com/ScriptReference/30_search.html?q=file
https://docs.unity3d.com/ScriptReference/Windows.File.html
引擎的API中File类的接口
Directory类
脚本编辑器用的C#
特点:
(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
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的包中;
(1)我们这里虚拟文件基本上只是用来管理目录,复杂的读写可以交给不同平台具体去操作;我们保证新建和获取文件系统的接口,帮助分配目录空间;
(2)是否要做异步的接口,我们这里可将其作为后续先扩展;
(3)用属性的目录映射不同平台的文件夹,几乎是跨平台接口的标准做法;
(4)是否需要listRoot这个操作可以交给各个系统获取到了目录之后自己操作;
(1)同步操作,交给上层进行异步处理;
(2)整个操作属于代理的工作无效率和内存问题
这里的文件操作接口,不涉及具体的读写的话,可以不用抛出异常
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。