Android系统删除文件原理

Android系统文件底层文件系统ext4/f2fs,这里先引入一点专业信息。

Inode:是 Linux 文件系统中用于描述文件的核心元数据结构,用于记录文件类型(普通文件 / 目录 / 设备等)、权限、属主、时间戳、数据块在磁盘上的映射关系。

目录项:是“文件名 → inode”的映射关系,存在于父目录的数据页中,删除文件时,文件名对应的目录项被移除,inode 的引用计数(nlink)随之减少。

F2fs:是 Google / Samsung 为 NAND Flash 设计的日志结构文件系统,核心特点包括顺序写(log-structured)、避免随机写、强依赖 GC(垃圾回收),与ext4 不同,F2FS 不会原地覆盖旧数据块,而是通过标记 invalid + GC 进行回收。

Orphan:是指已经失去所有目录项引用,但仍可能存在于磁盘中的 inode。

TRIM:是一种存储维护机制,可以理解为这个机制会告诉存储设备那些逻辑块已经失效。

GC:是垃圾回收机制,用于清除无效的逻辑块,通过迁移有效的数据覆盖无效的数据实现清除的效果。

“删除”这个动作通常是对应文件的元数据或者索引进行删除,把存储块标记为“可以被复用”,并不会立刻物理清零。数据本身仍然存在与闪存中,直到被新文件覆盖或者触发TRIM/GC。

如果不是系统级应用,根据Android的沙盒机制可以知道,如果删除app内的数据的情况下,app只能操作自己的目录,通过MediaStore删除媒体文件,在这一步,原理同样是标记空间为“可复用”

如果是系统的“相册”,删除照片的方式与上略有不同,在相册中点击删除的时候,实际上是移动到隐藏目录或者是用标志位标记(删除为0,未删除为1),这一步的时候,实际上文件从未被删除,但也不能一直占用空间,所以有一个时间期限,这部分就是相册的“回收站”,目前市面上大部分“回收站”的保存时间都是30天,时间达到之后才会调用底层删除,在系统级权限条件下,更容易触发 TRIM 或 GC,所以恢复难度较高。

 场景

 是否标记空间可用

 是否立刻物理删除

 普通 App 删除文件

 

 

 普通 App 清空缓存

 

 

 系统相册删除照片

 

 

 系统相册清空回收站

 

 (可能触发 TRIM)

 恢复出厂设置

 ❌(直接销毁 key)

 ❌(但等价物理删除)

源码分析(Android12)

首先我们要明确我们的流程是从Java→JNI→Native层→系统库(bionic/libc)→syscall→Linux Kernel内核,接下来逐步拆解,在进行删除操作后每层的代码会执行什么操作会造成什么后果。

这个是aosp源码中的文件处理类,完整路径为libcore/ojluni/src/main/java/java/io/File.java,由方法可知,在Java层中,根本不对数据做处理,只是由security.checkDelete()方法将路径传到native层。

JNI层,同时可以看到返回的是一个fs.delete()方法的值。这里的FS指的就是android的文件系统,截图如下所示:

那我们继续顺藤摸瓜查询DefaultFileSystem,该文件的玩着路径为libcore/ojluni/src/main/java/java/io/UnixFileSystem.java,截图如下所示:

那现在就可以明白Android的File实际上使用的是UnixFileSystem,那么真正的删除,就是使用UnixFileSystem对象去调用delete方法,根据这个思路我们去尝试查询方法。部分截图如下所示:

可以看到这个方法中只有 Libcore.os.remove获取了文件路径,那么我们可以继续去追这个方法。

文件完整路径为libcore/luni/src/main/java/libcore/io/Libcore.java通过追踪我们成功知道Libcore.os只是一个接口实例,其实就是调用Linux类中的remove(),同时我们查询了remove()方法后,发现在Java层和Jni层是没有实现任何的删除逻辑的,只是将文件层层传递到native层,部分截图如下所示:

之后我们进入native层继续查询remove()方法,文件完整路径为:libcore/luni/src/main/native/libcore_io_Linux.cpp,可以看见这个方法值只是将 Java String 转为 UTF-8 C 字符串,除此之外么有其它的操作。

接下来我们去查询c代码是如何写的,由代码可发现这是一个很标准的POSIX的兼容实现,这里可以发现代码尝试对一个目录使用unlink()方法,并且在POSIX中规定了unlink 只能用于普通文件,目录必须使用 rmdir,其底层最终仍通过 unlinkat + AT_REMOVEDIR 处理,并且使用这个方法只允许目录中的目录项,inode等信息,并不会清零目录内容,同时也不会递归清除里面的信息,部分截图如下所示:

现在我们去查看unlink是如何实现的,完整路径bionic/libc/bionic/unlink.cpp,由代码可以发现的unlink唯一作用,就是把老接口统一到 unlinkat(),主要就是将参数传进系统内核中,下一步我们进入内核进行分析,部分截图如下所示:

这里我们选用Android12对应的通用内核,版本为5.10

这里的作用是将用户调用映射到内核,SYSCALL_DEFINE3(unlinkat)这个方法是更加通用的版本,用于处理当前目录或其他 fd 表示的目录,根据 flag & AT_REMOVEDIR 决定是否是目录删除,最后会调用do_unlinkat()。.

这个函数主要的作用是路径的解析以及核对,权限以及安全检查,区分是文件还是目录,之后调用vfs接口,这个部分同样没有任何对数据进行的操作,截图图下所示:

vfs_unlink函数内同样不会对数据做任何的操作,主要是对文件的权限以及一致性做检查,检查文件是否是非法状态,是否存在只读挂载点,是否具有写权限等,然后是对inode状态处理,包括标记inode需要更新,metedata要变更等。

同时我们可以看到进入这个方法之后调用的具体函数为error = dir->i_op->unlink(dir, dentry)。代码主要含义是“调用当前目录所属文件系统的unlink实现”这里的当前目录所属文件系统,因为使用的aosp源码版本为12,通常使用的就是f2fs的文件系统,所以我们下一步是查询f2fs文件系统的unlink方法,部分截图如下所示:

这个方法内对目录做了一些操作,文件目录的目录项被移除,inode被标记为orphan,文件在文件系统逻辑上不可再访问,同样在这一步,没有对数据块进行清零或者说覆盖等操作,数据依然还在,只是无法通过文件系统进行访问,部分截图如下所示:

可以看到,从 Java 层到内核层,Android 并未在任何一层对数据块进行直接操作,所有逻辑仅用于路径解析、权限校验与元数据更新


Android系统删除文件原理
http://lhyki.cn/archives/AndroidDelete
发布于
2026年01月08日
许可协议