news 2026/7/25 23:13:35

Android 存储体系:内部存储、SD 卡、U 盘挂载读写、工控文件持久化方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android 存储体系:内部存储、SD 卡、U 盘挂载读写、工控文件持久化方案

Android 存储体系:内部存储、SD 卡、U 盘挂载读写、工控文件持久化方案

工控设备的数据比女朋友还难伺候——丢了不行、路径搞错也不行、U盘没挂载就写更不行。今天就把Android存储体系给你从头捋到脚。

一、Android 存储架构总览

Android的存储体系可以简化成三大块:

存储类型路径特征是否需要权限典型用途
内部存储/data/data/包名/不需要应用私有数据、数据库
外部存储(内部SD)/storage/emulated/0/Android 10+需要共享文件、日志导出
可移动存储(外部SD/U盘)/storage/XXXX-XXXX/需要SAF或StorageManager数据备份、固件升级

小提示:很多人被"外部存储"这个名字坑了。外部存储不是指SD卡——手机自带的那个32GB/128GB存储空间在Android眼里就是"外部存储"(External Storage),它是焊接在主板上的、根本拔不掉的。真正的SD卡叫"可移动存储"(Removable Storage)。

一张图理解它们的关系:

Android 文件系统 ├── 内部存储 (无需权限) │ ├── /data/data/com.example.industrial/ │ │ ├── files/ ← getFilesDir() │ │ ├── databases/ ← SQLite数据库 │ │ ├── shared_prefs/ ← SharedPreferences │ │ └── cache/ ← getCacheDir() │ └── /data/app/ ← APK安装位置 ├── 外部存储 (内部SD,需权限) │ ├── /storage/emulated/0/ │ │ ├── Download/ │ │ ├── DCIM/ │ │ └── Android/data/com.example.industrial/ │ │ ├── files/ ← getExternalFilesDir() │ │ └── cache/ ← getExternalCacheDir() └── 可移动存储 (U盘/SD卡) └── /storage/XXXX-XXXX/ ← 需要StorageManager枚举

二、内部存储读写

内部存储是应用专属的沙盒目录,其他APP无法访问,也不需要任何权限。

2.1 常用路径获取方法

// 1. 应用私有文件目录 - 推荐存放配置文件FilefilesDir=context.getFilesDir();// /data/user/0/com.example.industrial/files/// 2. 缓存目录 - 系统空间不足时会自动清理FilecacheDir=context.getCacheDir();// /data/user/0/com.example.industrial/cache/// 3. 数据库默认目录FiledatabaseDir=context.getDatabasePath("my_db");// /data/user/0/com.example.industrial/databases/my_db// 4. SharedPreferences目录// /data/user/0/com.example.industrial/shared_prefs/

2.2 配置文件读写

工控场景下,设备参数(采集频率、串口波特率、服务器地址等)需要持久化存储。推荐方案:

publicclassConfigManager{privatestaticfinalStringCONFIG_FILE="industrial_config.json";// 存储到应用私有目录,安全且不需要权限publicstaticvoidsaveConfig(Contextcontext,Stringjson){FileconfigFile=newFile(context.getFilesDir(),CONFIG_FILE);try(FileWriterwriter=newFileWriter(configFile)){writer.write(json);}catch(IOExceptione){e.printStackTrace();}}publicstaticStringloadConfig(Contextcontext){FileconfigFile=newFile(context.getFilesDir(),CONFIG_FILE);if(!configFile.exists())returnnull;try{returnnewString(Files.readAllBytes(configFile.toPath()));}catch(IOExceptione){returnnull;}}}

为什么用getFilesDir()而不是外部存储?因为工控设备可能没有外置SD卡,而且应用私有目录不需要权限、其他APP访问不了,数据更安全。

2.3 采集数据的存储策略

对于大量传感器采集数据,SQLite是最成熟的方案:

publicclassDataRepository{privateSQLiteDatabasedatabase;publicDataRepository(Contextcontext){DataDbHelperhelper=newDataDbHelper(context.getFilesDir().getAbsolutePath());database=helper.getWritableDatabase();}publicvoidinsertSensorData(SensorDatadata){ContentValuesvalues=newContentValues();values.put("temperature",data.temperature);values.put("humidity",data.humidity);values.put("timestamp",System.currentTimeMillis());database.insert("sensor_data",null,values);}// 定期清理旧数据,防止存储爆满publicvoidcleanupOldData(longbeforeTimestamp){database.delete("sensor_data","timestamp < ?",newString[]{String.valueOf(beforeTimestamp)});}}

内部存储虽然安全,但容量有限(通常几个GB)。对于长期运行且数据量大的工控设备,需要定期清理或转存到外部存储。

三、外部存储读写

3.1 公有目录与私有目录

// 公有目录 - 需要权限,所有APP都能访问FiledownloadDir=Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);// /storage/emulated/0/Download/// 应用私有外部目录 - Android 10+不需要权限,但需要用到分区存储APIFileexternalFilesDir=context.getExternalFilesDir(null);// /storage/emulated/0/Android/data/包名/files/// 应用外部缓存目录FileexternalCacheDir=context.getExternalCacheDir();// /storage/emulated/0/Android/data/包名/cache/

在Android 10(API 29)之前,直接使用Environment.getExternalStorageDirectory()获取根目录就行。但从Android 10开始引入分区存储(Scoped Storage),强制应用使用MediaStore API或SAF框架访问共享文件。

工控设备如果跑的是Android 11+,需要在Manifest中声明:

<uses-permissionandroid:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>

然后在代码中引导用户授权:

if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.R){if(!Environment.isExternalStorageManager()){Intentintent=newIntent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);startActivity(intent);}}

3.2 日志文件导出到外部存储

这是工控很常见的需求——设备出了问题,需要把日志导出来查看:

publicstaticvoidexportLogs(Contextcontext){FilelogDir=newFile(context.getExternalFilesDir(null),"logs");if(!logDir.exists())logDir.mkdirs();FilelogFile=newFile(logDir,"app_"+newSimpleDateFormat("yyyyMMdd_HHmmss",Locale.US).format(newDate())+".log");try(FileWriterwriter=newFileWriter(logFile)){writer.write("=== 工控APP运行日志 ===\n");writer.write("版本: "+BuildConfig.VERSION_NAME+"\n");writer.write("设备: "+Build.MODEL+"\n");writer.write("SDK: "+Build.VERSION.SDK_INT+"\n");// 写入更多日志内容...}catch(IOExceptione){e.printStackTrace();}}

四、U盘挂载检测与读写

工控设备经常通过U盘做数据导入导出或固件升级,这是工控和普通手机APP需求差异最大的地方。

4.1 U盘挂载检测

U盘插入后,Android系统会自动挂载到/storage/下的某个目录(通常是/storage/XXXX-XXXX/格式)。我们需要通过StorageManager来获取挂载信息:

publicclassUsbStorageManager{privateStorageManagerstorageManager;publicUsbStorageManager(Contextcontext){storageManager=(StorageManager)context.getSystemService(Context.STORAGE_SERVICE);}/** * 获取所有可移动存储设备的路径 */publicList<File>getRemovableVolumes(){List<File>volumes=newArrayList<>();if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){List<StorageVolume>storageVolumes=storageManager.getStorageVolumes();for(StorageVolumevolume:storageVolumes){// 判断是否可移动存储if(volume.isRemovable()&&volume.getState().equals(Environment.MEDIA_MOUNTED)){FilevolumeDir;if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.R){volumeDir=volume.getDirectory();}else{// Android 10及以下用反射获取路径volumeDir=getVolumePathReflect(volume);}if(volumeDir!=null){volumes.add(volumeDir);}}}}// 兼容性方案:直接扫描 /storage/FilestorageDir=newFile("/storage/");File[]files=storageDir.listFiles();if(files!=null){for(Filefile:files){Stringname=file.getName();// U盘挂载点通常是 XXXX-XXXX 格式if(!name.equals("emulated")&&!name.equals("self")&&file.canRead()&&file.listFiles()!=null){if(!volumes.contains(file)){volumes.add(file);}}}}returnvolumes;}@SuppressWarnings("JavaReflectionMemberAccess")privateFilegetVolumePathReflect(StorageVolumevolume){try{Class<?>clazz=volume.getClass();Methodmethod;if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){method=clazz.getMethod("getPathFile");}else{method=clazz.getMethod("getPath");}return(File)method.invoke(volume);}catch(Exceptione){returnnull;}}}

4.2 U盘文件读写

有了路径就可以用标准Java File API读写U盘了。下面是一个完整的文件读写工具类:

publicclassUsbFileManager{/** * 从U盘读取升级文件 */publicstaticFilefindUpgradeFile(FileusbRoot){File[]files=usbRoot.listFiles();if(files==null)returnnull;for(Filefile:files){if(file.getName().toLowerCase().endsWith(".apk")||file.getName().toLowerCase().endsWith(".zip")){returnfile;}}returnnull;}/** * 将采集数据导出到U盘 */publicstaticbooleanexportDataToUsb(FileusbRoot,StringfileName,byte[]data){FileexportDir=newFile(usbRoot,"IndustrialData");if(!exportDir.exists()){exportDir.mkdirs();}FileexportFile=newFile(exportDir,fileName);try(FileOutputStreamfos=newFileOutputStream(exportFile)){fos.write(data);fos.flush();returntrue;}catch(IOExceptione){e.printStackTrace();returnfalse;}}/** * 安全弹出U盘(卸载文件系统) */publicstaticbooleanunmountUsb(StorageManagerstorageManager,StorageVolumevolume){try{// 通过Intent请求卸载(兼容方案)// 实际卸载需要系统签名,普通APP只能通知用户拔出returntrue;}catch(Exceptione){returnfalse;}}}

4.3 使用 Storage Access Framework(SAF)

Android 4.4 引入了 SAF 框架,可以不申请存储权限就访问外部存储:

publicclassSafHelper{privatestaticfinalintREQUEST_CODE_OPEN_DIR=2001;publicstaticvoidopenDirectory(Activityactivity){Intentintent=newIntent(Intent.ACTION_OPEN_DOCUMENT_TREE);intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION|Intent.FLAG_GRANT_WRITE_URI_PERMISSION|Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);activity.startActivityForResult(intent,REQUEST_CODE_OPEN_DIR);}publicstaticUrihandleResult(intrequestCode,intresultCode,Intentdata){if(requestCode==REQUEST_CODE_OPEN_DIR&&resultCode==Activity.RESULT_OK){UritreeUri=data.getData();// 持久化权限,下次启动还能用inttakeFlags=data.getFlags()&(Intent.FLAG_GRANT_READ_URI_PERMISSION|Intent.FLAG_GRANT_WRITE_URI_PERMISSION);returntreeUri;}returnnull;}}

对于工控场景,SAF的缺点是每次都要用户手动选择目录,不适合无人值守模式。建议:如果设备跑Android 10以上就用分区存储API,如果跑低版本直接走文件路径更省事。

五、工控文件持久化方案选择

数据类型推荐方案存储位置理由
配置参数SharedPreferences / JSON文件getFilesDir()数据量小,读写频繁
采集数据SQLite数据库getDatabasePath()支持索引、事务、条件查询
图片/视频文件系统getExternalFilesDir()文件大,需定期清理
日志文件文件系统getFilesDir()+ 定期导出私有目录安全,导出到U盘
固件升级包文件系统U盘 / 私有目录从U盘读取,校验后复制到内部

路径获取方法速查表

// ========== 内部存储 ==========context.getFilesDir();// 应用私有文件context.getCacheDir();// 应用缓存context.getDatabasePath("name");// 数据库路径context.getDir("name",MODE_PRIVATE);// 自定义目录Environment.getDataDirectory();// /data// ========== 外部存储(内部SD)==========Environment.getExternalStorageDirectory();// 根目录(Android 10+受限)context.getExternalFilesDir(null);// 应用专属外部目录context.getExternalCacheDir();// 应用外部缓存Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);// ========== 可移动存储 ==========// 通过StorageManager枚举,无标准API

六、完整代码示例:U盘数据采集与导出

最后给出一个工控场景的完整示例——检测U盘插入后自动导出当天的采集数据:

publicclassUsbDataExporterextendsBroadcastReceiver{@OverridepublicvoidonReceive(Contextcontext,Intentintent){if(!UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())){return;}// 延迟2秒等待挂载完成newHandler(Looper.getMainLooper()).postDelayed(()->{exportTodayData(context);},2000);}privatevoidexportTodayData(Contextcontext){StorageManagersm=(StorageManager)context.getSystemService(Context.STORAGE_SERVICE);// 枚举可移动存储List<File>usbVolumes=newUsbStorageManager(context).getRemovableVolumes();if(usbVolumes.isEmpty()){Log.w("USB","未检测到可移动存储设备");return;}FileusbRoot=usbVolumes.get(0);StringtodayStr=newSimpleDateFormat("yyyyMMdd",Locale.US).format(newDate());// 从数据库导出今天的采集数据List<SensorData>todayData=queryTodayDataFromDb(context);StringBuildercsvBuilder=newStringBuilder();csvBuilder.append("时间,温度,湿度,压力\n");for(SensorDatadata:todayData){csvBuilder.append(data.toCsvLine()).append("\n");}// 写入U盘booleansuccess=UsbFileManager.exportDataToUsb(usbRoot,"采集数据_"+todayStr+".csv",csvBuilder.toString().getBytes(StandardCharsets.UTF_8));Log.i("USB","数据导出"+(success?"成功":"失败"));}}

Android存储看着复杂,其实核心就三句话:私有的放getFilesDir()、共享的放getExternalFilesDir()、U盘走 StorageManager 枚举路径。记住了这三句话,工控文件管理就不怕迷路了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/25 23:12:39

Apex英雄7月更新后启动错误与性能问题完整解决指南

这次我们来看一个专门解决 Apex 英雄游戏问题的实用指南。如果你在 7 月更新后遇到了进不去游戏、掉帧、停止服务关不掉、启动错误、卡顿、EA 服务器连接失败、反作弊未运行、闪退崩溃、跳帧抽帧等问题&#xff0c;这篇文章可以直接收藏备用。Apex 英雄作为一款热门的免费大逃杀…

作者头像 李华
网站建设 2026/7/25 23:11:58

涂胶显影机(Track)经理级工程师完整JD(12维度)+ 对外简化版JD

一、完整版内部JD&#xff08;定级定岗、薪酬核算、晋升评审、内部权责手册&#xff09;1. 对标职级半导体设备技术序列中层核心管理岗&#xff0c;承上启下关键职级。行业对标&#xff1a;大厂P8、研发经理/项目经理、部门负责人&#xff1b;高于组长&#xff08;P6/P7&#x…

作者头像 李华
网站建设 2026/7/25 23:07:39

2024年Codex实战指南:从API接入到VSCode集成,避开新手三大坑

这次我们来看一个关于 Codex 的深度入门教程。如果你在搜索“Codex 安装”、“Codex 使用教程”或“Codex 是什么软件”时&#xff0c;被各种零散、过时甚至相互矛盾的信息搞得一头雾水&#xff0c;那么这篇文章就是为你准备的。Codex 作为 OpenAI 推出的强大代码生成模型&…

作者头像 李华
网站建设 2026/7/25 23:07:08

三轴机械模组整机设计实战:从CAD建模到工程图输出的完整流程

这次我们来看一个针对XYZ轴机械模组的整机设计建模教程。这个项目不是某个具体的软件或模型&#xff0c;而是一套聚焦于实战的机械设计方法论与流程讲解。它的核心价值在于&#xff0c;摒弃了冗长的理论铺垫&#xff0c;直接切入如何使用主流CAD软件&#xff08;如SolidWorks、…

作者头像 李华
网站建设 2026/7/25 23:03:18

如何在WPS Office中无缝集成Zotero:5分钟实现学术写作效率革命

如何在WPS Office中无缝集成Zotero&#xff1a;5分钟实现学术写作效率革命 【免费下载链接】WPS-Zotero An add-on for WPS Writer to integrate with Zotero. 项目地址: https://gitcode.com/gh_mirrors/wp/WPS-Zotero 还在为学术写作中的文献引用而烦恼吗&#xff1f;…

作者头像 李华