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

📅 2026/7/25 23:13:36
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. 应用私有文件目录 - 推荐存放配置文件FilefilesDircontext.getFilesDir();// /data/user/0/com.example.industrial/files/// 2. 缓存目录 - 系统空间不足时会自动清理FilecacheDircontext.getCacheDir();// /data/user/0/com.example.industrial/cache/// 3. 数据库默认目录FiledatabaseDircontext.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_FILEindustrial_config.json;// 存储到应用私有目录安全且不需要权限publicstaticvoidsaveConfig(Contextcontext,Stringjson){FileconfigFilenewFile(context.getFilesDir(),CONFIG_FILE);try(FileWriterwriternewFileWriter(configFile)){writer.write(json);}catch(IOExceptione){e.printStackTrace();}}publicstaticStringloadConfig(Contextcontext){FileconfigFilenewFile(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){DataDbHelperhelpernewDataDbHelper(context.getFilesDir().getAbsolutePath());databasehelper.getWritableDatabase();}publicvoidinsertSensorData(SensorDatadata){ContentValuesvaluesnewContentValues();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都能访问FiledownloadDirEnvironment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);// /storage/emulated/0/Download/// 应用私有外部目录 - Android 10不需要权限但需要用到分区存储APIFileexternalFilesDircontext.getExternalFilesDir(null);// /storage/emulated/0/Android/data/包名/files/// 应用外部缓存目录FileexternalCacheDircontext.getExternalCacheDir();// /storage/emulated/0/Android/data/包名/cache/在Android 10API 29之前直接使用Environment.getExternalStorageDirectory()获取根目录就行。但从Android 10开始引入分区存储Scoped Storage强制应用使用MediaStore API或SAF框架访问共享文件。工控设备如果跑的是Android 11需要在Manifest中声明uses-permissionandroid:nameandroid.permission.MANAGE_EXTERNAL_STORAGE/然后在代码中引导用户授权if(Build.VERSION.SDK_INTBuild.VERSION_CODES.R){if(!Environment.isExternalStorageManager()){IntentintentnewIntent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);startActivity(intent);}}3.2 日志文件导出到外部存储这是工控很常见的需求——设备出了问题需要把日志导出来查看publicstaticvoidexportLogs(Contextcontext){FilelogDirnewFile(context.getExternalFilesDir(null),logs);if(!logDir.exists())logDir.mkdirs();FilelogFilenewFile(logDir,app_newSimpleDateFormat(yyyyMMdd_HHmmss,Locale.US).format(newDate()).log);try(FileWriterwriternewFileWriter(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);}/** * 获取所有可移动存储设备的路径 */publicListFilegetRemovableVolumes(){ListFilevolumesnewArrayList();if(Build.VERSION.SDK_INTBuild.VERSION_CODES.N){ListStorageVolumestorageVolumesstorageManager.getStorageVolumes();for(StorageVolumevolume:storageVolumes){// 判断是否可移动存储if(volume.isRemovable()volume.getState().equals(Environment.MEDIA_MOUNTED)){FilevolumeDir;if(Build.VERSION.SDK_INTBuild.VERSION_CODES.R){volumeDirvolume.getDirectory();}else{// Android 10及以下用反射获取路径volumeDirgetVolumePathReflect(volume);}if(volumeDir!null){volumes.add(volumeDir);}}}}// 兼容性方案直接扫描 /storage/FilestorageDirnewFile(/storage/);File[]filesstorageDir.listFiles();if(files!null){for(Filefile:files){Stringnamefile.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?clazzvolume.getClass();Methodmethod;if(Build.VERSION.SDK_INTBuild.VERSION_CODES.N){methodclazz.getMethod(getPathFile);}else{methodclazz.getMethod(getPath);}return(File)method.invoke(volume);}catch(Exceptione){returnnull;}}}4.2 U盘文件读写有了路径就可以用标准Java File API读写U盘了。下面是一个完整的文件读写工具类publicclassUsbFileManager{/** * 从U盘读取升级文件 */publicstaticFilefindUpgradeFile(FileusbRoot){File[]filesusbRoot.listFiles();if(filesnull)returnnull;for(Filefile:files){if(file.getName().toLowerCase().endsWith(.apk)||file.getName().toLowerCase().endsWith(.zip)){returnfile;}}returnnull;}/** * 将采集数据导出到U盘 */publicstaticbooleanexportDataToUsb(FileusbRoot,StringfileName,byte[]data){FileexportDirnewFile(usbRoot,IndustrialData);if(!exportDir.exists()){exportDir.mkdirs();}FileexportFilenewFile(exportDir,fileName);try(FileOutputStreamfosnewFileOutputStream(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 FrameworkSAFAndroid 4.4 引入了 SAF 框架可以不申请存储权限就访问外部存储publicclassSafHelper{privatestaticfinalintREQUEST_CODE_OPEN_DIR2001;publicstaticvoidopenDirectory(Activityactivity){IntentintentnewIntent(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(requestCodeREQUEST_CODE_OPEN_DIRresultCodeActivity.RESULT_OK){UritreeUridata.getData();// 持久化权限下次启动还能用inttakeFlagsdata.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// 外部存储内部SDEnvironment.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);// 枚举可移动存储ListFileusbVolumesnewUsbStorageManager(context).getRemovableVolumes();if(usbVolumes.isEmpty()){Log.w(USB,未检测到可移动存储设备);return;}FileusbRootusbVolumes.get(0);StringtodayStrnewSimpleDateFormat(yyyyMMdd,Locale.US).format(newDate());// 从数据库导出今天的采集数据ListSensorDatatodayDataqueryTodayDataFromDb(context);StringBuildercsvBuildernewStringBuilder();csvBuilder.append(时间,温度,湿度,压力\n);for(SensorDatadata:todayData){csvBuilder.append(data.toCsvLine()).append(\n);}// 写入U盘booleansuccessUsbFileManager.exportDataToUsb(usbRoot,采集数据_todayStr.csv,csvBuilder.toString().getBytes(StandardCharsets.UTF_8));Log.i(USB,数据导出(success?成功:失败));}}Android存储看着复杂其实核心就三句话私有的放getFilesDir()、共享的放getExternalFilesDir()、U盘走 StorageManager 枚举路径。记住了这三句话工控文件管理就不怕迷路了。