一、分区存储来了,传感器数据文件怎么办?

Android 10 开始,Google 搞了个大动作——分区存储(Scoped Storage)。

说白了,就是不让 App 随便乱翻别人的文件夹了。以前那种直接往 /sdcard 根目录写文件的日子,一去不复返。

我刚开始接触这个变化时,心里也犯嘀咕:传感器数据文件,比如光线传感器的日志、校准参数,这些该往哪放?

嗯,今天我们就来聊聊这个。

1.1 分区存储到底改了啥?

先看一张图,帮你快速理解核心变化:

Android 9 及以下 App 可访问整个共享存储 READ_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE 传感器数据可随意读写 Android 10+ 分区存储 App 只能访问自己的沙箱 App 专属目录 MediaStore 访问媒体 SAF 文件选择器

你看,左边是旧模式,App 权限很大。右边是新模式,App 被圈在自己的「沙箱」里。

对于传感器数据来说,这意味着什么?

  • 不能再往公共目录乱写文件了——比如 /sdcard/sensor_log.txt,系统会直接拒绝。
  • 但 App 自己的私有目录不受影响——context.getExternalFilesDir() 依然可以正常读写。
  • 如果非要共享数据——得用 MediaStore 或者 SAF(存储访问框架)。

1.2 传感器数据文件的典型场景

我在项目中遇到过这样一个需求:App 需要每隔 100ms 采集一次光线传感器数据,保存到 CSV 文件,方便后续分析。

在 Android 10 之前,代码大概是这样的:

// ❌ 旧方式,Android 10+ 会报错
File file = new File(Environment.getExternalStorageDirectory(), "light_sensor.csv");
FileOutputStream fos = new FileOutputStream(file);
// 写入数据...
fos.close();

这段代码在 Android 10 以上会抛出 FileNotFoundException,因为根目录不让写了。

那正确的做法是什么?

核心原则:传感器数据文件,优先放在 App 专属目录。

1.3 适配方案一:使用 App 专属目录

这是我最推荐的方式。简单、稳定、不用申请额外权限。

// ✅ 推荐方式:使用 App 专属外部存储目录
File sensorDir = context.getExternalFilesDir("sensor_data");
if (sensorDir != null && !sensorDir.exists()) {
    sensorDir.mkdirs();
}
File file = new File(sensorDir, "light_sensor_" + System.currentTimeMillis() + ".csv");
FileOutputStream fos = new FileOutputStream(file);
// 写入传感器数据...
fos.close();

你想想看,这个目录对应的是 /sdcard/Android/data/你的包名/files/sensor_data/

好处很明显:

  • 不需要 READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE 权限
  • App 卸载时,系统会自动清理这些文件
  • 其他 App 默认访问不到,数据安全

小提示:我个人习惯在 onCreate 里先检查一下目录是否存在,不存在就创建。避免在写入时才发现目录没了。

1.4 适配方案二:使用 MediaStore(适合共享场景)

如果传感器数据需要导出给其他 App 使用,比如分享给数据分析工具,那可以用 MediaStore。

// 使用 MediaStore 保存到 Downloads 目录
ContentValues values = new ContentValues();
values.put(MediaStore.Downloads.DISPLAY_NAME, "light_sensor.csv");
values.put(MediaStore.Downloads.MIME_TYPE, "text/csv");
values.put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + "/SensorData");

Uri uri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values);
OutputStream os = contentResolver.openOutputStream(uri);
// 写入传感器数据...
os.close();

这里要注意:

  • Android 10 上,RELATIVE_PATH 是新增的字段,用来指定子目录
  • 不需要 WRITE_EXTERNAL_STORAGE 权限,但需要 WRITE_EXTERNAL_STORAGE 在 Android 10 上已经废弃了
  • 读取自己写入的文件,不需要额外权限

曾经踩过的坑:我在一个项目里用 MediaStore 写文件,结果发现文件名重复时,系统会自动加后缀(比如 "light_sensor (1).csv")。后来我每次写入前先检查一下同名文件是否存在,或者用时间戳命名,避免冲突。

1.5 适配方案三:使用 SAF(存储访问框架)

如果用户想自己选择保存位置,比如保存到某个特定文件夹,那就用 SAF。

// 启动 SAF 文件选择器
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("text/csv");
intent.putExtra(Intent.EXTRA_TITLE, "light_sensor.csv");
startActivityForResult(intent, REQUEST_CODE_SAVE_FILE);

// 在 onActivityResult 中获取 Uri
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_CODE_SAVE_FILE && resultCode == RESULT_OK) {
        Uri uri = data.getData();
        // 通过 ContentResolver 写入数据
        OutputStream os = getContentResolver().openOutputStream(uri);
        // 写入传感器数据...
        os.close();
    }
}

这种方式用户体验最好,但代码稍微复杂一些。适合「导出」功能。

1.6 三种方案对比

方案 适用场景 权限要求 文件可见性 推荐指数
App 专属目录 内部使用,不共享 仅本 App ⭐⭐⭐⭐⭐
MediaStore 需要共享给其他 App 无(Android 10+) 系统媒体库可见 ⭐⭐⭐⭐
SAF 用户自定义保存位置 用户指定位置 ⭐⭐⭐

1.7 关于 Android 11 的补充

Android 11 进一步收紧了权限。如果你还想用 MANAGE_EXTERNAL_STORAGE 权限来绕过分区存储,Google 审核会非常严格。

我的建议是:别折腾了,老老实实用上面三种方案

对于传感器数据这种场景,App 专属目录基本够用。除非你有特别强的理由,否则别去申请 MANAGE_EXTERNAL_STORAGE

一句话总结:Android 10+ 的分区存储,对传感器数据文件的影响就是——别再往公共目录乱写了。用 getExternalFilesDir() 或者 MediaStore,既合规又省心。

好了,关于分区存储对传感器数据文件的影响,就聊到这里。下一节我们聊聊如何在不同 Android 版本上做兼容处理。


公众号:蓝海资料掘金营,微信 deep3321