首页 > 解决方案 > DownloadManager 在 Android 11 模拟器上抛出 IllegalStateException 或 SecurityException

问题描述

我的目标是 Android 10 / api 29,并且 DownloadManager 在版本 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,则在使用请求调用 enqueue 时会出现异常:

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);

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

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

此外,如果我在出现异常后再次尝试调用它,则不会引发异常,但是 DownloadManager 将无法下载文件并在查询时返回 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/...

标签: androidscoped-storageandroid-11

解决方案


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

好像是这个问题

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


推荐阅读