4、文件系统沙箱:应用私有目录、文件访问权限控制、沙箱文件系统挂载

文件系统沙箱,说白了就是给每个应用圈一块地,让它只能在自己的地盘里折腾。你想想看,如果所有应用都能随便读写手机里的任何文件,那不乱套了?我刚开始接触鸿蒙时,第一反应就是:这跟Linux的权限管理有啥区别?后来深入一看,发现鸿蒙做得更绝——它从根上就把应用给“关”起来了。

4.1 应用私有目录:每个App都有自己的“小黑屋”

在鸿蒙系统里,每个应用安装后,系统会分配一个专属的目录。这个目录长什么样呢?我直接给你看个例子:

/data/app/el2/100/base/com.example.myapp/

这个路径里藏着不少信息。我来拆解一下:

  • el2:表示加密等级。el2是设备级加密,重启后需要输入密码才能访问
  • 100:这是用户的ID。多用户场景下,每个用户都有自己的100、101
  • base:基础目录,存放应用的核心数据
  • com.example.myapp:应用的包名,唯一标识

在这个私有目录下,鸿蒙又细分了几个子目录:

子目录 用途 我踩过的坑
files/ 应用自己的文件,比如数据库、配置文件 别把缓存文件放这里,会被系统清理掉
cache/ 临时缓存,系统可以随时清空 我曾经把用户登录token放cache里,结果一清理就掉线
preferences/ 键值对存储,类似SharedPreferences 注意不要存敏感信息,加密等级不够
databases/ 关系型数据库文件 记得做数据库版本迁移,不然升级就崩
我的习惯:每次创建文件前,先用context.getApplicationContext()获取正确的路径。别自己拼字符串,容易拼错。我见过有人把路径写死,结果换了设备就找不到文件了。

4.2 文件访问权限控制:不是你想读就能读

鸿蒙的权限控制,我总结为“三层过滤”:

第一层:应用沙箱边界
应用默认只能访问自己的私有目录。想读别人的?门都没有。这是最基础的隔离。

第二层:文件级权限
就算在私有目录里,也不是所有文件都能随便读。鸿蒙引入了“文件访问令牌”机制。每个文件打开时,系统会检查调用方是否有对应的令牌。

// 错误示范:直接传路径
let file = await fileIo.open("/data/storage/el2/base/files/data.txt");

// 正确做法:通过沙箱接口获取
let context = getContext(this);
let filePath = context.filesDir + "/data.txt";
let file = await fileIo.open(filePath);

我曾经在项目里犯过一个低级错误:直接把硬编码路径传进去,结果在真机上死活打不开文件。后来才发现,鸿蒙的沙箱路径是动态映射的,你看到的路径和实际物理路径根本不是一回事。

第三层:能力权限
如果应用确实需要访问公共资源,比如相册、文档,那就得申请对应的ohos.permission。这些权限会在安装时弹窗让用户确认。

注意:鸿蒙对权限的管控比Android更严格。我记得有一次,应用只是读取了一下媒体库的缩略图,就被系统判定为“频繁访问”,直接限制了接口调用频率。所以,能不走公共目录就别走。

4.3 沙箱文件系统挂载:看不见的“透明墙”

这部分是鸿蒙最巧妙的设计。你想想,应用看到的文件路径是/data/storage/el2/base/files/,但实际物理路径可能是/data/app/el2/100/base/com.example.myapp/files/。这中间的映射,就是靠“沙箱文件系统挂载”实现的。

鸿蒙使用了一种叫“挂载命名空间”的技术。每个应用启动时,系统会创建一个独立的挂载命名空间。在这个空间里,只有应用自己的私有目录被挂载进来。其他应用的目录?根本看不见。

我画个简化的流程图给你看:

应用视角:
/data/storage/el2/base/files/  →  实际物理路径
/data/storage/el2/base/cache/  →  实际物理路径
/data/storage/el2/base/preferences/  →  实际物理路径

系统视角:
/data/app/el2/100/base/com.example.myapp/files/
/data/app/el2/100/base/com.example.myapp/cache/
/data/app/el2/100/base/com.example.myapp/preferences/

这种设计的好处很明显:

  • 路径统一:不管设备怎么变,应用看到的路径都是固定的
  • 安全隔离:应用根本不知道其他应用的存在,更别说访问了
  • 便于迁移:换手机时,系统只需要重新挂载一下,应用数据就过来了

避坑指南:我曾经在调试时,想通过adb shell直接进到应用的私有目录看看。结果发现,就算有root权限,你也得先找到正确的挂载点。鸿蒙把物理路径藏得很深,普通开发者根本不需要关心。嗯,这也是为了保护你——少知道点,反而更安全。

4.4 跨应用文件共享:怎么破墙?

有时候,两个应用确实需要共享文件。比如,图片编辑应用要读取相册里的照片。这时候怎么办?鸿蒙提供了两种方式:

方式一:FilePicker(文件选择器)
用户主动选择文件,系统生成一个临时访问令牌。应用拿到令牌后,只能读取这个文件,不能遍历目录。

// 调用文件选择器
let picker = new filePicker.FilePicker();
let result = await picker.select({
  maxCount: 1,
  fileType: ["image/jpeg"]
});
// result里返回的是临时URI,不是真实路径

方式二:SAF(存储访问框架)
通过ContentProvider暴露数据。这种方式适合需要频繁共享的场景,比如文档编辑器。

我个人更推荐FilePicker。为什么?因为SAF需要你写ContentProvider,维护起来麻烦。而且用户每次选择文件时,系统都会弹窗确认,透明度更高。

小技巧:如果你需要把文件分享给其他应用,别直接传路径。用FileProvider生成一个content:// URI,这样接收方只能读这个文件,不能顺藤摸瓜找到你的私有目录。我见过有人直接传路径,结果被恶意应用利用了。

4.5 总结一下

文件系统沙箱,说白了就是三件事:

  1. 圈地:每个应用有自己的私有目录,别人进不来
  2. 上锁:文件访问有令牌控制,不是你想读就能读
  3. 隐身:通过挂载命名空间,应用根本不知道其他应用的存在

这套机制,我刚开始觉得有点繁琐。但后来在项目里遇到一次安全审计,审计方问我们:“应用数据怎么隔离的?”我直接把鸿蒙的沙箱机制讲了一遍,对方连连点头。嗯,那一刻我觉得,繁琐一点也值了。

下一章,我会聊聊进程隔离。那又是另一套完全不同的玩法了。