Autojs基础-文件系统(files)

0 阅读16分钟

1.前言

脚本开发过程中,考虑成本等多种因素,不会配置云端数据库。我们可以将数据通过文件系统保存到本地txt文件,当我们需要数据时,再通过文件系统取出。除了保存基本文件外,我们也可以将脚本图片等资源在脚本初次启动时加载到本地。文件系统在每个脚本中的使用方式类似,操作简单,但是非常重要。

2.函数

1.概况

我们一般是操作sdcard文件下的文件,sdcard文件夹相当于/storage/emulated/0文件夹,两者是等价的。但是,我看到一些地方说,sdcard文件夹有些情况下不可以,不如/storage/emulated/0兼容性好。因此,我一般使用/storage/emulated/0文件夹。说实话,我真没有发现两者有什么区别,好在我们常操作文件夹是固定的。

如果在Android中看过应用sdcard文件夹会发现,应用文件夹很有特点。我们可以通过com+名称+功能或游戏名称的方式进行命名,因为这是学习阶段,我的常用文件夹就是com.py.test。文件夹全部路径为/storage/emulated/0/com.py.test/,可以在脚本开始时全局保存。

2.isFile

判断文件路径为文件,需要传递路径一个参数,参数类型为字符串,返回类型为boolean。需要注意的是,如果文件不存在时,也会返回false。

// 保存文件总地址
let fileRootPath = "/storage/emulated/0/com.py.test/";
console.log( "路径存在并且为文件:"+ files.isFile(fileRootPath + "test.txt"));
console.log( "路径存在并且为文件夹:"+ files.isFile(fileRootPath));
console.log( "路径不存在并且为文件:"+ files.isFile(fileRootPath + "test2.txt"));

3.isDir

判断文件路径为文件夹,需要传递路径一个参数,参数类型为字符串,返回类型为boolean。需要注意的是,如果文件夹不存在时,也会返回false。

// 保存文件总地址
let fileRootPath = "/storage/emulated/0/com.py.test/";
console.log( "路径存在并且为文件夹:"+ files.isDir(fileRootPath));
console.log( "路径存在并且为文件:"+ files.isDir(fileRootPath + "test.txt"));
console.log( "路径不存在并且为文件夹:"+ files.isDir(fileRootPath + "test2/"));

4.isEmptyDir

判断文件夹是否为空文件夹,需要传递文件夹路径一个参数,参数类型为字符串,返回类型为boolean。需要注意的是,如果文件夹不存在时,也会返回false。这个函数使用之前最好先通过exists函数判断路径是否存在,当存在时,再调用此函数。

// 保存文件总地址
let fileRootPath = "/storage/emulated/0/com.py.test/";
console.log( "路径存在并且为空文件夹:"+ files.isEmptyDir(fileRootPath + "test/"));
console.log( "路径存在并且为非空文件夹:"+ files.isEmptyDir(fileRootPath));
console.log( "路径存在并且为文件:"+ files.isEmptyDir(fileRootPath + "test.txt"));
console.log( "路径不存在并且为文件夹:"+ files.isEmptyDir(fileRootPath + "test2/"));

5.join

将父路径与子路径进行拼接,需要传递两个参数,分别为父目录路径和子路径,参数类型均为字符串,返回拼接后的路径,返回类型为字符串。有些小伙伴可能认为,这个函数和直接使用“+”进行字符串拼接一个功能。其实,两者还是有点区别的,这个函数能够自动忽略路径上多余的“/”或者加上少的“/”。

console.log(files.join("/storage/emulated/0/com.py.test", "test.txt"));
console.log(files.join("/storage/emulated/0/com.py.test/", "/test.txt"));

6.create与createWithDirs

create和createWithDirs均是用于创建文件或文件夹,均需要传递路径一个参数,均返回文件或文件夹是否创建成功,返回类型均为boolean。如果路径上的文件夹都存在(创建文件夹时,不包含最后一个)时,两个函数宫那个一致。当路径上存在一个或者多个未创建的文件夹(创建文件夹时,不包含最后一个)时,create函数会创建失败,而createWithDirs函数会将路径上所有的文件夹创建(创建文件夹时,不包含最后一个),然后再创建最后一个文件或者文件夹。可以看出,createWithDirs函数功能更加强大,create函数通常先通过exists函数判断路径是否存在再进行调用。虽然createWithDirs函数功能强大,但是很多时候输入错误路径时,也会创建出文件来,但是,使用createWithDirs函数时要保证路径一定不要出错。综合来看,两个函数各有优缺点,两者频率差距不大。

// 保存文件总地址
let fileRootPath = "/storage/emulated/0/com.py.test/";
// 文件夹是否存在
console.log("当前文件夹是否存在:" + files.exists(fileRootPath));
console.log("create创建文件是否成功:" + files.create(fileRootPath + "test11.txt"));
console.log("createWithDirs创建文件是否成功:" + files.createWithDirs(fileRootPath + "test12.txt"));

console.log("-----------------------");
let currentFolderPath = fileRootPath + "test1/";
console.log("当前文件夹是否存在:" + files.exists(currentFolderPath));
console.log("create创建文件是否成功:" + files.create(currentFolderPath + "test21.txt"));
console.log("createWithDirs创建文件是否成功:" + files.createWithDirs(currentFolderPath + "test22.txt"));

7.exists

判断文件或文件夹是否存在,需要传递路径一个参数,参数类型为字符串,返回文件或文件是否存在,返回类型为boolean。

// 保存文件总地址
let fileRootPath = "/storage/emulated/0/com.py.test/";
let currentFolderPath = fileRootPath + "test1/";
console.log("test21文件是否存在:" + files.exists(currentFolderPath + "test21.txt"));
console.log("test22文件是否存在:" + files.exists(currentFolderPath + "test22.txt"));

8.ensureDir

确保路径中所有文件夹均存在,需要传递路径一个参数,参数类型为字符串。这个函数类似调用exists函数判断文件夹是否存在,如果不存在,调用createWithDirs函数来创建路径中所有的文件夹。其实,文件系统中很多函数功能类似于多个函数的综合使用,并且函数兼容性比较好,函数一般能够兼容文件路径和文件夹路径。为什么说兼容性比较好?以这个函数为例,传递文件夹路径和文件路径均可以,只确保文件路径中的所有文件夹均存在罢了,不保证最后的文件存在。

// 保存文件总地址
let fileRootPath = "/storage/emulated/0/com.py.test/";
let currentFolderPath = fileRootPath + "test2/";
console.log("当前文件夹是否存在:" + files.exists(currentFolderPath));
files.ensureDir(currentFolderPath);
console.log("确保文件夹存在");
console.log("当前文件夹是否存在:" + files.exists(currentFolderPath));

9.read

读取文件路径的文本内容,需要传递两个参数,分别为文件路径和字符编码(默认为utf-8,可以不传递),参数类型均为字符串,返回读取到的文件内容,返回类型为字符串。我们保存的文件大部分情况下还有中文,一般使用推荐的utf-8编码,因此,我建议大家将read当成只传递文件路径一个参数的函数。但是,调用这个函数之前需要调用exists函数确保文件存在,否则会报错。

// 保存文件总地址
let fileRootPath = "/storage/emulated/0/com.py.test/";
let currentFolderPath = fileRootPath + "test1/";
let currentFilePath = currentFolderPath + "test22.txt";
// 如果文件存在
if (files.exists(currentFilePath)) {
    let fileContent = files.read(currentFilePath);
    console.log(fileContent);
    console.log("类型为:" + (typeof fileContent));
}

其实,为了保证安全性,除了调用exists函数保证路径存在外,还需要调用isFile函数保证为文件。正常情况下,我们不会去读取文件夹的内容,只要保证路径存在即可,但是防止特殊情况下报错而导致脚本停止,最好加上个文件判断或者捕获一下读取文件的异常。但是,我自己使用是,只需要判断文件存在即可,因为我能保证不会去读取文件夹的内容。这部分内容主要是为了让大家知道,读取文件夹会出现下面报错,如果出现这种报错,可以找到方向。

如果读取不存在文件的内容,会出现以下报错:

10.readBytes

通过字节的方式读取文件的内容,需要传递文件路径一个参数,参数类型为字符串,返回读取到的字节数据,返回类型为字节数组。这个函数中文件路径参数要求和read函数一致,一定要保证路径存在且为文件。这个函数只有在特殊情况下使用,比如某个操作文件的函数只接收字节数组类型的数据,我们才使用readBytes函数读取数据,然后直接传到操作函数中。我们自己读取文件内容,肯定不会通过这种方式来读取,远没有read函数方便。

11.write

将文本内容写入本地文件,需要传递三个参数,分别为文件路径、文本内容和字符编码(默认为utf-8,可以不传递),参数类型均为字符串。由于中文写入文件概率很高,我们可以将此函数默认只需要传递文件路径和文本内容两个参数。写入文件之前要保证这个文件已经存在,并且写入方式为覆盖方式,也就是说,如果文件中有内容,会先删除原来内容,再写入新内容。

// 保存文件总地址
let fileRootPath = "/storage/emulated/0/com.py.test/";
let currentFolderPath = fileRootPath + "test1/";
let currentFilePath = currentFolderPath + "test22.txt";
// 如果文件存在
if (files.exists(currentFilePath)) {
    let fileContent = files.read(currentFilePath);
    console.log("文件初始内容为:");
    console.log(fileContent);
    console.log("---------------");
    files.write(currentFilePath, "成功替换");
    fileContent = files.read(currentFilePath);
    console.log("替换后新内容为:");
    console.log(fileContent);
}

12.writeBytes

将字节数据写入到本地文件中,需要传递两个参数,分别文件路径和字节数据,参数类型分别为字符串和字节数组。文件路径要求和写入方式与write一致,这是写入的内容不同。这个函数只有特殊情况下将文件写入本地时才会调用,媒体文件居多。但是,将文件写入本地的方式很多,以图片为例,图片处理模块有将图片直接保存到本地的函数,肯定不会多此一举使用此函数。小伙伴们可以认为,这个函数和readBytes函数一样,只有特殊情况下才会使用,至于啥时候为特殊情况,需要自己判断。简言之,这个函数和readBytes函数平时不要使用,在没办法时,有个考虑思路即可。

13.append与appendBytes

append与appendBytes函数都是在文件中追加内容,这个两个函数的区别与write和writeBytes函数类似,只是追加方式不同罢了。

append函数需要传递三个参数,分别为文件路径、文件内容和字符编码(默认为utf-8,可以不传递),参数类型均为字符串。由于中文写入文件概率很高,我们可以将此函数默认只需要传递文件路径和文本内容两个参数。

appendBytes函数需要传递两个参数,分别为路径和字节数据,参数类型分别为字符串和字节数组。只有特殊情况下,才会使用。需要注意,官方文档中函数上有三个参数,但是详细中只有两个参数的介绍,到底以哪个为主?我通过测试发现,这个函数确实是只能传递两个参数,传递三个参数会报错,可能官方文档上函数上参数没处理好。

// 保存文件总地址
let fileRootPath = "/storage/emulated/0/com.py.test/";
let currentFolderPath = fileRootPath + "test1/";
let currentFilePath = currentFolderPath + "test22.txt";
// 如果文件存在
if (files.exists(currentFilePath)) {
    let fileContent = files.read(currentFilePath);
    console.log("文件初始内容为:");
    console.log(fileContent);
    console.log("---------------");
    let appendContent = "追加内容";
    files.append(currentFilePath, appendContent);
    fileContent = files.read(currentFilePath);
    console.log("追加后新内容为:");
    console.log(fileContent);
}

虽然对这两个追加函数介绍很多,但是我不推荐这两种方式。为了读取数据方便,我们平时会将数组或者对象等数据保存文件中,追加方式根本没用,我更推荐是将文件中内容读取出来,然后通过复杂操作后,然后将最终字符串内容通过write函数写回文件中。哪怕真的用追加的方式可以完成操作,我还是推荐采用全部读取+字符串处理+全部写回的方式完成。我感觉,这个种方式看起来复杂,真正的效率可能比写会效率更高。简言之,append与appendBytes函数可以学习过程中使用,脚本开发过程中没必要使用。但是这种追加的方式,用来保存打印日志倒是一个不错的选择,我看看有时间真得尝试下能不能自己弄个日志打印文件,然后读取文件,自己手搓一个日志悬浮窗。又说多了,这是我灵光一现的想法,但是感觉工作量不小,有时间可以考虑一下。

14.copy与move

copy函数用于将文件从一个文件夹复制到另一个文件夹,需要传递两个参数,分别为需要复制的源文件路径和复制到目标文件的路径,参数类型均为字符串。

move函数用于将文件从一个文件夹移动到另一个文件夹,需要传递两个参数,分别为需要移动的源文件路径和移动到目标文件的路径,参数类型均为字符串。

// 保存文件总地址
let fileRootPath = "/storage/emulated/0/com.py.test/";
// 源文件路径
let currentFolderPath = fileRootPath + "test1/";
let currentFilePath = currentFolderPath + "test22.txt";

// 目标文件路径
let targetFolderPath = fileRootPath + "test2/";
let targetFilePath = targetFolderPath + "test22副本.txt";

// 源副本文件路径
let sourceCopyFilePath = currentFolderPath + "test22副本.txt";

// 文件状态打印
fileStatusLog();

console.log("---------------------");
files.copy(currentFilePath, targetFilePath);
console.log("完成复制");
// 文件状态打印
fileStatusLog();

console.log("---------------------");
files.move(targetFilePath, sourceCopyFilePath);
console.log("完成移动");
// 文件状态打印
fileStatusLog();

// 文件状态打印
function fileStatusLog() {
    console.log("源文件是否存在:" + files.exists(currentFilePath));
    console.log("目标文件是否存在:" + files.exists(targetFilePath));
    console.log("源副本文件是否存在:" + files.exists(sourceCopyFilePath));
}

15.rename与renameWithoutExtension

rename和renameWithoutExtension函数均是用于修改文件名,均需要传递两个参数,分别为文件路径和新文件名。rename传递的新文件名需要包含文件后缀,而renameWithoutExtension函数传递的新文件名不需要包含文件后缀。如果只需要改文件名可以调用renameWithoutExtension函数,如果修改文件名和文件类型可以调用rename函数。

// 保存文件总地址
let fileRootPath = "/storage/emulated/0/com.py.test/";
let currentFolderPath = fileRootPath + "test1/";
let currentFilePath = currentFolderPath + "test22副本.txt";
// 获取当前文件夹所有文件
let fileList = files.listDir(currentFolderPath);
console.log(fileList);

console.log("--------------");
files.renameWithoutExtension(currentFilePath, "test33");
console.log("成功修改文件名");
// 获取当前文件夹所有文件
fileList = files.listDir(currentFolderPath);
console.log(fileList);


console.log("--------------");
files.rename(currentFolderPath + "test33.txt", "test44.txt");
console.log("成功修改文件名和文件类型");
// 获取当前文件夹所有文件
fileList = files.listDir(currentFolderPath);
console.log(fileList);

16.getName、getNameWithoutExtension与getExtension

getName、getNameWithoutExtension和getExtension函数均用于获取文件信息,均需要传递文件路径一个参数,参数类型均为字符串,均返回字符串类型的数据。

getName用于获取文件包含后缀的名称。

getNameWithoutExtension用于获取文件不包含后缀的名称。

getExtension用于获取文件后缀。

let fileRootPath = "/storage/emulated/0/com.py.test/";
let currentFilePath = fileRootPath + "test1/test22.txt";
// 如果文件存在
if (files.exists(currentFilePath)) {
    console.log("包含后缀的名称:" + files.getName(currentFilePath));
    console.log("不包含后缀的名称:" + files.getNameWithoutExtension(currentFilePath));
    console.log("文件后缀:" + files.getExtension(currentFilePath));
}

17.remove与removeDir

remove和removeDir函数均用于删除操作,均需要传递路径一个参数,参数类型均为字符串,均返回是否操作成功,返回类型均为boolean。

remove用于删除文件或者空文件夹,删除文件夹时,需要写先判断文件夹是否为空。

removeDir用于删除文件夹,如果文件夹不为空,会删除该文件夹下的所有内容,再删除此文件夹。

let fileRootPath = "/storage/emulated/0/com.py.test/";
let currentFolderPath = fileRootPath + "test2/";
let fileList = files.listDir(currentFolderPath);
console.log(fileList);

console.log("-----------------");
console.log("是否成功删除单个文件:" + files.remove(currentFolderPath + "test44.txt"));
fileList = files.listDir(currentFolderPath);
console.log(fileList);

console.log("-----------------");
console.log("是否成功删除文件夹:" + files.removeDir(currentFolderPath));
console.log("test2文件夹是否存在:" + files.exists(currentFolderPath));

除了上面基础用法外,大家应该比较关注要删除的文件夹中含有文件夹情况下,removeDir函数是否生效。我通过测试发现,需要删除的文件夹下如果嵌套多层文件夹也能成功删除。removeDir函数删除功能非常强大,但是也得注意错误删除文件夹,需要谨慎使用这个函数。推荐还是,删除文件夹下所有文件后,再删除空文件夹。

18.getSdcardPath与cwd

getSdcardPath函数用于获取sd卡地址,返回sd卡的地址,返回类型为字符串。

cwd函数用于返回当前工作路径,返回当前工作路径地址,返回类型为字符串。

let sdcardPath = files.getSdcardPath();
let workPath = files.cwd();
console.log(sdcardPath);
console.log(workPath);

19.path

返回相对路径对应的绝对路径,需要传递相对路径一个参数,参数类型为字符串,返回绝对路径,返回类型为字符串。其实,这个函数作用就用cwd()函数路径替换相对路径“./”的内容。

let absolutePath = files.path("./文件系统/test.txt");
console.log(absolutePath);

读取相对路径文件时,必须将整个项目保存到设备,然后再进行启动。真正启动时会发现,在设备上启动,相对路径文件可以找到。但是,在代码编辑器中启动时,相对路径文件又无法找到。通过这个函数获取的绝对路径,可以找到文件。

let absolutePath = files.path("./文件系统/test.txt");
console.log(absolutePath);
if (files.exists(absolutePath)) {
    let fileContent = files.read(absolutePath);
    console.log(fileContent);
}else {
    console.log("文件不存在");
}

但是,通过path函数找到绝对路径开发时可以使用,以“文件系统”项目为例,打包时都在这个项目中打包,打包时采用这种绝对路径方式必然失败,应该采用相对路径方式打包。这样就出现了个问题,如果采用相对路径方式,需要在设备中启动项目;如果采用path函数绝对路径,打包后,无法再次运行项目(有个别小伙伴说,我打包后也能找到文件,这是因为设备中也有编辑器推送的代码,在新设备中根本无法找到文件的)。
基于上面这种情况,我们可以提前预制开发环境和生产环境对应的相对路径文件夹。然后赋值给当前相对路径relativeFolder变量,以后都使用relativeFolder变量即可。如果环境改变时,切换relativeFolder变量赋值即可。

// 生产环境相对路径文件夹
let procRelativeFolder = "./";
 
// 项目名称,根据实际情况替换
let projectName = "文件系统";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
let relativeFolder = procRelativeFolder;

let absolutePath = files.path(relativeFolder + "test.txt");
console.log(absolutePath);
if (files.exists(absolutePath)) {
    let fileContent = files.read(absolutePath);
    console.log(fileContent);
    toast(fileContent);
}else {
    console.log("文件不存在");
}

生产环境的相对路径是固定的,开发环境每个项目需要修改对应的项目名称,其他地方不用修改。项目名称其实是文件夹名称,以下是代码编辑器和autojs中的项目名称,可以在任意地方查看,然后进行替换即可。

20.listDir

打印文件夹下所有文件带后缀的文件名称,需要传递两个参数,分别为文件夹路径和过滤函数(可不传递),参数类型分别为字符串和函数,返回文件信息,返回类型为数组。我不建议在这个函数中直接调用过滤函数,我们可以先获取所有的文件信息,然后通过js的数组过滤方法来获取需要的数据。

let fileRootPath = "/storage/emulated/0/com.py.test/";
let currentFolderPath = fileRootPath + "test1/";
if (files.exists(currentFolderPath)) {
    let fileList = files.listDir(currentFolderPath);
    console.log(fileList);

    console.log("---------------------------");
    fileList = files.listDir(currentFolderPath, function (fileName) {
        return fileName.startsWith("test2");
    });
    console.log(fileList);

    console.log("---------------------------");
    // 相当于
    fileList = files.listDir(currentFolderPath);
    let filterList = fileList.filter(function (fileName, index){
        return fileName.startsWith("test2");
    }) 
    console.log(filterList);
}

3.总结

特别注意,只有通过个人主页博客或者个人介绍中方式,才能获取源码