我的目标是 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/...