java - IntentService/Service - 即使应用程序终止也保持运行 - Android

标签 java android kotlin service android-service

我正在实现 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 MB)。
  • 我用过ThreadPoolExcuter/Thread使用 runOnUiThread 更新通知但它也会在应用程序终止时被杀死。 谢谢!

编辑: 关注m0skit0's Answer 在 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)
    

    }

最佳答案

要保持 Service 处于 Activity 状态,您可以使用 startForeground API。

来自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.)

关于java - IntentService/Service - 即使应用程序终止也保持运行 - Android,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55104486/

相关文章:

java - 如何验证按钮单击时的方法

java - Spring-boot @RequestParam 注解将请求中的 UTC 时区转换为本地时区

Android MediaPlayer.Create() 返回 null

android - 注册为 Git 根目录,但在那里找不到 Git 存储库

Android 蓝牙耳机 getConnectedDevices() 列表为空

android - 如何样式化 (CSS) 由 Android 应用程序 (Intent) 创建的电子邮件的 HTML 正文?

java - 数组错误(java)

java - 内部类中的静态方法错误

android - 密度 APK 拆分不会反射(reflect)在 mipmap 文件夹中

java - 如何在 Kotlin 中重写此方法(在 Java 中有效,但在 Kotlin 中无效)