8

很长一段时间以来,我一直在努力解决这个问题......我正在更新一个使用 DownloadManger 执行简单任务的应用程序,例如将文件下载到外部存储公共目录,即:

Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)

从 Android api 19-28 开始一切正常。它在 API 29 (Q/10) 上进行测试时会出现问题。Android 实现了范围存储,因此弃用了 getExternalStoragePublicDirectory... 因此,我需要找出一个兼容的解决方案来支持 API 19-29。我不能使用内部应用程序存储,因为 DownloadManager 会抛出 SecurityException。Androids 文档声明我可以使用 DownloadManager.Request setDestinationUri,它甚至提到了我可以使用的 Android Context.getExternalFilesDir(String)Q。但是,当我这样做时,路径仍然是模拟路径:

/storage/emulated/0/Android/data/com.my.package.name/files/Download/myFile.xml

我从下载管理器收到一个回调,说下载已完成(使用正确的 ID),但是我无法从保存它的区域获取下载。我检查文件是否存在并返回 false:

new File("/storage/emulated/0/Android/data/com.my.package.name/files/Download/myFile.xml").exists();

任何帮助表示赞赏

为上下文添加代码。所以设置下载管理器

    private void startDownload() {
        IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
        registerReceiver(downloadReceiver, filter);

        String remoteURL= getString(R.string.remote_url);

        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(remoteUrl));
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
        request.setTitle(getString(R.string.download_title));
        request.setDescription(getString(R.string.download_description));
        request.setDestinationUri(Uri.fromFile(new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "myFile.xml")));

        DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
        mainDownloadID= manager.enqueue(request);
    }

检查文件是否存在:

new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "myFile.xml").exists(); //this returns false in the onReceive (and download IDs match)
4

4 回答 4

1

是的,它的范围存储,但即使您可以使用下载管理器在 Q+ 中下载文件,也无需这样做android:requestLegacyExternalStorage="true"

我正在这样做。

  1. 显现
  • -->
  1. 下载管理器

     val fileName =
         Constants.FILE_NAME + Date().time
    
     val downloadUri = Uri.parse(media.url)
     val request = DownloadManager.Request(
         downloadUri
     )
     request.setAllowedNetworkTypes(
         DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE
     )
         .setAllowedOverRoaming(true).setTitle("Some name")
         .setDescription("Downloading file")
         .setDestinationInExternalPublicDir(
             Environment.DIRECTORY_DOWNLOADS, File.separator + FOLDER + File.separator + fileName
         )
    
    
     Toast.makeText(
         context,
         "Download successfully to ${downloadUri?.path}",
         Toast.LENGTH_LONG
     ).show()
    
     downloadManager.enqueue(request)
    

因此,它会在 Q 下方询问写权限,但在 Q 和 Q+ 中,它会在不询问 /Download/folder 目录中的权限的情况下下载。

于 2021-01-23T18:01:59.110 回答
1

尝试将此添加到应用程序标记中的清单文件中

android:requestLegacyExternalStorage="true"

于 2019-12-04T13:47:06.840 回答
0

Android Q 及更高版本中应用程序私有目录之外的文件路径无用。

https://developer.android.com/training/data-storage#scoped-storage

您还需要询问用户在哪里下载文件,这将为您提供DownloadManager目的地的 URI。

https://developer.android.com/training/data-storage/shared/documents-files#grant-access-directory

您可能希望保留此权限

https://developer.android.com/training/data-storage/shared/documents-files#persist-permissions

于 2019-12-04T13:49:49.270 回答
-1

使用这段代码并享受,这段代码使用 RxJava 进行网络调用:

import android.content.ContentValues
import android.content.Context
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import io.reactivex.Observable
import io.reactivex.ObservableEmitter
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.ResponseBody
import java.io.*
import java.net.HttpURLConnection
import java.util.concurrent.TimeUnit

    class FileDownloader(
         private val context: Context,
         private val url: String,
         private val fileName: String
         ) {
    
        private val okHttpClient: OkHttpClient = OkHttpClient.Builder()
            .connectTimeout(60, TimeUnit.SECONDS)
            .readTimeout(60, TimeUnit.SECONDS)
            .build()
    
        private val errorMessage = "File couldn't be downloaded"
        private val bufferLengthBytes: Int = 1024 * 4
    
        fun download(): Observable<Int> {
            return Observable.create<Int> { emitter ->
    
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // To Download File for Android 10 and above
                    val content = ContentValues().apply {
                        put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
                        put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
                    }
                    val uri = context.contentResolver.insert(
                        MediaStore.Downloads.EXTERNAL_CONTENT_URI,
                        content
                    )
                    uri?.apply {
                        val responseBody = getResponseBody(url)
                        if (responseBody != null
                        ) {
                            responseBody.byteStream().use { inputStream ->
                                context.contentResolver.openOutputStream(uri)?.use { fileOutStream ->
                                    writeOutStream(
                                        inStream = inputStream,
                                        outStream = fileOutStream,
                                        contentLength = responseBody.contentLength(),
                                        emitter = emitter
                                    )
                                }
                                emitter.onComplete()
                            }
                        } else {
                            emitter.onError(Throwable(errorMessage))
                        }
                    }
                }
                    else { // For Android versions below than 10
                        val directory = File(
                            Environment.getExternalStoragePublicDirectory(
                                Environment.DIRECTORY_DOWNLOADS).absolutePath
                        ).apply {
                            if (!exists()) {
                                mkdir()
                            }
                        }
    
                    val file = File(directory, fileName)
                    val responseBody = getResponseBody(url)
    
                    if (responseBody != null) {
                        responseBody.byteStream().use { inputStream ->
                            file.outputStream().use { fileOutStream ->
                                writeOutStream(
                                    inStream = inputStream,
                                    outStream = fileOutStream,
                                    contentLength = responseBody.contentLength(),
                                    emitter = emitter
                                )
                            }
                            emitter.onComplete()
                        }
    
                    } else {
                        emitter.onError(Throwable(errorMessage))
                    }
                }
            }
        }
    
        private fun getResponseBody(url: String): ResponseBody? {
            val response = okHttpClient.newCall(Request.Builder().url(url).build()).execute()
    
            return if (response.code >= HttpURLConnection.HTTP_OK &&
                response.code < HttpURLConnection.HTTP_MULT_CHOICE &&
                response.body != null
            )
                response.body
            else
                null
        }
    
        private fun writeOutStream(
            inStream: InputStream,
            outStream: OutputStream,
            contentLength: Long,
            emitter: ObservableEmitter<Int>) {
                var bytesCopied = 0
                val buffer = ByteArray(bufferLengthBytes)
                var bytes = inStream.read(buffer)
                while (bytes >= 0) {
                    outStream.write(buffer, 0, bytes)
                    bytesCopied += bytes
                    bytes = inStream.read(buffer)
    //                emitter.onNext(
                        ((bytesCopied * 100) / contentLength).toInt()
    //                )
                }
        outStream.flush()
        outStream.close()
    
        }
    }

在调用方你必须纠正这个:

private fun downloadFileFromUrl(context: Context, url: String, fileName: String) {
    FileDownloader(
        context = context,
        url = url,
        fileName = fileName
    ).download()
        .throttleFirst(2, TimeUnit.SECONDS)
        .toFlowable(BackpressureStrategy.LATEST)
        .subscribeOn(Schedulers.io())
        .observeOn(mainThread())
        .subscribe({
            // onNext: Downloading in progress
        }, { error ->
            // onError: Download Error
            requireContext()?.apply {
                Toast.makeText(this, error.message, Toast.LENGTH_SHORT).show()
            }
        }, {
            // onComplete: Download Complete
            requireContext()?.apply {
                Toast.makeText(this, "File downloaded to Downloads Folder", Toast.LENGTH_SHORT).show()
            }
        })
}
于 2021-09-10T07:15:50.293 回答