简介:这份资源是面向Android开发初学者与进阶者的文件管理器完整项目源码,基于Android Studio 4.2.1开发,可直接运行于近两年官网下载的AS版本。项目围绕SD卡目录管理展开,涵盖新建、查看、搜索、删除文件或文件夹以及返回根目录等核心功能,并涉及动态权限申请、自定义Dialog与菜单、文件系统操作和适配器等知识点,适合用于课程设计、毕业设计或自学练手。压缩包共479个文件,以flat、json、png、xml、bin、dex、class、jar等为主,包含资源文件、编译产物与依赖库,整体约14.28MB,目录结构完整。目前已有917人学习下载。源码中关键逻辑均配有详细注释,读者可从中掌握权限申请流程、SD卡根目录获取、适配器刷新、deleteFile删除文件、createNewFile新建文件以及contains关键词搜索等实现思路,便于快速理解并二次开发。
1. 从一份带注释的 Android 文件管理器源码说起
很多人第一次在 Android Studio 里做文件管理器,卡住的地方不是界面,而是 Android 10 之后的分区存储把WRITE_EXTERNAL_STORAGE直接废掉,代码照抄旧教程一跑就崩。这份「Android Studio 实现文件管理器,源代码详细注释」的资源,价值就在于它把目录遍历、文件增删改查、权限申请、列表适配这几块串成了一个能跑通的完整 App,而且关键逻辑都写了注释,适合两类人:一类是刚学完四大组件、想找一个真实项目练手的移动应用开发新手;另一类是接私活或做课程设计,需要一份结构清晰、能直接改的安卓 App 源码底子的从业者。它解决的不是「教你写 Hello World」,而是「给你一个能读、能改、能打包的文件管理器骨架」。
2. 环境搭建与工程结构:把源码跑起来的第一步
拿到一份 Android 源码,最怕的就是打开就一片红。这一章先把环境对齐,再讲清楚工程里每个目录是干什么的,后面读代码才不会迷路。
2.1 Android Studio 版本、SDK 与 Gradle 的匹配
这份源码属于典型的传统 View 体系项目,不是 Jetpack Compose 那套,所以对 Android Studio 版本不算挑剔,但 Gradle 和 AGP 的版本必须和 JDK 对上,否则同步阶段就翻车。常见做法是:用 Android Studio 2021.2.1 及以后的稳定版打开,JDK 选 11 或 17,SDK 至少装到 API 33,因为分区存储相关的MediaStore和MANAGE_EXTERNAL_STORAGE在 API 30 之后才有完整行为。
导入步骤按这个顺序走:
# 1. 解压源码包,确认根目录下有 settings.gradle 和 build.gradle # 2. 用 Android Studio 的 Open 打开根目录,不要打开 app 子目录 # 3. 首次同步前先检查 gradle-wrapper.properties 里的 distributionUrl # 如果下载慢,换成国内镜像或本地已缓存的 gradle 版本同步完成后,如果报Could not find com.android.tools.build:gradle,八成是 AGP 版本和 Gradle 版本不匹配。对照关系是:AGP 7.x 配 Gradle 7.x,AGP 8.x 配 Gradle 8.x。改build.gradle里的classpath和gradle-wrapper.properties里的版本号,两边一起改,别只改一个。
提示:如果 Android Studio 提示「资源重复错误」,先看
res目录下有没有同名文件被放在不同drawable-xxx文件夹里,这是移植项目时最常见的低级坑。
2.2 工程目录结构与关键类职责
一个规范的文件管理器源码,目录结构基本是固定的。下面这张表是我读这类项目时习惯先扫一遍的清单:
| 目录/文件 | 职责 | 读代码时的关注点 |
|---|---|---|
MainActivity | 入口,承载文件列表 | 权限申请回调、列表点击事件 |
adapter/ | RecyclerView 适配器 | ViewHolder 绑定、图标区分 |
bean/或model/ | 文件实体类 | 是否封装了路径、大小、类型 |
utils/ | 文件操作工具类 | 复制、删除、重命名的实现 |
AndroidManifest.xml | 权限声明 | 存储权限、requestLegacyExternalStorage |
build.gradle | 依赖与编译配置 | compileSdk、minSdk、依赖库版本 |
读源码的顺序建议是:先看AndroidManifest.xml确认权限,再看MainActivity的onCreate理清初始化流程,最后钻进utils看文件操作的具体实现。这样读,逻辑是顺着用户操作走的,不会一上来就陷进适配器的细节里。
2.3 权限声明与运行时申请
这是整个项目最容易出问题的地方。Android 6.0 之后存储权限要运行时申请,Android 10 之后又加了分区存储,Android 11 之后MANAGE_EXTERNAL_STORAGE成了访问全盘文件的唯一途径。源码里如果只写了READ_EXTERNAL_STORAGE,在新机型上大概率只能看到自己 App 的目录。
<!-- AndroidManifest.xml 中常见的权限声明 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />maxSdkVersion="28"这行的意思是:WRITE_EXTERNAL_STORAGE只在 API 28 及以下生效,29 开始系统不再授予这个权限。所以如果你的targetSdk是 30 以上,必须走MANAGE_EXTERNAL_STORAGE的跳转申请流程,否则文件列表永远是空的。运行时申请的核心代码逻辑是:先判断Environment.isExternalStorageManager(),返回 false 就跳转到系统设置页让用户手动授权,返回 true 才去加载文件列表。这一步不做,后面所有文件操作都是空中楼阁。
3. 核心功能实现:目录遍历、列表渲染与文件操作
环境跑通之后,真正决定这个文件管理器好不好用的,是这三块:怎么把目录读出来、怎么把文件列表画出来、怎么对文件做增删改查。源码的注释主要集中在这里,也是改造成自己项目时动刀最多的地方。
3.1 目录遍历与文件列表数据构建
遍历目录看起来简单,但递归读大目录时如果不做异步,主线程直接 ANR。常见做法是把遍历放在子线程或ExecutorService里,读完再回主线程刷新适配器。
// 遍历指定目录,返回文件列表 public List<FileBean> listFiles(String path) { List<FileBean> result = new ArrayList<>(); File dir = new File(path); File[] files = dir.listFiles(); // 可能返回 null,必须判空 if (files == null) { return result; // 无权限或目录不存在时返回空列表 } for (File f : files) { FileBean bean = new FileBean(); bean.setName(f.getName()); bean.setPath(f.getAbsolutePath()); bean.setDirectory(f.isDirectory()); bean.setSize(f.length()); bean.setLastModified(f.lastModified()); result.add(bean); } // 目录排前面,文件排后面,同类按名称排序 Collections.sort(result, (a, b) -> { if (a.isDirectory() != b.isDirectory()) { return a.isDirectory() ? -1 : 1; } return a.getName().compareToIgnoreCase(b.getName()); }); return result; }这段代码有三个参数点值得注意:listFiles()返回 null 是权限问题的信号,不是异常,必须判空;f.length()对目录返回的是目录项大小而非递归总大小,想显示文件夹真实大小得单独递归算;排序规则决定了用户体验,目录置顶是文件管理器的基本礼仪。如果源码里没做排序,列表会按系统返回顺序乱排,用起来很别扭。
3.2 RecyclerView 适配器与图标区分
文件列表用RecyclerView是标配,适配器的核心工作是根据文件类型显示不同图标,并把点击事件透传出去。
@Override public void onBindViewHolder(ViewHolder holder, int position) { FileBean bean = list.get(position); holder.tvName.setText(bean.getName()); // 根据扩展名或是否目录选择图标 if (bean.isDirectory()) { holder.ivIcon.setImageResource(R.drawable.ic_folder); holder.tvSize.setText("文件夹"); } else { holder.ivIcon.setImageResource(getIconByExt(bean.getName())); holder.tvSize.setText(formatSize(bean.getSize())); } // 点击进入目录或打开文件 holder.itemView.setOnClickListener(v -> { if (listener != null) { listener.onItemClick(bean, position); } }); } // 按扩展名返回图标资源 private int getIconByExt(String name) { String ext = name.contains(".") ? name.substring(name.lastIndexOf(".") + 1).toLowerCase() : ""; switch (ext) { case "jpg": case "png": case "gif": return R.drawable.ic_image; case "mp4": case "avi": return R.drawable.ic_video; case "mp3": case "wav": return R.drawable.ic_audio; default: return R.drawable.ic_file; } }getIconByExt里的lastIndexOf(".")是为了处理a.b.txt这种多后缀文件名,用split(".")会踩坑。formatSize负责把字节数转成 KB/MB/GB,源码里如果没写这个工具方法,列表里显示一长串数字会很难看。点击事件用接口回调而不是在适配器里直接startActivity,是为了让 Activity 掌握导航逻辑,适配器只负责展示,这是解耦的基本功。
3.3 文件复制、删除、重命名的实现与边界
文件操作是文件管理器的灵魂,也是最容易写出 bug 的地方。复制要处理大文件、删除要处理目录递归、重命名要处理目标已存在的情况。
// 递归删除文件或目录 public boolean deleteFile(File file) { if (file == null || !file.exists()) { return false; } if (file.isDirectory()) { File[] children = file.listFiles(); if (children != null) { for (File child : children) { deleteFile(child); // 递归删除子项 } } } return file.delete(); // 目录删完子项后自身也要删 } // 复制文件,使用流式读写避免大文件 OOM public boolean copyFile(File src, File dest) { if (src == null || !src.exists() || src.isDirectory()) { return false; } try (FileInputStream in = new FileInputStream(src); FileOutputStream out = new FileOutputStream(dest)) { byte[] buffer = new byte[8192]; // 8KB 缓冲区,兼顾内存和速度 int len; while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); } return true; } catch (IOException e) { e.printStackTrace(); return false; } }deleteFile的递归顺序必须是先删子项再删自身,反过来会失败。copyFile用 8KB 缓冲区是经验值,太小频繁 IO,太大占内存,FileChannel.transferTo是更高效的替代方案但代码复杂些。重命名用File.renameTo时要注意:目标路径已存在会返回 false,跨分区移动也会失败,稳妥做法是先判断目标是否存在,跨分区时降级为「复制 + 删除」。这些边界源码注释里通常会提,但不会全写,改的时候自己补上。
4. 避坑与常见问题排查
这一章是我踩过的坑里挑出来的,每一条都对应一个真实报错或异常现象,按「现象 → 原因 → 解决」写,照着排查能省不少时间。
4.1 文件列表为空,但权限已经给了
现象:App 启动后列表一片空白,日志里没有明显异常,权限弹窗也点了允许。
原因:targetSdk升到 30 以上后,READ_EXTERNAL_STORAGE只能读到媒体文件,普通目录读不到,必须用MANAGE_EXTERNAL_STORAGE并跳转系统设置页授权。
解决:在AndroidManifest.xml声明MANAGE_EXTERNAL_STORAGE,代码里判断Environment.isExternalStorageManager(),为 false 时用Intent跳转到Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,用户手动开启后返回再刷新列表。
4.2 点击文件夹闪退,报 SecurityException
现象:点进某个子目录时 App 直接崩溃,日志里是java.lang.SecurityException: Permission Denial。
原因:Android 11 之后,即使有了全盘权限,访问Android/data和Android/obb目录仍会被系统拦截,这是系统级限制,不是权限问题。
解决:在遍历时过滤掉这两个目录,或者在点击时捕获异常并提示用户「该目录受系统保护」。源码里如果没做这个过滤,用户点到就崩,体验很差。
4.3 大文件复制时 App 无响应
现象:复制一个几百 MB 的视频,界面卡死几秒到十几秒,严重时 ANR 弹窗。
原因:文件读写放在了主线程,FileInputStream.read是阻塞操作。
解决:把复制逻辑放进ExecutorService或AsyncTask,复制过程中用Handler或LiveData回传进度,界面上显示进度条。源码里如果只有同步复制方法,改造时优先补这一块。
4.4 重命名后文件消失
现象:重命名成功提示弹了,但列表里文件不见了,去文件系统里找又在。
原因:重命名后没有刷新数据源,适配器还持有旧的FileBean列表,路径已经变了但列表没更新。
解决:重命名成功后重新调用listFiles刷新当前目录,再notifyDataSetChanged。别偷懒只改FileBean的 name 字段,路径不同步迟早出问题。
4.5 Gradle 同步报资源重复错误
现象:Duplicate resources或Resource entry xxx is already defined。
原因:移植项目时把不同来源的res目录合并,strings.xml或ic_launcher出现同名资源。
解决:用 Android Studio 的Analyze > Inspect Code定位重复项,或者直接看res/values/strings.xml里有没有重复的string name。图标类资源检查mipmap-xxx各密度文件夹里是否放了同名文件。
5. 二次开发与打包:把源码变成自己的 App
跑通只是第一步,真正要用起来,得会改包名、换图标、调界面,最后打出一个能装到手机上的 APK。这一章讲几个实操技巧,都是改这类源码时绕不开的。
5.1 改包名与 App 名称的正确姿势
改包名不能只在build.gradle里改applicationId,那样R类和Manifest里的包名会对不上。正确顺序是:先在build.gradle改applicationId,再用 Android Studio 的Refactor > Rename改package,最后检查AndroidManifest.xml里的package属性是否同步。App 名称改res/values/strings.xml里的app_name,别去Manifest里硬写android:label。
5.2 打包 APK 与签名配置
调试包用Build > Build Bundle(s) / APK(s) > Build APK(s)就够了,但要装到别人手机上或上架,必须配签名。
// build.gradle 中配置签名 android { signingConfigs { release { storeFile file("your.keystore") storePassword "your_password" keyAlias "your_alias" keyPassword "your_key_password" } } buildTypes { release { signingConfig signingConfigs.release minifyEnabled false // 源码项目先别开混淆,容易出问题 } } }minifyEnabled先设 false,等确认功能都正常了再开混淆,否则反射和序列化相关的代码被混淆后会出现各种玄学崩溃。生成 keystore 用 Android Studio 的Build > Generate Signed Bundle / APK向导,比命令行keytool直观。
5.3 界面微调与常用 UI 控件替换
源码里的界面通常比较朴素,想改成自己的风格,重点动这几个地方:列表项布局item_file.xml里的ImageView和TextView尺寸、颜色;RecyclerView的itemDecoration加分割线;顶部加一个Toolbar显示当前路径和返回按钮。Toolbar替换默认ActionBar时记得在styles.xml里把主题设成NoActionBar,否则会出现两个标题栏叠在一起。
注意:改布局时别直接删
id,适配器里findViewById找不到会直接空指针,改名前先全局搜索一下这个 id 在哪些地方被引用。
5.4 用注释反推设计意图的读码习惯
这份源码最大的价值是注释,但注释也分两种:一种是「这行代码干什么」的废话注释,一种是「为什么这么写」的设计注释。读的时候重点看后者,比如权限申请那段如果注释写了「API 30 以上必须走 MANAGE_EXTERNAL_STORAGE」,说明作者踩过坑,这个信息比代码本身值钱。我一般会边读边在关键类顶部补一段自己的理解,下次再打开这个项目,三分钟就能回忆起来龙去脉。从那以后我每次拿到一份带注释的源码,都强制自己先读注释再读代码,顺序反了容易陷进实现细节里出不来。希望这份文件管理器源码能帮你把 Android 存储这块硬骨头啃下来。
本文还有配套的精品资源,点击获取