DownloadManager не работает на Android 10 (Q)

Я долго боролся с этой проблемой... Я обновляю приложение, которое использует DownloadManger для выполнения простой задачи, такой как загрузка файла в общедоступный каталог внешнего хранилища, то есть:

Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)

Здесь все отлично работает с Android api 19-28. Проблемы возникают при тестировании по API 29 (Q/10). Android внедрено хранения и область видимости, так осуждал getExternalStoragePublicDirectory... В итоге мне нужно выяснить, совместимое решение для поддержки API, 19-29. Я не могу использовать внутреннее хранилище приложения, так как DownloadManager выдаст исключение SecurityException. В документации Android указано, что я могу использовать DownloadManager.Request.setDestinationUri и даже упоминается для Android Q, что я могу использовать Context.getExternalFilesDir(String). Когда я это делаю, путь по-прежнему остается эмулированным путем:

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

Я получаю обратный вызов от диспетчера загрузки, что загрузка завершена (с правильным идентификатором), но затем я не могу получить загрузку из области, в которой я ее сохранил. Я проверяю, существует ли файл, и он возвращает 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 ответа

Попробуйте добавить это в свой файл манифеста в теге приложения

android:requestLegacyExternalStorage="true"

Да, его объем хранилища, но даже если вы можете загрузить файл в Q + с помощью downloadmanger, делать это не нужно. android:requestLegacyExternalStorage="true"

Я так и делаю.

  1. манифест
  • ->
  1. Downloadmanger.

             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 dir.

Пути к файлам вне частных каталогов приложения в Android Q и выше бесполезны.

См. https://developer.android.com/training/data-storage.

Вам также нужно спросить пользователя, где скачать файлы, это даст вам URI для DownloadManager пункт назначения.

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

Вы, вероятно, захотите сохранить это разрешение

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

Используйте этот код и наслаждайтесь, этот код использует 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()
            }
        })
}
Другие вопросы по тегам