You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 10:43:21