Android 31如何访问下载文件夹中的.zip文件并迁移至内部存储?
解决Android中无法访问Download文件夹内.zip文件及迁移至内部存储的问题
问题分析
你的代码存在几个关键问题导致无法获取目标文件:
- 路径获取方式冗余且不可靠:通过遍历根目录查找
DIRECTORY_DOWNLOADS,不如直接调用系统API获取准确路径。 - 权限处理缺失:Android 6.0+需要主动申请动态存储权限;Android 10+(API 29)启用分区存储后,App默认无法直接访问其他App创建的文件。
- listFiles()的局限性:分区存储模式下,
File.listFiles()无法访问不属于App沙盒的文件,尤其是其他App下载的.zip等非媒体文件。
解决方案
1. 配置并申请存储权限
在AndroidManifest.xml中添加权限声明:
<!-- Android 12及以下 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- Android 13+ 针对.zip等非媒体文件,可申请全存储权限(注意谷歌审核政策) --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
添加动态权限申请逻辑:
private static final int REQUEST_STORAGE_PERMISSION = 100; private void requestStoragePermission() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_STORAGE_PERMISSION); } else { processDownloadFiles(); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_STORAGE_PERMISSION) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { processDownloadFiles(); } else { Toast.makeText(this, "需要存储权限才能访问文件", Toast.LENGTH_SHORT).show(); } } }
2. 正确获取Download文件夹路径
替换遍历根目录的低效方式,直接获取标准Download路径:
File downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
3. 适配分区存储(Android 10+)
若targetSdkVersion >= 29,默认启用分区存储,File.listFiles()无法跨App访问文件,可通过两种方式解决:
方式一:用MediaStore查询Downloads目录文件
适用于获取公共Downloads下的所有文件(包括.zip):
private List<Uri> getDownloadFilesUris() { List<Uri> fileUris = new ArrayList<>(); String[] projection = {MediaStore.Downloads._ID, MediaStore.Downloads.DISPLAY_NAME}; Cursor cursor = getContentResolver().query( MediaStore.Downloads.EXTERNAL_CONTENT_URI, projection, null, null, null ); if (cursor != null) { while (cursor.moveToNext()) { long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Downloads._ID)); Uri contentUri = ContentUris.withAppendedId(MediaStore.Downloads.EXTERNAL_CONTENT_URI, id); fileUris.add(contentUri); } cursor.close(); } return fileUris; }
方式二:用存储访问框架(SAF)让用户选择文件夹
更合规的方式,避免全存储权限的审核风险:
private static final int REQUEST_PICK_FOLDER = 200; private void pickDownloadFolder() { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); // 默认定位到Download文件夹 intent.putExtra("android.provider.extra.INITIAL_URI", Uri.parse("content://com.android.externalstorage.documents/tree/primary%3ADownload")); startActivityForResult(intent, REQUEST_PICK_FOLDER); } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_PICK_FOLDER && resultCode == RESULT_OK && data != null) { Uri treeUri = data.getData(); // 授予持久访问权限 getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); traverseFolderUsingSAF(treeUri); } } private void traverseFolderUsingSAF(Uri treeUri) { DocumentFile rootDoc = DocumentFile.fromTreeUri(this, treeUri); if (rootDoc != null && rootDoc.isDirectory()) { for (DocumentFile doc : rootDoc.listFiles()) { if (doc.isDirectory()) { traverseFolderUsingSAF(doc.getUri()); } else { copyFileToInternalStorage(doc); } } } }
4. 迁移文件至应用内部存储
无论是通过File API还是SAF获取的文件,都可以复制到App内部存储(如getFilesDir()):
// SAF方式复制 private void copyFileToInternalStorage(DocumentFile docFile) { try (InputStream inputStream = getContentResolver().openInputStream(docFile.getUri()); OutputStream outputStream = openFileOutput(docFile.getName(), Context.MODE_PRIVATE)) { byte[] buffer = new byte[1024]; int length; while ((length = inputStream.read(buffer)) > 0) { outputStream.write(buffer, 0, length); } Toast.makeText(this, "文件迁移完成:" + docFile.getName(), Toast.LENGTH_SHORT).show(); } catch (IOException e) { e.printStackTrace(); Toast.makeText(this, "迁移失败:" + e.getMessage(), Toast.LENGTH_SHORT).show(); } } // File API方式复制 private void copyFileToInternalStorage(File sourceFile) { File destFile = new File(getFilesDir(), sourceFile.getName()); try (InputStream inputStream = new FileInputStream(sourceFile); OutputStream outputStream = new FileOutputStream(destFile)) { byte[] buffer = new byte[1024]; int length; while ((length = inputStream.read(buffer)) > 0) { outputStream.write(buffer, 0, length); } } catch (IOException e) { e.printStackTrace(); } }
内容的提问来源于stack exchange,提问作者Anju
相关产品推荐
相关产品推荐

