返回

Expo DocumentPicker: Android JSON 选择难题与方案

Android

Expo DocumentPicker在低版本安卓上选择JSON文件的处理方法

使用Expo的DocumentPicker在React Native应用中选择文件很常见。但在一些旧版本的Android系统中,指定type: 'application/json'后,DocumentPicker可能无法正常选择JSON文件,尽管它们在文件管理器中可见。用户期望能够顺畅选择application/json类型的文件,而又不希望出现其它不符合要求的文件类型。本文提供一些可行的解决方案。

问题分析

问题出在低版本安卓系统(例如Android 7、8)对于MIME类型的处理方式与DocumentPicker和操作系统的交互方式上。即使文件后缀名为.json,操作系统也可能无法正确识别其MIME类型为application/json。或者,某些文件管理器可能不会向DocumentPicker暴露所有类型的文件,即使它们理论上匹配所请求的MIME类型。

解决方案

解决思路是降低对MIME类型的要求,允许选择所有文件类型,然后在代码中进行校验,确保用户选择了正确的JSON文件。另一个思路是在低版本 Android 上,直接设置其他通用MIME类型。

方案一:不指定文件类型,手动校验

放弃在DocumentPicker中指定type,允许用户选择任何文件。拿到文件后,在代码中检查文件的MIME类型和内容。

import * as DocumentPicker from 'expo-document-picker';
import * as mime from 'react-native-mime-types';
import { Platform } from 'react-native';

const uploadJsonFile = async () => {
  try {
    const file = await DocumentPicker.getDocumentAsync({
      copyToCacheDirectory: true,
    });

    if (!file.assets) return;

    const mimeType = mime.lookup(file.assets[0].uri);

    if (mimeType === 'application/json') {
      // TODO: 额外安全检查:文件大小限制,内容是否符合JSON格式。
      setUploadedBackup(file.assets);
    } else {
      alert('请选择JSON文件');
    }
  } catch (err) {
    console.log(err);
  }
};

操作步骤:

  1. 引入expo-document-pickerreact-native-mime-types 依赖。
  2. 使用DocumentPicker.getDocumentAsync()时,移除type选项。
  3. 通过react-native-mime-typeslookup() 方法获取选定文件的MIME类型。
  4. 验证MIME类型是否为application/json。 如果验证不通过,给予提示。

注意事项:

  • 需要安装react-native-mime-types: npm install react-native-mime-types或者 yarn add react-native-mime-types
  • 虽然移除了类型限制,但是还是需要在业务代码里面手动校验文件的合法性,避免造成非预期的错误。比如文件名,文件大小,文件内容,以及格式是否符合要求等。

方案二:指定备选 MIME 类型

针对 Android 版本进行判断,当版本低于特定值时,使用 application/octet-stream或其他通用的 MIME 类型,而不是 application/json。这样可以让 DocumentPicker 能够选择文件,后续再进行 MIME 类型验证。

import * as DocumentPicker from 'expo-document-picker';
import * as mime from 'react-native-mime-types';
import * as Device from 'expo-device';
import { Platform } from 'react-native';

const uploadJsonFile = async () => {
  const androidVersion = Number(Device.osVersion);
  const fileMimeType = androidVersion < 9 ? 'application/octet-stream' : 'application/json'; // Android 9 是一个可能的分界线

  try {
    const file = await DocumentPicker.getDocumentAsync({
      copyToCacheDirectory: true,
      type: fileMimeType,
    });

    if (!file.assets) return;

    const mimeType = mime.lookup(file.assets[0].uri);

    if (mimeType === 'application/json') {
      setUploadedBackup(file.assets);
    } else {
      alert('请选择JSON文件');
    }
  } catch (err) {
    console.log(err);
  }
};

操作步骤:

  1. 引入expo-device用来获取安卓系统版本. 需要安装 expo-devicenpx expo install expo-device
  2. 检查 Android 系统版本。低于某个版本时(例如Android 9),指定类型为 application/octet-stream
  3. 通过react-native-mime-typeslookup() 方法获取选定文件的MIME类型。
  4. 验证MIME类型是否为application/json。 如果验证不通过,给予提示。

安全建议

  • 始终对用户上传的文件进行服务器端验证,确保文件内容安全,防止恶意代码注入。
  • 限制文件大小,避免资源滥用。
  • 对文件名进行规范化,防止XSS攻击。

这两种方案均能解决在老旧Android设备上使用DocumentPicker选取JSON文件的问题。建议根据具体项目情况进行选择,注意做好错误处理和安全性验证,确保应用的稳定性和安全性。