Tamim Attafi
Tamim Attafi

Reputation: 2511

IntentService/Service - Keep running even if the application is terminated - Android

I'm implementing an IntentService to download a PDF file, it shows a notification to the user on downloadStart and keep updating progress during this process, the service is working fine and the progress is updated correctly, the problem is as soon as i remove my app from "Recents" the download stops without even showing an error.

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)
            }

        })
    }

}
}

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)
            }
        }
    }
}

After a long searching on the internet, i have found that using a Service instead of IntentService will solve the problem, i have changed my class structure to inherit from Service() instead, everything worked fine except for the onError(message : String) returning a null e.message (in this case it returns "Unknown Error") from the downloadFile method immediately after starting the process in catch (e : Exception). Is there any way/alternative to keep the file downloading and updating the notification on certain events?

Notes :

Edit : following m0skit0's Answer in the onCreate method, i have created a notification that will be visible during the whole downloading process, showing the number of downloads waiting to handled while it shows an other notification with progress for each downloading process. by calling startForeground(ID, notification) in onCreate, the service will be available even the app is killed.

Upvotes: 0

Views: 42

Answers (1)

m0skit0
m0skit0

Reputation: 25873

To keep a Service alive you can use the startForeground API.

From the documentation:

A started service can use the startForeground(int, Notification) API to put the service in a foreground state, where the system considers it to be something the user is actively aware of and thus not a candidate for killing when low on memory. (It is still theoretically possible for the service to be killed under extreme memory pressure from the current foreground application, but in practice this should not be a concern.)

Upvotes: 1

Related Questions