IntentService / Service - продолжайте работать, даже если приложение закрыто - Android - PullRequest
0 голосов
/ 11 марта 2019

Я внедряю IntentService для загрузки файла PDF , он показывает уведомление пользователю на downloadStart и продолжает обновлять прогресс во время этого процесса, служба работает нормально и прогрессобновляется правильно, проблема в том, что как только я удаляю свое приложение из «Недавних», загрузка останавливается, даже не показывая ошибку.

  • Вот мой DownloadService класс:

class DownloadService : IntentService("DownloadService") {

lateinit var  downloadNotification : DownloadNotification
lateinit var book : BookData
private lateinit var fileName : String
private lateinit var fileFolder : String
private lateinit var filePath : String
lateinit var fileUrl : String
var isCancelled = false
private lateinit var handler : Handler

override fun onCreate() {
    super.onCreate()
    handler = Handler()
}


override fun onHandleIntent(p0: Intent?) {
    book = Gson().fromJson<BookData>(p0?.getStringExtra("book"), BookData::class.java)
    downloadNotification = DownloadNotification(this, book.id!!)
    init(book)
}

fun getFilePath() : String {
    val directory = File(fileFolder)
    if (!directory.exists()) {
        directory.mkdirs()
    }
    return filePath
}

private fun init(book : BookData) {
    fileName = "${book.id}.pdf"
    fileFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).toString() + File.separator + "Libranova/Books/"
    filePath = fileFolder + fileName
    fileUrl = book.downloadLink!!
    startDownload()
}

private fun startDownload() {
    downloadNotification.setTitle(book.name!!).setText("Preparing...").notificationCompat.apply {
        downloadNotification.notifyManager(true)
        DownloadUtils.downloadFile(this@DownloadService, object : DownloadListener {
            override fun onStarted() {
                handler.post {
                    Toast.makeText(this@DownloadService,"Download Started", Toast.LENGTH_LONG).show()
                }
            }

            override fun onSuccess() {
                downloadNotification.onFinishDownload().freeUp().setSuccess().notifyManager(true)
            }

            override fun onError(message: String) {
                downloadNotification.onFinishDownload().freeUp().setError(message).notifyManager(true)
            }

            override fun onCanceled() {
                downloadNotification.cancel()
            }

            override fun onProgress(progress: Int) {
                downloadNotification.setProgress(progress).setText("$progress%").notifyManager(false)
            }

        })
    }

}
}
  • и вот мой downloadFile метод, который находится в object:

object DownloadUtils {
    
    fun downloadFile(downloadService: DownloadService, downloadListener: DownloadListener) {
        try {
            val url = URL(downloadService.fileUrl)
            val connection = url.openConnection()
            connection.connect()
            val lengthOfFile = connection.contentLength
            val input = BufferedInputStream(url.openStream(), 8192)
            val output = FileOutputStream(downloadService.getFilePath())
            val data = ByteArray(1024)
            var total: Long = 0
            var count = input.read(data)
            downloadListener.onStarted()
            while (count != -1)  {
                if (!downloadService.isCancelled) {
                    total += count.toLong()
                    downloadListener.onProgress(((total * 100) / lengthOfFile).toInt())
                    output.write(data, 0, count)
                    count = input.read(data)
                }
                else break
            }
            output.flush()
            output.close()
            input.close()
            if (downloadService.isCancelled) downloadListener.onCanceled() else downloadListener.onSuccess()
        }
        catch (e : Exception) {
            downloadListener.onError(e.message ?: "Unknown Error")
        }
    }

    fun fastFileDownload(downloadService: DownloadService) {
        URL(downloadService.fileUrl).openStream().use { input ->
            val folder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).toString() + File.separator + "Libranova/Books/"
            val directory = File(folder)
            if (!directory.exists()) {
                directory.mkdirs()
            }
            FileOutputStream(File(downloadService.getFilePath())).use { output ->
                input.copyTo(output)
            }
        }
    }
}

После долгих поисков в Интернете я обнаружил, что использование Service вместо IntentService решит проблему, я изменил свою структуру классов, чтобы наследоватьвместо Service() все работало нормально, за исключением onError(message : String), возвращающего null e.message (в данном случае он возвращает "Unknown Error") из downloadFile метода сразу после запуска процесса в catch (e : Exception).Есть ли способ / альтернатива, чтобы сохранить файл загрузки и обновления уведомлений об определенных событиях?

Примечания:

  • Я уже использовал AsyncTask, но мой файл загружался довольно долго, что не очень подходит (размер файла в 5..150 МБ).
  • Я использовал ThreadPoolExcuter / Thread, который обновляет уведомление с помощью runOnUiThread, но оно также уничтожается при завершении работы приложения.Спасибо!

Редактировать : после ответа m0skit0 в методе onCreate, я создал уведомление, которое будет отображаться в течение всего процесса загрузки, показываяколичество загрузок, ожидающих обработки, пока оно показывает другое уведомление с прогрессом для каждого процесса загрузки.позвонив по номеру startForeground(ID, notification) в onCreate, сервис будет доступен даже после закрытия приложения.

  • Вот мой новый onCreate () метод:

    override fun onCreate() {
    super.onCreate()
    handler = Handler()
    val notificationBuilder = NotificationCompat.Builder(this, "LibranovaDownloadService")
    val notification = notificationBuilder.setOngoing(true)
        .setSmallIcon(R.mipmap.ic_launcher)
        .setPriority(NotificationCompat.PRIORITY_HIGH)
        .setSubText("Download Queue")
        .setContentText("Waiting For Download : 1 Book")
        .setCategory(NotificationCompat.CATEGORY_SERVICE)
        .build()
    startForeground(123, notification)
    

    }

1 Ответ

1 голос
/ 11 марта 2019

Чтобы сохранить Service в живых, вы можете использовать startForeground API.

Из документации :

Запущенная служба может использовать startForeground(int, Notification) API для перевода сервиса в приоритетное состояние, в котором находится система считает, что это что-то, что пользователь активно знает и, таким образом, не кандидат на убийство, когда мало памяти. (Это все еще теоретически возможно убить службу под давление памяти от текущего приложения переднего плана, но в практиковать это не должно быть проблемой.)

...