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)
)并不总是有效。我必须先卸载应用程序。
我的目标是 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)
)并不总是有效。我必须先卸载应用程序。