Android文件管理器源码详解:分区存储与权限适配实战

发布时间:2026/10/11 19:24:49
Android文件管理器源码详解:分区存储与权限适配实战
简介这份资源是面向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 或 17SDK 至少装到 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.xAGP 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权限声明存储权限、requestLegacyExternalStoragebuild.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:nameandroid.permission.READ_EXTERNAL_STORAGE / uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE android:maxSdkVersion28 / uses-permission android:nameandroid.permission.MANAGE_EXTERNAL_STORAGE /maxSdkVersion28这行的意思是WRITE_EXTERNAL_STORAGE只在 API 28 及以下生效29 开始系统不再授予这个权限。所以如果你的targetSdk是 30 以上必须走MANAGE_EXTERNAL_STORAGE的跳转申请流程否则文件列表永远是空的。运行时申请的核心代码逻辑是先判断Environment.isExternalStorageManager()返回 false 就跳转到系统设置页让用户手动授权返回 true 才去加载文件列表。这一步不做后面所有文件操作都是空中楼阁。3. 核心功能实现目录遍历、列表渲染与文件操作环境跑通之后真正决定这个文件管理器好不好用的是这三块怎么把目录读出来、怎么把文件列表画出来、怎么对文件做增删改查。源码的注释主要集中在这里也是改造成自己项目时动刀最多的地方。3.1 目录遍历与文件列表数据构建遍历目录看起来简单但递归读大目录时如果不做异步主线程直接 ANR。常见做法是把遍历放在子线程或ExecutorService里读完再回主线程刷新适配器。// 遍历指定目录返回文件列表 public ListFileBean listFiles(String path) { ListFileBean 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 存储这块硬骨头啃下来。本文还有配套的精品资源点击获取