当前位置: 首页 > news >正文

【WorkManager】Android 后台任务调度的核心组件指南

【WorkManager】Android 后台任务调度的核心组件指南

  • 一、定时上报需求描述
  • 二、定时上报需求分析
  • 三、定时上报实现方案
    • 3.1 Application 注册网络变化监听
    • 3.2 网络请求和后台任务
    • 3.3 保存网络环境状态
    • 3.4 实现网络变化接收器

一、定时上报需求描述

实现定时上报设备信息的Android系统应用,功能需求:开机上报、定时每24时上报、上报失败重试机制、网络环境变更上报、断网重连不上报,并且所有上报都需要网络连接时进行。

关于本应用的特殊性:不需要考虑如何被启动问题、不需要考虑被杀问题、需要考虑任务执行的准时性和可靠性、需要考虑开机设备未解锁时执行上报任务。

二、定时上报需求分析

基础设置:绑定 android:sharedUserId="android.uid.phone" 进程开机会自动启动应用,并设置 android:persistent="true" 属性在正常情况下应用进程永远不会被系统杀死,即使出现被杀也会自动重启,并且在 Direct Boot 模式下执行任务需要设置 android:directBootAware="true"

WorkManager 是 Android 平台上推荐用于处理可延迟工作的任务调度程序,作为 Android Jetpack 架构组件库中用于管理后台任务的核心组件,它专为可延迟、需可靠执行且不要求立即完成的任务设计,能确保任务即使应用退出或设备重启后仍能执行​。这里我们考虑使用 WorkManager 执行定时上报任务,使用简单且可靠准时,由于原生 WorkManager 是不支持 Direct Boot 模式,解决方案参考:【WorkManager】无法在 Direct Boot 模式下初始化。

在这里插入图片描述

三、定时上报实现方案

实现上诉的定时上报任务,主要按照以下步骤进行:

1. Application onCreate 中注册网络变化监听。
2. 网络监听中判断是否首次开机,必须执行上报;每次接收网络变化时保存当前网络环境为一个自定义参数,用于判断网络环境是否变化。
3. 执行上报任务,失败时重试三次;成功时计划24时的延时上报任务。

3.1 Application 注册网络变化监听

自定义的 Application 类,这个类是应用全局的上下文,在整个应用的生命周期中只存在一个实例,非常适合用来注册监听和管理全局的 BroadcastReceiver,用于网络变化的广播 ConnectivityManager.CONNECTIVITY_ACTION

public class DeviceReportApplication extends Application {private static final String TAG = "DeviceReportApplication";private NetworkChangeReceiver networkChangeReceiver;@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base.createDeviceProtectedStorageContext());}@Overridepublic void onCreate() {super.onCreate();LogUtil.i(TAG, "onCreate register NetworkChangeReceiver");registerNetworkChangeReceiver();}@Overridepublic void onTerminate() {super.onTerminate();LogUtil.i(TAG, "onTerminate unregister NetworkChangeReceiver");unregisterNetworkChangeReceiver();}private void registerNetworkChangeReceiver() {networkChangeReceiver = new NetworkChangeReceiver();IntentFilter filter = new IntentFilter();filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);registerReceiver(networkChangeReceiver, filter);}private void unregisterNetworkChangeReceiver() {if (networkChangeReceiver != null) {unregisterReceiver(networkChangeReceiver);networkChangeReceiver = null;}}@Overridepublic boolean isDeviceProtectedStorage() {return false;}
}

3.2 网络请求和后台任务

创建后台任务 (UploadWorker.java),这个 Worker 类负责执行实际的网络请求,以及请求失败重试机制。Worker 自带 getRunAttemptCount 方法获取重试次数,不需要额外计次。

public class UploadWorker extends Worker {private static final String TAG = "UploadWorker";private static final String SERVER_URL = "http://xxx.xxx.com/";private static final int MAX_RETRY_ATTEMPTS = 3;public UploadWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {super(context, workerParams);}@NonNull@Overridepublic Result doWork() {Log.d(TAG, ">>> 开始执行上报任务...");int runAttemptCount = getRunAttemptCount();Log.d(TAG, ">>> 开始执行上报任务... (第 " + (runAttemptCount + 1) + " 次尝试)");// 自定义的获取设备信息的方法DeviceInfo deviceInfo = DeviceInfoHelper.getFullDeviceInfo(getApplicationContext());String jsonStr = deviceInfo.toJsonString();Log.d(TAG, "设备信息: " + jsonStr);try {String encodedJson = URLEncoder.encode(jsonStr, "UTF-8");URL url = new URL(SERVER_URL + "?jsonstr=" + encodedJson);HttpURLConnection connection = (HttpURLConnection) url.openConnection();// ... set timeouts ...connection.setRequestMethod("GET");connection.setConnectTimeout(15000); // 15秒超时connection.setReadTimeout(15000);int responseCode = connection.getResponseCode();Log.d(TAG, "服务器响应码: " + responseCode);if (responseCode == HttpURLConnection.HTTP_OK) {Log.d(TAG, "上报成功。现在开始调度下一次(24小时后)的上报任务...");// *** 核心修改在这里 ***// 无论何时成功,都去调度下一次延迟24小时的任务ReportScheduler.scheduleNextDelayedReport(getApplicationContext());return Result.success();} else {Log.w(TAG, "上报失败,准备重试。");return handleFailure(runAttemptCount);}} catch (IOException e) {Log.e(TAG, "上报异常: " + e.getMessage());return handleFailure(runAttemptCount);}}private Result handleFailure(int runAttemptCount) {Log.w(TAG, "上报失败。当前尝试次数: " + (runAttemptCount + 1));// 检查是否还有重试机会// 注意:首次运行 runAttemptCount 是 0,所以重试3次意味着 runAttemptCount 会是 0, 1, 2if (runAttemptCount < MAX_RETRY_ATTEMPTS - 1) {Log.d(TAG, "还有重试机会,将按照退避策略在30分钟后重试。");return Result.retry(); // 告诉 WorkManager 使用 setBackoffCriteria 的策略重试} else {// 所有重试次数已用尽Log.e(TAG, "已达到最大重试次数(" + MAX_RETRY_ATTEMPTS + "),上报彻底失败。");Log.d(TAG, "将在最后一次重试的24小时后,再次进行终端信息上报尝试。");// *** 核心逻辑:手动调度一个24小时后的任务 ***ReportScheduler.scheduleNextDelayedReport(getApplicationContext());// 明确地将当前任务标记为失败,结束这个任务链return Result.failure();}}
}

创建任务调度器 (ReportScheduler.java),这个类负责安排和取消 WorkManager 任务。OneTimeWorkRequest 是用于调度一次性执行的任务,setConstraints 为任务设置执行约束,setBackoffCriteria 设置重试策略,enqueueUniqueWork 方法专用于调度一次性的唯一任务,能确保同一时刻只有一个具有特定名称的任务实例在执行,有效避免重复调度的问题。

周期性任务可以使用 PeriodicWorkRequest,这里没有使用是便于控制唯一任务

public class ReportScheduler {// 我们现在只需要一个唯一的任务名称,因为所有任务都是同一个类型private static final String UPLOAD_WORK_NAME = "device_info_upload_work";/*** 调度一个立即执行的上报任务。* 用于开机或网络变化。*/public static void scheduleInitialReport(Context context) {Constraints constraints = new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build();OneTimeWorkRequest uploadWorkRequest =new OneTimeWorkRequest.Builder(UploadWorker.class).setConstraints(constraints).setBackoffCriteria(BackoffPolicy.LINEAR,30, TimeUnit.MINUTES).build();// 使用 REPLACE 策略:如果有一个延迟的任务正在等待,// 而此时网络发生了变化,那么应该取消那个等待的任务,立即执行一次新的。WorkManager.getInstance(context).enqueueUniqueWork(UPLOAD_WORK_NAME,ExistingWorkPolicy.REPLACE,uploadWorkRequest);}/*** 调度一个在24小时后执行的上报任务。* 由 UploadWorker 成功后自己调用,形成链条。*/public static void scheduleNextDelayedReport(Context context) {Constraints constraints = new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build();OneTimeWorkRequest uploadWorkRequest =new OneTimeWorkRequest.Builder(UploadWorker.class).setConstraints(constraints)// *** 延迟24小时 ***.setInitialDelay(24, TimeUnit.HOURS).setBackoffCriteria(BackoffPolicy.LINEAR,30, TimeUnit.MINUTES).build();// 同样使用 REPLACE 策略,确保任务链的唯一性WorkManager.getInstance(context).enqueueUniqueWork(UPLOAD_WORK_NAME,ExistingWorkPolicy.REPLACE,uploadWorkRequest);}
}

3.3 保存网络环境状态

关于网络环境变更这一具体场景,例如:

  • 设备一直连接着 Wi-Fi A。
  • 用户关闭 Wi-Fi,设备自动切换到 4G/5G 移动网络。
  • 用户走到另一个地方,设备从 4G 切换到 Wi-Fi B。

每次监听到连接状态下网络时候,获取当前活动网络的唯一标识信息(例如 Wi-Fi 的 SSID+BSSID 的信息组合,移动网络的 MCC+MNC+网络类型 的信息组合),并保存到 SharedPreferences。后面就可以从 SharedPreferences 中读取上一次保存的网络标识比较:如果当前标识和保存的标识不同,或者之前没有保存过标识,则说明网络环境发生了变化。

public class NetworkStateHelper {private static final String PREFS_NAME = "network_state_prefs";private static final String KEY_LAST_NETWORK_ID = "last_network_id";// 获取当前网络的唯一标识符public static String getCurrentNetworkId(Context context) {ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);if (cm == null) return "NO_CONNECTIVITY_MANAGER";Network activeNetwork = cm.getActiveNetwork();if (activeNetwork == null) return "NO_ACTIVE_NETWORK";NetworkCapabilities caps = cm.getNetworkCapabilities(activeNetwork);if (caps == null) return "NO_NETWORK_CAPABILITIES";if (caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);if (wifiManager != null) {WifiInfo connectionInfo = wifiManager.getConnectionInfo();// Wi-Fi的BSSID (路由器MAC地址) 是一个很好的唯一标识return "WIFI_" + (connectionInfo != null ? connectionInfo.getBSSID() : "UNKNOWN_BSSID");}} else if (caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);if (tm != null) {String networkOperator = tm.getNetworkOperator();if (!TextUtils.isEmpty(networkOperator)) {// 移动网络的 "MCC+MNC (移动国家码+移动网络码)+网络类型" 可以作为标识//CELLULAR_46000_13return "CELLULAR_" + networkOperator + "_" + tm.getDataNetworkType();} else {// 移动网络的 "网络运营商+网络类型" 可以作为标识 (getNetworkOperatorName会随系统语言的改变而返回不同的字符串)//CELLULAR_中国移动_13 或 CELLULAR_China Mobile_13return "CELLULAR_" + tm.getNetworkOperatorName() + "_" + tm.getDataNetworkType();}}}return "UNKNOWN_NETWORK_TYPE";}// 读取上次保存的网络标识public static String getLastNetworkId(Context context) {SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);return prefs.getString(KEY_LAST_NETWORK_ID, null);}// 保存当前的网络标识public static void saveCurrentNetworkId(Context context, String networkId) {SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);SharedPreferences.Editor editor = prefs.edit();editor.putString(KEY_LAST_NETWORK_ID, networkId);editor.apply();}
}

3.4 实现网络变化接收器

当网络变化时候,首先判断是否连接状态。若为连接状态,则判断是否首次开机或变量变化。默认启动应用时即是首次开机,通过设置变量 isFirstConnectionAfterBoot = true,第一次启动时则上报设备信息,完成就置为 false,后面就只有网络变化影响上报规则了,对比网络环境状态来判断是否上报设备信息。

public class NetworkChangeReceiver extends BroadcastReceiver {private static final String TAG = "NetworkChangeReceiver";public static final String NO_CONNECTION = "NO_CONNECTION";public static volatile boolean isFirstConnectionAfterBoot = true;@Overridepublic void onReceive(Context context, Intent intent) {ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);NetworkInfo activeNetwork = cm.getActiveNetworkInfo();boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting();if (isConnected) {if (isFirstConnectionAfterBoot) {LogUtil.i(TAG, "First boot! report device info");ReportScheduler.scheduleInitialReport(context);saveCurrentNetworkSignature(context, getCurrentNetworkSignature(context));isFirstConnectionAfterBoot = false;} else {String currentSignature = getCurrentNetworkSignature(context);String lastSignature = getLastNetworkSignature(context);LogUtil.d(TAG, "Network Connected! currentSignature: " + currentSignature + ", lastSignature: " + lastSignature);if (!currentSignature.equals(NO_CONNECTION) && !currentSignature.equals(lastSignature)) {LogUtil.i(TAG, "Network Changed! report device info");ReportScheduler.scheduleInitialReport(context);saveCurrentNetworkSignature(context, currentSignature);} else {LogUtil.i(TAG, "Network Reconnected! not report device info");}}} else {LogUtil.i(TAG, "Network Disconnected!");}}
}

文章转载自:

http://lzwYsig4.qypjk.cn
http://37v7t3T8.qypjk.cn
http://hvv5HaA3.qypjk.cn
http://q4Us46jr.qypjk.cn
http://kO19nk1c.qypjk.cn
http://ib9kbBZl.qypjk.cn
http://eURapzFG.qypjk.cn
http://dWy50xEi.qypjk.cn
http://VyWsJ3Et.qypjk.cn
http://ZU07zkSq.qypjk.cn
http://tbqJW55k.qypjk.cn
http://mTK1rKdv.qypjk.cn
http://7C0zqeHe.qypjk.cn
http://bKwtVExi.qypjk.cn
http://Ys9NpbuI.qypjk.cn
http://Pt9H37e3.qypjk.cn
http://7FWTVME9.qypjk.cn
http://McateLrb.qypjk.cn
http://ohdYWazD.qypjk.cn
http://MniZASZw.qypjk.cn
http://WwBE3q2v.qypjk.cn
http://xHL5a1uV.qypjk.cn
http://kS3nxO7g.qypjk.cn
http://4rbFfe8e.qypjk.cn
http://hI3LiXkj.qypjk.cn
http://XGgnJtaG.qypjk.cn
http://VuGEBHWg.qypjk.cn
http://htzh4x2x.qypjk.cn
http://xzSU9EKQ.qypjk.cn
http://9qki4TZh.qypjk.cn
http://www.dtcms.com/a/379416.html

相关文章:

  • python项目批量安装包和生成requirements.txt文件
  • 零部件力学测试系统参数
  • 3D Web轻量引擎HOOPS赋能BIM/工程施工:实现超大模型的轻量化加载与高效浏览!
  • Java Web应用的安全性与防护措施!
  • 填写简历信息
  • 优先算法——专题十一:字符串
  • [Spring Cloud][3]从零开始简单工程搭建实践详解,远程调用
  • 为什么要显示调用析构函数
  • MySQL 数据完整性与约束:从基础到实战,守护数据准确性
  • Python中的“占位符”艺术:深入理解pass关键字的妙用
  • 构建企业级Python离线包仓库:从下载到服务部署全流程指南
  • C++面向对象之多态
  • 个人自留笔记——git操作
  • 命令模式,餐厅订单管理系统C++
  • Android EDLA测试命令总结
  • opencv基础实践;银行卡号识别
  • 【录屏软件】 实用工具推荐——电脑录屏软件班迪(Bandicam)录屏图文安装指南
  • 微服务事务管理实践与 Seata 框架解析
  • 今日行情明日机会——20250911
  • P4105 [HEOI2014] 南园满地堆轻絮
  • Docker 命令核心语法、常用命令
  • Windows安装Chroma DB
  • 60_基于深度学习的羊群计数统计系统(yolo11、yolov8、yolov5+UI界面+Python项目源码+模型+标注好的数据集)
  • Linux 命令 top、vmstat、iostat、free、iftop 正常用法和退出.
  • 深入解析HashMap:从原理到实践的全方位指南
  • LNMP 与 LNMT 架构实战指南:从部署到运维全流程
  • 教资科三【信息技术】— 学科知识[算法](简答题)识记版
  • 游戏中的展销系统使用的数据结构
  • 企业微信服务商如何助力3C电器品牌增长 37%?数据与案例拆解
  • Python采集京东店铺所有商品数据,json数据返回