告别繁琐,使用WorkManager解决后台工作的难题

告别繁琐,使用WorkManager解决后台工作的难题

前言

执行后台任务是Android开发中比较麻烦的部分,需要考虑许多事情。首先是省电续航问题,后台任务频繁执行,电量像流水一样消耗,这是用户和系统都无法忍受的。谷歌为了治理Android应用野蛮地在后台偷偷工作的恶行,推出各种后台限制机制(Doze Mode、App Standby、Background Execution Limits),新版本Android的后台管理越来越严格,给开发者带来一定的兼容性难题。最重要的是开发者难以保证应用的后台任务的可靠性,系统常常因为各种原因暂停或者杀死后台任务。

WorkManager是Android Jetpack 家族中的一位"后台任务管家",是适用于持久性工作和一般后台处理操作的官方推荐 API。本文以开发一个下载器为例子介绍WorkManager的使用方法。下载器的代码可以在下面仓库找到:github.com/Ilovecat194...

欢迎大家关注我的音视频开发项目:github.com/Ilovecat194... 。这是一个开源的Android音视频编辑器,支持ffmpeg命令行、视频编码压缩和格式转换、视频剪裁变速等多种音视频功能。

WorkManager为什么是最佳选择

1.官方背书,可靠性强:作为谷歌官方推荐的后台任务解决方案,WorkManager 会持续得到支持和优化,具有简单且一致的 API 。

2.持久可靠地执行:这是WorkManager最大的亮点。即使用户导航离开屏幕、退出应用或重启设备也不影响工作的执行。对于失败的任务,WorkManager提供了灵活的重试政策,包括可配置的指数退避政策。

3.高效节能:WorkManager遵循低电耗模式等省电功能和最佳做法,具有强大的调度能力。

4.约束条件灵活:开发者可以为任务设置各种约束条件,让它在最合适的时机执行,从而节省电量和流量。

例如:

网络状态:只在Wi-Fi连接时执行。

充电状态:只在设备充电时执行。

存储空间:只在存储空间足够时执行。

5.任务类型多样:支持一次性任务、周期性任务和链式任务,满足开发者的多种需求。

核心理念与实践:WorkManager是如何工作的?

WorkManager 的设计理念非常清晰,它将后台任务分解为三个核心组件:

1.工作单元:Worker

理念:WorkManager将后台任务抽象成一个个独立的Worker。每个Worker只负责执行一个具体的任务,例如:上传日志、同步数据、下载文件等。

实现:继承Worker类并重写doWork()方法。在这个方法中放入你的后台任务逻辑。

2.任务请求:WorkRequest

理念:WorkRequest是用来描述任务如何执行的,它包含了任务的约束条件、延迟执行时间等信息。

实践:使用OneTimeWorkRequest(一次性任务)或PeriodicWorkRequest(周期性任务)来创建任务请求,并通过Constraints构建器设置约束。

3.任务调度:WorkManager

理念:WorkManager是后台任务的调度中心,它负责接收WorkRequest,并根据约束条件和系统状态来决定何时执行Worker。

实践:通过WorkManager.getInstance(context).enqueue(workRequest)来将任务提交给WorkManager。

用代码说话:使用WorkManager开发一个下载器

设计

我们在开发之前,需要先想好我们开发的应用是怎么样的。

1.使用kotlin语言开发、使用jetpack compose开发UI。

2.能够申请实现下载所需的权限,如网络权限,文件读写权限。

3.界面上支持用户输入下载链接并开启下载。

4.有下载任务列表,展示下载进度和状态。

6.支持下载进度通知。

这里需要强调一下通知的重要性。对于长时间运行且需要持续访问网络或 CPU 的任务,它需要一个"通行证"来告知系统该任务的重要性。为了解决这个问题,我们需要将下载任务升级为 "前台服务(Foreground Service)"。前台服务会向系统声明任务正在进行,并且对用户可见(通过通知),因此系统不会轻易终止它。

代码实现

我们首先需要在AndroidManifest.xml里声明所需要的权限。对于Android13以及后面的Android版本,我们需要显式地声明通知权限。

xml 复制代码
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    <!-- 声明网络权限,这是下载功能所必需的 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

我们还需要运行时检查和请求权限,还有创建通知通道。

kotlin 复制代码
    private val requestPermissionLauncher =
        registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
            if (isGranted) {
                // Permission granted. Continue with the app flow.
            } else {
                Toast.makeText(this, "需要通知权限才能显示下载进度", Toast.LENGTH_LONG).show()
            }
        }
    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val name = "下载通知"
            val descriptionText = "显示下载文件的进度和状态"
            val importance = NotificationManagerCompat.IMPORTANCE_LOW
            val channel = android.app.NotificationChannel(DOWNLOAD_NOTIFICATION_CHANNEL_ID, name, importance).apply {
                description = descriptionText
            }
            val notificationManager: android.app.NotificationManager =
                getSystemService(Context.NOTIFICATION_SERVICE) as android.app.NotificationManager
            notificationManager.createNotificationChannel(channel)
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
    ...
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R){
            val permission = arrayOf(
                Manifest.permission.WRITE_EXTERNAL_STORAGE,
                Manifest.permission.READ_EXTERNAL_STORAGE
            )
            val requestPermissionLauncher =
                this.registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->

                }
            permission.forEach {
                if(! (this.checkSelfPermission(it) == PackageManager.PERMISSION_GRANTED)){
                    requestPermissionLauncher.launch(it)
                }
            }
        }
        else{
            if(!Environment.isExternalStorageManager()){
                val builder = android.app.AlertDialog.Builder(this)
                    .setMessage("需要获取文件读写权限")
                    .setPositiveButton("ok") { _, _ ->
                        val packageName = this.packageName
                        val intent = Intent()
                        intent.action = Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
                        intent.data = Uri.fromParts("package", packageName, null)
                        ContextCompat.startActivity( this, intent, null)
                    }
                    .setNeutralButton("稍后再问"){ _, _ ->

                    }
                builder.show()
            }
        }


        // 请求通知权限 (适用于 Android 13 及以上版本)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            if (ActivityCompat.checkSelfPermission(
                    this,
                    Manifest.permission.POST_NOTIFICATIONS
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
            }
        }

        // 创建通知渠道
        createNotificationChannel()    
    ...    
    }        

基于自己的需求定义一个工作类,执行任务的具体逻辑都写在这个工作类里面。

kotlin 复制代码
class DownloadWorker(
    appContext: Context,
    workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {

    private val notificationManager = NotificationManagerCompat.from(appContext)
    private val notificationBuilder = NotificationCompat.Builder(applicationContext, DOWNLOAD_NOTIFICATION_CHANNEL_ID)
        .setContentTitle("下载中")
        .setSmallIcon(android.R.drawable.stat_sys_download)
        .setPriority(NotificationCompat.PRIORITY_LOW)
        .setOngoing(true)

    // 最大文件名长度
    private val MAX_FILENAME_LENGTH = 50

    // 重写 getForegroundInfo() 以提供前台服务通知信息
    override suspend fun getForegroundInfo(): ForegroundInfo {
        return ForegroundInfo(
            DOWNLOAD_NOTIFICATION_ID,
            notificationBuilder.setContentText("开始下载...").build()
        )
    }

    override suspend fun doWork(): Result {
     //文件下载的具体逻辑写在这里面 
     ...
    }

    // Helper function to set progress for the UI
    private fun setProgress(progress: Int) {
        val progressData = workDataOf("progress" to progress)
        setProgressAsync(progressData)
    }
}

我们通过上下文获得一个WorkManager的实例,这个实例可以直接帮我们完成任务数据库和调度系统的全部工作。WorkManager 使用了一个 Room 数据库(一个 SQLite 数据库的抽象层),这个数据库位于应用私有的存储空间中,从而实现任务的持久化和状态管理。我们可以直接从WorkManager的实例中获取所有任务的信息。配合LiveData,应用界面能实时观察并显示任务的状态和进度,实现后台任务与UI的同步。

scss 复制代码
    val workManager = WorkManager.getInstance(context)
    ...
    val workInfos: LiveData<List<WorkInfo>> = remember {
        workManager.getWorkInfosByTagLiveData(DOWNLOAD_WORK_TAG)
    }
    val downloadTasks by workInfos.observeAsState(initial = emptyList())
    ...
    LazyColumn(
        modifier = Modifier.fillMaxSize()
    ) {
        items(downloadTasks) { workInfo ->
            DownloadTaskItem(workInfo)
        }
    }    

定义一个下载启动方法,封装提交任务给WorkManager的流程。

kotlin 复制代码
fun startDownload(context: Context, url: String) {
    // 1. 创建 WorkRequest,指定要执行的 Worker
    val downloadRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
        .setInputData(workDataOf(DOWNLOAD_WORK_URL to url)) // 2. 传递输入数据
        .addTag(DOWNLOAD_WORK_TAG) // 3. 添加标签以便跟踪
        .build()

    // 4. 将任务加入队列
    WorkManager.getInstance(context).enqueue(downloadRequest)
}

以上就是使用使用WorkManager开发一个下载器的思路。

展望未来:Android后台任务的新趋势与谷歌政策

谷歌一直在收紧后台任务的权限,以保护用户隐私和设备电量。未来的Android版本可能会有更严格的后台执行限制,这意味着传统的Service和BroadcastReceiver等方式会越来越难用。 在这样的背景下,WorkManager 作为官方推荐的方案,其重要性将日益凸显。它能帮助我们遵循谷歌的政策,同时保证任务的可靠执行。

总结

回顾 WorkManager 的优势:可靠、兼容、持久、灵活。它解放了开发者,让我们可以专注于业务逻辑,而不用再为复杂的后台任务适配和兼容性问题而焦头烂额。

如果你还在为后台任务而烦恼,是时候拥抱 WorkManager 了。它不仅能让你的应用更稳定,也能让你的开发工作更高效。

下载器的代码可以在下面仓库找到:github.com/Ilovecat194...

欢迎大家关注我的音视频开发项目:ilovecat1949.github.io/AudioAndVid... 。这是一个开源的Android音视频编辑器,支持ffmpeg命令行、视频编码压缩和格式转换、视频剪裁变速等多种音视频功能。

参考

1.Android后台工作官方指南

2.WorkManager官方课程

相关推荐
Coovally AI模型快速验证20 分钟前
华为发布开源超节点架构,以开放战略叩响AI算力生态变局
人工智能·深度学习·神经网络·计算机视觉·华为·架构·开源
ajassi20002 小时前
开源 java android app 开发(十五)自定义绘图控件--仪表盘
android·java·开源
胡耀超3 小时前
开源生态与技术民主化 - 从LLaMA到DeepSeek的开源革命(LLaMA、DeepSeek-V3、Mistral 7B)
人工智能·python·神经网络·开源·大模型·llama·deepseek
love530love6 小时前
Windows 系统部署 阿里团队开源的先进大规模视频生成模型 Wan2.2 教程——基于 EPGF 架构
运维·人工智能·windows·python·架构·开源·大模型
说私域6 小时前
定制开发开源AI智能名片S2B2C商城小程序在智慧零售价值链重构中的价值研究
人工智能·小程序·开源
鹏大师运维10 小时前
信创环境下的远程桌面新选择:RustDesk自建服务全攻略
linux·开源·操作系统·远程桌面·麒麟·rustdesk·统信uos
ajassi200010 小时前
开源 C# 快速开发(七)通讯--串口
开源·c#·mfc
HelloGitHub10 小时前
《HelloGitHub》第 114 期
开源·github
伞啊伞11 小时前
开源的容器化平台:Docker
docker·容器·开源
徐小夕@趣谈前端19 小时前
如何实现多人协同文档编辑器
javascript·vue.js·设计模式·前端框架·开源·编辑器·github