DownloadManager 在 Android 11 模拟器上抛出 IllegalStateException 或 SecurityException

DownloadManager throws IllegalStateException or SecurityException on Android 11 emulator

我的目标是 Android 10 / api 29,下载管理器在版本 10 或更低版本上运行良好。它也适用于 R 预览版(我认为是预览版 2)。但是,当我使用新的 api 30(修订版 6)在模拟器中进行测试时,它将不起作用。

当 运行 在具有 api 30 的模拟器上并调用 setDestinationInExternalFilesDir 时,它在同一行给出了一个异常:

java.lang.IllegalStateException: Failed to get external storage files directory
        at android.app.DownloadManager$Request.setDestinationInExternalFilesDir

代码是这样的:

request.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS + "/foo/",  "bar.zip");

如果我改为调用 setDestinationUri,则在使用请求调用入队时出现异常:

request.setDestinationUri(Uri.fromFile(new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) + "/foo/", "bar.zip")));

异常:

java.lang.SecurityException: Unsupported path /null/foo/bar.zip

异常的行是这样的:

return downloadManager.enqueue(request);

setDestinationInExternalFilesDir 和 setDestinationUri 都在 运行 on api 29 时有效。

编辑:我也试过定位 api 30,它有同样的问题。

此外,如果我在异常后尝试再次调用它,则不会抛出异常,但是 DownloadManager 将无法下载文件并且 return DownloadManager.STATUS_PAUSED 在查询它时。

编辑:这是一个完整的示例,说明它不适用于 Android 11,但适用于 Android 10。我没有使用 android:requestLegacyExternalStorage="true",它仍然是正在处理 Android 10。在文档中,他们还声明当使用 setDestinationUri 时,它将被保存到应用程序特定的目录中。

import android.app.DownloadManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;

import com.google.android.material.floatingactionbutton.FloatingActionButton;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import android.os.Environment;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;

import java.io.File;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                DownloadManager.Request request = new DownloadManager.Request(Uri.parse("https://en.wikipedia.org/wiki/Main_Page#/media/File:Tukwila_Int'l_Blvd_station_with_northbound_Link_train_(2009).jpg"));
                request.setDescription("Downloading files");
                request.setTitle("Test dm");
                request.setVisibleInDownloadsUi(false);
                request.setDestinationInExternalFilesDir(MainActivity.this, Environment.DIRECTORY_DOWNLOADS, "testimage.jpg");
                //request.setDestinationUri(Uri.fromFile(new File(MainActivity.this.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "testimage.jpg")));

                DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
                long id = downloadManager.enqueue(request);

                for (int x = getProgress(id); x < 100; x = getProgress(id)) {
                    Log.d("getProgress", x + "");
                    if (x == -1) {
                        break;
                    }
                    SystemClock.sleep(100);

                    if (getStatus(id) == 0) {
                        break;
                    }
                }
            }
        });
    }

    private int getProgress(long id) {
        DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);

        if (downloadManager != null) {
            DownloadManager.Query query = new DownloadManager.Query();
            query.setFilterById(id);

            Cursor cursor = downloadManager.query(query);
            if (cursor != null && cursor.moveToFirst()) {
                int sizeIndex = cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES);
                int downloadedIndex = cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR);

                long size = cursor.getInt(sizeIndex);
                long downloaded = cursor.getInt(downloadedIndex);

                Log.d(TAG, "Download status : " + cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)));

                if (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) == DownloadManager.STATUS_PENDING
                        || cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) == DownloadManager.STATUS_PAUSED
                        || cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) == DownloadManager.STATUS_RUNNING) {
                    return (int) (downloaded * 100 / size);
                } else if (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) == DownloadManager.STATUS_SUCCESSFUL) {
                    return 100;
                } else if (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) == DownloadManager.STATUS_FAILED) {
                    Log.e(TAG, "Download failed, Reason : " + cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON)));
                    return -1;
                }
            }

            if (cursor != null) {
                cursor.close();
            }
        }

        return 0;
    }

    private int getStatus(long id) {
        DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);

        int status = 0;
        DownloadManager.Query query = new DownloadManager.Query();
        query.setFilterById(id);

        Cursor c = downloadManager.query(query);

        if (c != null) {
            if (c.moveToFirst()) {
                status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
            }
            c.close();
        }

        return status;
    }
}

在抛出异常之前也有这个警告:

W/ContextImpl: Failed to ensure /storage/emulated/0/Android/data/...

您的代码在模拟器上运行失败,但在当前测试版 (RPB2.2000611.009) 的真实 Pixel 3a 上运行,文件实际已下载。

好像是this issue.

奇怪的是,建议的解决方法(手动调用 getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS))并不总是有效。我必须先卸载应用程序。