资讯动态

Android+java实现文件选择+导入(读取)+导出(写入),适配安卓高低版本

发布时间:2026/8/27 13:39:31 来源:尧图企业网站定制
1、前言最近做了安卓APP的导入和导出导入需要先引导用户选择文件并读取导出则需要确定文件保存位置和写入。导入做的比较快但导出时遇到了安卓高版本系统限制访问应用的私有数据目录导致导出后文件在文件管理器中找不到可能是视图过滤了某些目录,我的能在文件APP中访问或者访问受限提示只能在电脑上访问无法导入。经过搜索终于解决了这个问题。在此记录下解决的办法。其中文件的读取和写入可以看我上一篇AndoridJava使用Apache POI库实现doc、docx、xls、xlsx的读取和写入-CSDN博客。2026.8.26修导出也可以先引导用户选择文件要保存的位置并写入。SAF(Storage Access Framework)通过Intent.ACTION_OPEN_DOCUMENT(导入/读取或ACTION_CREATE_DOCUMEN导出/保存等动作启动系统提供的文件选择器。用户通过文件管理器访问时文件管理器自带系统级权限相当于“系统组件”在操作不受分区存储Android 10后引入限制。这样就不用传统方式应用在 Manifest 中声明权限系统在安装或运行时询问用户是否授权授权后应用获得整个外部存储的读写权限。高版本导出图片2、导入和导出2.1 配置权限首先确保应用有权限读取和写入外部存储器。在AndroidManifest.xml文件中声明权限//uses-permission android:nameandroid.permission.READ_EXTERNAL_STORAGE/ //uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE/2026.8.26修SAF不需要配置权限2.2 导入2.2.1 启动文件选择器使用Intent启动文件选择器让用户选择文件//REQUEST_CODE_PICK_FILE 是一个自定义的整数常量用于标识 startActivityForResult 方法中的请求, //以便在 onActivityResult 方法中能够区分不同的请求。 private static final int REQUEST_CODE_PICK_FILE 42; // 创建一个Intent来选择文件 private void selectFile() { Intent intent new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); //指定了要创建的文档的 MIME 类型为 text/plain即纯文本文件txt intent.setType(text/plain); startActivityForResult(intent, REQUEST_CODE_PICK_FILE); }2.2.2 处理选择结果重写onActivityResult来处理文件选择结果2026.3.11修补充用户取消文件选择的判断Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); //当文件选择器的结果返回时可以使用 REQUEST_CODE_PICK_FILE 这个请求代码来确认结果是由哪个请求发起的 if (requestCode REQUEST_CODE_PICK_FILE) { if (resultCode RESULT_OK) { Uri uri data.getData(); if (uri ! null) { // 处理选中的文件 handleSelectedFile(uri); } } else if (resultCode RESULT_CANCELED) { // 用户取消操作返回键或点击外部 Log.d(TAG, 用户取消文件选择); } } }若是intent.setType(*/*)即允许选择任何类型的文件时也可以设置只能选择某些类型的文件需要从uri中提取文件名从而进行不同文件类型的读取。由于uri 类型在Android高低版本中类型不同有content://URI和file://URI可以用下面代码来提取文件名。注意判断方法返回值是否为null防止报错空指针异常。2026.3.11修后续发现之前的方法覆盖不够全面在此更新一下//通过Uri获取文件名 public static String getFileName(Context context, Uri uri) { String result null; if (uri.getScheme().equals(content)) { try (Cursor cursor context.getContentResolver().query(uri, null, null, null, null)) { if (cursor ! null cursor.moveToFirst()) { int nameIndex cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); if (nameIndex ! -1) { result cursor.getString(nameIndex); } } } catch (Exception e) { Log.e(TAG, FilePicker获取文件名失败, e); } } if (result null) { result uri.getPath(); if (result ! null) { int cut result.lastIndexOf(/); if (cut ! -1) { result result.substring(cut 1); } } } return result; } // 获取文件扩展名 public static String getFileExtension(String fileName) { if (fileName null) return null; int lastDot fileName.lastIndexOf(.); if (lastDot ! -1 lastDot fileName.length() - 1) { return fileName.substring(lastDot 1).toLowerCase(); } return null; }2.2.3 读取文件内容使用ContentResolver从选中的Uri读取文件内容private void handleSelectedFile(Uri uri) { try { //getContentResolver().openOutputStream(uri): 通过 ContentResolver 获取对所选文件的 OutputStream InputStream inputStream getContentResolver().openInputStream(uri); // 读取文件内容 BufferedReader reader new BufferedReader(new InputStreamReader(inputStream,UTF-8)); String line; StringBuilder stringBuilder new StringBuilder(); while ((line reader.readLine()) ! null) { stringBuilder.append(line); } //关闭输出流 inputStream.close(); // 处理文件内容 String fileContents stringBuilder.toString(); //将文件内容打印到日志上 Log.d(test, fileContents); } catch (FileNotFoundException e) { e.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); } }2.3 导出由于安卓高版本系统限制访问应用的私有数据目录有两种解决方式一、使用设备的文件管理应用如“文件”应用、“文件管理器”应用手动浏览并选择文件之后将文件复制或移动到Downloads或其他公共目录再打开应用并使用导入的文件选择器进行访问。二、引导用户选择目录或文件保存位置。以下导出使用的是第二种方式2026.8.26修1、应用能在自己的私有目录自由读写不好找因为它是给应用用的不是给用户用的应用私有目录属于应用不属于用户。应用对它拥有完全控制权无需权限但用户没有“浏览”它的必要——用户应该通过应用界面来管理数据而不是直接操作文件。这种设计也杜绝了用户误删除关键文件导致应用故障的风险。而高版本 Android 的限制更严从 Android 11API 30开始系统禁止第三方文件管理器访问/Android/data/和/Android/obb/目录即使是应用私有目录外部存储上的也不行。当用户通过文件管理器尝试进入该目录时会看到“访问受限”如上图或“需要权限”的提示。目录类型路径示例权限要求原因应用私有目录/data/data/包名/files/不需要权限这个目录归应用独有其他应用无法访问不影响用户和其他应用是应用的“自留地”。系统允许 App 任意读写自己的私有区域。公共目录/storage/emulated/0/Download/需要 SAF 或权限这个目录是所有应用共享的。如果允许多个 App 随意读写会造成数据混乱、隐私泄露、存储空间被恶意填满。2.3.1 适配不同Android版本下面这段代码用于处理 Android 设备上的文件保存操作考虑到不同的 Android 版本能够适应不同版本的存储权限模型以确保应用在各个 Android 版本上都能正确保存文件2026.3.11修发现从Android 4.4API19就能通过系统文件选择器进行文件选择或保存而ACTION_CREATE_DOCUMENT实际上也是从 Android 4.4API 19开始引入的在此将判断条件改成Build.VERSION_CODES.KITKAT。我测试的真机最低版本是Android8能正常进行文件选择和保存。2.3.2 处理文件创建重写 onActivityResult来处理文件创建请求2026.3.11修补充用户取消文件选择的判断private static final int CREATE_FILE_REQUEST_CODE 1; Override protected void onActivityResult(int requestCode, int resultCode, Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode CREATE_FILE_REQUEST_CODE) { if (resultCode RESULT_OK) { if (data ! null data.getData() ! null) { Uri uri data.getData(); if (uri ! null) { // Assume we have a file type based on the file name // 获取文件名和扩展名 String fileName getFileName(this, uri); Log.d(TAG, fileName fileName); String fileExtension getFileExtension(fileName); Log.d(TAG, fileExtension fileExtension); writeTxtFile(uri); } } } else if (resultCode RESULT_CANCELED) { //用户取消操作返回键或点击外部 Log.d(TAG, 用户取消文件选择); } } }2.3.3 写入文件内容根据文件类型来生成相应的文件格式://writeTxtFile使用 PrintWriter 生成 txt 文件并将其写入用户选择的文件 URI private void writeTxtFile(Uri uri) { try(OutputStream outputStream getContentResolver().openOutputStream(uri); PrintWriter writer new PrintWriter(outputStream)) { printWriter.println(Hello, World!); printWriter.println(This is a test.); } catch (IOException e) { Log.d(test,File not found.); e.printStackTrace(); } }

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价