FilesEditor 源码剖析:Android 内部存储与外部存储的文件读写实战

发布时间:2026/10/15 20:39:09
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),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询