当前位置: 首页 > news >正文

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 枚举路径。记住了这三句话,工控文件管理就不怕迷路了。

http://www.cnnetsun.cn/news/3649792.html

相关文章:

  • Apex英雄7月更新后启动错误与性能问题完整解决指南
  • 涂胶显影机(Track)经理级工程师完整JD(12维度)+ 对外简化版JD
  • 2024年Codex实战指南:从API接入到VSCode集成,避开新手三大坑
  • 三轴机械模组整机设计实战:从CAD建模到工程图输出的完整流程
  • 如何在WPS Office中无缝集成Zotero:5分钟实现学术写作效率革命
  • AI数据清洗效率提升300%的7个秘密:从脏数据到高质量训练集的实战路径
  • Claude Code Skill 完整指南:从概念到实战创建自定义开发技能
  • Jellium Desktop错误恢复日志:查看恢复过程记录
  • 一文读懂MARS框架:为什么方差 reduction 是大模型训练的关键?
  • 计算机Python毕设实战-基于 Python Web 的学生日常考勤信息系统 班级学生考勤登记与异常报备系统设计【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • LZHAM新手入门:从安装到压缩第一个文件的完整教程
  • Kimi K3编程实力远超GLM5.2:一个Trae复赛证据
  • 解锁SwiftUI 5新特性:Metal Shader Collection中的滚动增强与视觉效果
  • 【管理科学】第五十六篇 企业管理层的权-责-利益分析及权利-人性-资源限制分析01
  • Netty在HuLa-Server中的应用:高性能WebSocket连接管理与消息推送
  • 5分钟掌握OBS专业虚拟背景:零绿幕AI抠图完全指南
  • Buzz项目管理:高效管理平台开发的方法与工具
  • Java后端面试7天冲刺:从八股文到实战的系统复习指南
  • Qwest兼容性处理:IE8+与现代浏览器适配方案
  • 从AI高考数学148分看大模型推理能力:原理、瓶颈与编程实战指南
  • n8n与RAG技术在钉钉机器人中的智能客服应用
  • GitHub_Trending/cla/claude-skills异常检测技能:识别系统异常行为的终极指南
  • AI如何变革学术专著创作:工具链与效率提升实战
  • 孪生网络原理与应用:从相似性度量到工业实践
  • PyOfficeRobot定时群发攻略:解放双手的微信营销神器
  • Node.js网站下载器环境配置与实战指南
  • TkinterMapView核心功能详解:标记、多边形与路径绘制的完整教程
  • Splunk Attack Data高级技巧:选择性拉取数据集节省90%存储空间
  • gh_mirrors/core109/core安全最佳实践:保护用户数据与API接口的终极指南
  • SpatialClaw代码接口:空间智能体的精确控制与工程实践