FilesEditor 源码剖析:Android 内部存储与外部存储的文件读写实战
示例工程教程【免费下载链接】cw-omnibusSource code to omnibus edition of _The Busy Coders Guide to Android Development_项目地址https://gitcode.com/gh_mirrors/cw/cw-omnibus点击查看免费下载导读本文基于 cw-omnibus 仓库中的 FilesEditor 示例应用项目文档系统讲解 Android 中内部存储与外部存储两类文件 I/O 的完整实现方案。通过阅读本文你将掌握getFilesDir()、getExternalFilesDir()与Environment.getExternalStoragePublicDirectory()三种存储位置的适用场景与差异理解如何用后台线程安全读写文件、如何在 Android 6.0 上正确处理WRITE_EXTERNAL_STORAGE运行时权限并看懂一套可复用的异步加载 生命周期内自动保存文本编辑器骨架。该示例应用在The Busy Coders Guide to Android Development一书的使用文件working with files章节中有更深入的配套讲解本仓库中的代码则是书中内容的可直接运行验证版本。一、示例应用要解决的问题FilesEditor 是一个演示性质的小型 Android 应用它提供一个文本编辑界面允许用户对同一个test.txt文件分别进行内部存储、外部私有存储和外部公共存储三处位置的读写。其核心价值在于用最直观的三标签页对比回答一个 Android 开发中非常基础却又容易混淆的问题文件应该放在哪里内部存储和外部存储的 API 有何区别哪些位置需要运行时权限读写操作如何避免阻塞 UI 线程从仓库顶层 README 可以确认cw-omnibus 收录的是书中数百个示例应用的源码顶层目录名与书籍章节主题对应Files/目录即对应文件处理相关章节FilesEditor 正是其中的代表性样本。二、三种存储位置内部、外部私有、外部公共示例的三个标签页由 SampleAdapter.java 定义。它继承自FragmentPagerAdapter固定返回 3 个页面每个页面针对一个存储位置创建一个File对象然后交给同一个EditorFragment编辑private static final String FILENAMEtest.txt; Override public Fragment getItem(int position) { File fileToEdit; switch(position) { case TAB_INTERNAL: fileToEditnew File(ctxt.getFilesDir(), FILENAME); break; case TAB_EXTERNAL: fileToEditnew File(ctxt.getExternalFilesDir(null), FILENAME); break; default: fileToEdit new File(Environment. getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), FILENAME); break; } return(EditorFragment.newInstance(fileToEdit)); }2.1 内部存储Context.getFilesDir()路径语义应用私有的内部存储目录通常位于/data/data/包名/files下具体路径因设备而异不应硬编码。可见性对用户和其他应用不可见卸载应用时随之删除。权限无需任何运行时权限应用天然拥有读写权。适用场景敏感数据、缓存以外的私有数据、用户文档等不希望暴露的文件。2.2 外部私有存储Context.getExternalFilesDir()路径语义外部存储通常是 SD 卡或内置共享存储分区上属于当前应用的私有目录典型形态是/storage/emulated/0/Android/data/包名/files/。可见性目录对用户和其他应用不可见但用户通过文件管理器可以访问到整个外部存储。权限从 Android 4.4API 19起无需任何权限即可读写卸载应用时目录随应用删除。适用场景较大的媒体文件、需要在外部存储上但希望随卸载清理的数据。示例传入了null作为子目录参数实际开发中可传Environment.DIRECTORY_MOVIES、Environment.DIRECTORY_PICTURES等分类子目录。2.3 外部公共存储Environment.getExternalStoragePublicDirectory()路径语义外部存储上的公共目录如Documents/、Music/、Pictures/等。示例使用的是Environment.DIRECTORY_DOCUMENTS因此目标文件是Documents/test.txt。可见性用户和其他应用都可以看到。权限写入需要WRITE_EXTERNAL_STORAGE运行时权限API 19 上该权限属于危险权限这也是本示例引入权限请求机制的根源。适用场景用户希望长期保留、可被其他应用读取或通过 USB 拷贝的文档与媒体。注意Environment.getExternalStoragePublicDirectory()在较新 Android 版本中已被标记为 deprecated并建议改用 MediaStore 或getExternalFilesDir()体系本示例的minSdkVersion/targetSdkVersion为 19/27见下文构建配置仍然完整展示了经典的外部公共目录读写范式。三、构建环境与工程配置示例采用 Gradle 构建相关配置可以直接用于复现实验app/build.gradlecompileSdkVersion 27、buildToolsVersion 27.0.3、minSdkVersion 19、targetSdkVersion 27唯一的外部依赖是com.android.support:support-v4:27.1.1用于FragmentActivity、ViewPager、FragmentPagerAdapter等 support-v4 组件。build.gradle根构建脚本使用 Android Gradle Plugin3.1.4仓库源为google()与jcenter()。gradle/wrapper/gradle-wrapper.properties指向 Gradle 4.4-all。按仓库顶层 README 的说明导入前务必检查 wrapper 配置命令行构建需要本机安装 Gradle 3.3。AndroidManifest.xml声明android.permission.WRITE_EXTERNAL_STORAGE权限主题为Theme.Apptheme入口为MainActivity。注意在 API 19minSdkVersion上WRITE_EXTERNAL_STORAGE还属于普通权限安装时自动授予只有在 Android 6.0API 23及以上设备运行时才会真正触发下面的运行时授权流程。四、运行时权限处理AbstractPermissionActivity由于目标文件位于外部公共目录写入前必须获得WRITE_EXTERNAL_STORAGE。示例将权限逻辑封装成可复用的抽象基类 AbstractPermissionActivity.java设计思路值得借鉴。4.1 抽象契约基类定义三个抽象方法子类只需关心要什么权限和授权后干什么abstract protected String[] getDesiredPermissions(); abstract protected void onPermissionDenied(); abstract protected void onReady(Bundle state);MainActivity的实现非常精简public class MainActivity extends AbstractPermissionActivity { Override protected String[] getDesiredPermissions() { return(new String[]{WRITE_EXTERNAL_STORAGE}); } Override protected void onPermissionDenied() { Toast .makeText(this, R.string.msg_sorry, Toast.LENGTH_LONG) .show(); finish(); } Override protected void onReady(Bundle savedInstanceState) { setContentView(R.layout.main); // ... 初始化 ViewPager } }权限被拒绝时弹出 strings.xml 中定义的msg_sorrySorry, but we need permission to continue!并直接finish()退出。4.2 流程细节onCreate()中的决策分支若hasAllPermissions(getDesiredPermissions())为真直接onReady(state)否则若当前未处于权限请求中!isInPermission则用ActivityCompat.requestPermissions()发起请求请求码固定为REQUEST_PERMISSION61125。netPermissions()会过滤掉已经授予的权限只请求缺失项onSaveInstanceState()中通过STATE_IN_PERMISSION保存请求状态避免旋转屏幕等配置变更导致重复弹窗回调onRequestPermissionsResult()中再次检查全部授予则进入onReady否则走onPermissionDenied()。这套模式可以直接迁移到任何需要危险权限的 Activity 中。五、UI 骨架ViewPager 三标签页主界面布局 main.xml 非常简洁——一个带PagerTabStrip的ViewPagerandroid.support.v4.view.ViewPager android:idid/pager android:layout_widthmatch_parent android:layout_heightmatch_parent android.support.v4.view.PagerTabStrip android:layout_widthmatch_parent android:layout_heightwrap_content android:layout_gravitytop/ /android.support.v4.view.ViewPager三个标签页的标题来自 strings.xml 中的internal、external、pub分别渲染为 Internal、External、Public由SampleAdapter.getPageTitle()返回。这样用户只需左右滑动即可在同一编辑体验下对比三个存储位置的行为差异。编辑区布局 editor.xml 是一个全屏多行EditTextEditText android:idid/editor android:layout_widthmatch_parent android:layout_heightmatch_parent android:inputTypetextMultiLine android:gravityleft|top/textMultiLine允许多行输入gravityleft|top让光标从左上角开始——这是文本编辑器类 UI 的基础配置。六、EditorFragment异步读、后台写、生命周期联动EditorFragment.java 是整个示例的核心集中体现了文件 I/O 的正确姿势绝不在 UI 线程读写文件。6.1 通过 arguments 传递目标文件Fragment 通过静态工厂方法newInstance(File)创建目标File以Serializable形式放入BundleKEY_FILE并在onViewCreated中取出。同时setRetainInstance(true)使 Fragment 在配置变更时保留实例配合loaded标志避免旋转屏幕后重复加载。6.2 异步读取LoadTextTask读取操作放在AsyncTask的doInBackground()中并显式指定线程池执行器AsyncTask.THREAD_POOL_EXECUTOR而非默认的串行执行器避免多个标签页加载互相排队private class LoadTextTask extends AsyncTaskFile, Void, String { Override protected String doInBackground(File... files) { String resultnull; if (files[0].exists()) { BufferedReader br; try { brnew BufferedReader(new FileReader(files[0])); try { StringBuilder sbnew StringBuilder(); String linebr.readLine(); while (line!null) { sb.append(line); sb.append(\n); linebr.readLine(); } resultsb.toString(); } finally { br.close(); } } catch (IOException e) { Log.e(getClass().getSimpleName(), Exception reading file, e); } } return(result); } ... }要点FileReader以系统默认字符集读取文本readLine()逐行拼接并补回换行符用finally保证流关闭文件不存在时返回nullonPostExecute直接以空内容进入编辑态。onDestroy()中调用loadTask.cancel(false)做清理。6.3 自动保存SaveThread保存策略是onPause 即保存用户一离开页面就基于当前EditText内容启动一个独立Thread执行写盘避免 Activity 不可见后数据丢失Override public void onPause() { if (loaded) { new SaveThread(editor.getText().toString(), (File)getArguments().getSerializable(KEY_FILE)).start(); } super.onPause(); }SaveThread的写入实现有两点值得学习private static class SaveThread extends Thread { Override public void run() { try { fileToEdit.getParentFile().mkdirs(); // 确保父目录存在 FileOutputStream fosnew FileOutputStream(fileToEdit); Writer wnew BufferedWriter(new OutputStreamWriter(fos)); try { w.write(text); w.flush(); fos.getFD().sync(); // 强制数据落盘 } finally { w.close(); } } catch (IOException e) { Log.e(getClass().getSimpleName(), Exception writing file, e); } } }getParentFile().mkdirs()先创建父目录防止因目录不存在导致写入失败——对Documents/test.txt这类多级路径尤其关键OutputStreamWriterBufferedWriter把String编码为字节写入缓冲减少磁盘 I/O 次数fos.getFD().sync()把文件描述符的数据强制同步到物理存储。这一调用对崩溃恢复、电源中断等场景下的数据完整性非常重要普通close()并不保证立即落盘。从源码看整个写盘链路是Java 流 → 文件描述符 → 内核sync()处于最末端是数据持久化兜底。6.4 布局与主题的版本适配styles.xml 定义Theme.Apptheme继承Theme.Holo而 values-v21/styles.xml 中同名主题在 API 21 上继承Theme.Material并配置 colors.xml 中的主色#3f51b5、深主色#1a237e与强调色#ffee58。这是同一资源名、多配置限定符实现主题分级降级的典型写法与文件存储 API 的版本差异处理思路一脉相承。七、各文件职责一览与可复用结论文件仓库相对路径职责MainActivity.java声明所需权限授权后搭建 ViewPager UIAbstractPermissionActivity.java可复用的运行时权限请求基类SampleAdapter.java定义三个存储位置与对应FileEditorFragment.java异步读、后台写、onPause 自动保存AndroidManifest.xml声明WRITE_EXTERNAL_STORAGE权限与入口main.xml、editor.xml标签页容器与编辑区布局strings.xml页面标题与拒绝提示文案app/build.gradleSDK 版本与 support-v4 依赖实践要点归纳位置选择决定权限内部存储与外部应用私有目录无需权限外部公共目录写入必须申请WRITE_EXTERNAL_STORAGE。读写务必脱离 UI 线程读用AsyncTask配合THREAD_POOL_EXECUTOR避免排队写用独立线程。保存时机与数据安全onPause自动保存可防止数据丢失写文件先mkdirs()建父目录最后fd.sync()保证落盘。权限流程可抽象复用把请求权限 → 回调重查 → onReady/onDenied封装成抽象基类业务 Activity 只保留声明式代码。File 对象可序列化传递通过 FragmentsetArguments()传递Serializable的File是配置变更后恢复编辑目标的轻量方案。需要提醒的是该示例基于 2018 年前后的 SDK 27 与 support-v4 构建针对现代 Android高版本targetSdkVersion、分区存储、FileProvider等需要相应适配但它所展示的存储 API 语义、异步 I/O 与生命周期结合的工程模式至今仍是 Android 文件处理的基础知识。赞分享示例工程教程【免费下载链接】cw-omnibusSource code to omnibus edition of _The Busy Coders Guide to Android Development_项目地址https://gitcode.com/gh_mirrors/cw/cw-omnibus点击查看免费下载相关推荐Android 文件存储实战使用 File API 在内部存储与外部存储中保存数据Android 文件存储实战使用 File API 在内部存储与外部存储中保存数据 本指南聚焦于 Android 官方培训课程《数据保存》系列中的文件存储主题文档教程移动开发Tasmota外部存储使用SD卡文件读写与数据日志存储实现Tasmota外部存储使用SD卡文件读写与数据日志存储实现 引言解决物联网设备的数据存储痛点 你是否还在为Tasmota设备无法持久化存储传感器数据而烦恼嵌入式物联网固件智能家居Termux数据存储内部存储和外部SD卡访问Termux数据存储内部存储和外部SD卡访问 引言 你是否曾在Android设备上使用Termux终端时为文件存储位置感到困惑是否想知道如何安全地管理应用移动开发CLI操作系统上一篇Windows 11终极优化指南用Win11Debloat让你的电脑快51%下一篇IDM激活脚本终极指南免费解锁30天试用期的完整方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Ambari Agent中ComponentStatusExecutor原理与实战解析

Ambari Agent中ComponentStatusExecutor原理与实战解析

1. 项目概述:一个被低估的“健康哨兵”如何默默守护集群心跳在Hadoop生态里,Ambari从来不是最耀眼的那个——它不处理PB级数据,不调度千万级任务,也不优化Shuffle性能。但它干了一件更基础、更关键的事:让整个集群的运…

2026/10/15 20:39:15 阅读更多 →
Java后端转正述职材料指南:PPT结构、数据量化与排练方法

Java后端转正述职材料指南:PPT结构、数据量化与排练方法

简介:这份Java后端转正述职PPT及配套演讲稿,面向处于试用期转正节点的后端开发工程师,帮助其系统梳理工作成果、技术成长与职业规划,解决述职材料结构松散、重点不突出的问题。资源包共1个pptx文件,约5.29MB&#xff0…

2026/10/15 20:39:15 阅读更多 →
2核2G云服务器跑Docker够用吗?资源账单与调优实战

2核2G云服务器跑Docker够用吗?资源账单与调优实战

先说结论:阿里云2核2G的云服务器跑Docker,完全够用,但“够用”是有边界的。你能跑起来一批轻量级容器,也能跑一个开发或中小流量项目的核心服务栈,但别指望在一台2G内存的机器上同时扛数据库、一堆Java应用和几十个并发…

2026/10/15 20:38:14 阅读更多 →

最新新闻

【SPIE出版 | 广东·广州 | 广东第二师范学院主办】2026年绿色能源与光电材料国际学术会议(ICGEOM 2026)

【SPIE出版 | 广东·广州 | 广东第二师范学院主办】2026年绿色能源与光电材料国际学术会议(ICGEOM 2026)

2026年绿色能源与光电材料国际学术会议(ICGEOM 2026)暨2026年微纳能源与光电子器件学术会议 2026 International Conference on Green Energy and Optoelectronic Materials 2026年绿色能源与光电材料国际学术会议(ICGEOM 2026)…

2026/10/15 21:30:47 阅读更多 →
MFC GDI+ 图层控件实现:解决透明叠加与交互渲染难题

MFC GDI+ 图层控件实现:解决透明叠加与交互渲染难题

简介:本资源是一套基于VS2010 MFC平台的GDI图层控件开发实践项目,面向C桌面图形开发初学者与中级工程师,解决传统MFC绘图缺乏图层管理、难以实现分层绘制与混合显示的核心痛点。项目通过封装多图层Bitmap与Graphics对象,提供图层创…

2026/10/15 21:30:47 阅读更多 →
Linux网卡驱动r8168源码编译与DKMS实战指南

Linux网卡驱动r8168源码编译与DKMS实战指南

简介:这份资源面向在 CentOS 7(内核 3.10)环境下需要为 Realtek 网卡安装 r8168 驱动的运维与 Linux 学习者,重点解决系统自带 r8169 驱动兼容性不佳、网卡无法正常识别或联网的问题。压缩包共 40 个文件,约 1.37MB&am…

2026/10/15 21:30:47 阅读更多 →
SAP ABAP DDIC DDL 中 DEFINE STRUCTURE 的 ext_annos 设计,C0 扩展契约下如何控制字段后缀、字段配额与字节容量

SAP ABAP DDIC DDL 中 DEFINE STRUCTURE 的 ext_annos 设计,C0 扩展契约下如何控制字段后缀、字段配额与字节容量

在 ABAP Dictionary 里创建一个 Structure,本身并不是什么新鲜事。传统项目里,我们很熟悉 SE11,也知道 Structure 可以被程序、Function Module、BAPI、数据库表 Include、接口参数乃至整个应用层的数据模型反复引用。 到了 ABAP Cloud、SAP S/4HANA Cloud 和 Clean Core 体…

2026/10/15 21:30:47 阅读更多 →
AI评测独立性:评测员入局后,如何守住可验证的中立边界

AI评测独立性:评测员入局后,如何守住可验证的中立边界

1. 评测员进场之后,独立性为什么反而成了新问题各家AI实验室这两年都在干同一件事:把评测员请进来。不是远程派单、外包打分那种松散合作,而是让评测团队常驻实验室,跟模型研发坐同一个工位、提同一个迭代节奏、甚至共享同一份模型…

2026/10/15 21:30:47 阅读更多 →
第一次接中转站,我会把第一小时安排明白

第一次接中转站,我会把第一小时安排明白

第一次接中转站,我会把第一小时安排明白 拿到一个新入口以后,先翻模型列表,还是先充值?对我来说,这两个动作都可以稍后再做。真正值得安排的是第一小时:先弄清楚自己要解决什么问题,再走完一条能…

2026/10/15 21:29:46 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →