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/ | 关系型数据库文件 | 记得做数据库版本迁移,不然升级就崩 |
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。这些权限会在安装时弹窗让用户确认。
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,维护起来麻烦。而且用户每次选择文件时,系统都会弹窗确认,透明度更高。
4.5 总结一下
文件系统沙箱,说白了就是三件事:
- 圈地:每个应用有自己的私有目录,别人进不来
- 上锁:文件访问有令牌控制,不是你想读就能读
- 隐身:通过挂载命名空间,应用根本不知道其他应用的存在
这套机制,我刚开始觉得有点繁琐。但后来在项目里遇到一次安全审计,审计方问我们:“应用数据怎么隔离的?”我直接把鸿蒙的沙箱机制讲了一遍,对方连连点头。嗯,那一刻我觉得,繁琐一点也值了。
下一章,我会聊聊进程隔离。那又是另一套完全不同的玩法了。