android WorkManger 使用攻略

本文介绍了 Android JetPack 中用于执行后台任务的使用攻略,包括导入依赖库、简单执行、添加配置选项、监听执行情况、关联多任务、取消未完成任务、执行周期性和特殊任务以及自定义初始化参数等内容,还给出了参考链接。

WorkManager 使用攻略

WorkManagerAndroid JetPack 的一部分。是用于执行后台任务的。

导入依赖库

对应的库:implementation "android.arch.work:work-runtime:1.0.1"

最简单的WorkerManager

一个简单的 WorkManger 执行,需要包含一个Worker, 一个WorkRequest.
比如:

public class MouseWorker extends Worker {
    public MouseWorker(@NonNull Context context, 
                       @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @NonNull
    @Override
    public Result doWork() {
        // 子线程
        LogUtils.e(Thread.currentThread().getName());
        return Result.success();
    }
}
// 执行该  worker
WorkManager.getInstance().enqueue(new OneTimeWorkRequest.Builder(MouseWorker.class).build());

这里的 doWork()的返回值Result,除了 Result.success();还有 失败与重试两种不同的结果。

添加一些配置选项
// Create a Constraints object that defines when the task should run
Constraints constraints = new Constraints.Builder()
    .setRequiresDeviceIdle(true)
    .setRequiresCharging(true)
     .build();

// ...then create a OneTimeWorkRequest that uses those constraints
OneTimeWorkRequest compressionWork =
                new OneTimeWorkRequest.Builder(CompressWorker.class)
     .setConstraints(constraints) // 添加约束
     .build();

OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadWorker.class)
        .setInitialDelay(10, TimeUnit.MINUTES) // 延时执行
        .build();

这里的 Constraints提供了好几个 set方法,比如,是不是充电中这些。

监听Worker的执行情况
WorkManager.getInstance().getWorkInfoByIdLiveData(uploadWorkRequest.getId())
        .observe(lifecycleOwner, new Observer<WorkInfo>() {
            @Override
            public void onChanged(@Nullable WorkInfo workInfo) {
              if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) {
                  displayMessage("Work finished!")
              }
            }
        });
//  lifecycleOwner 是一个接口, AppCompatActivity, Fragment 都实现了这个接口
// 所以,如果是在 activity 里面监听,可以直接传入 this.
关联多个任务一起执行
WorkManager.getInstance()
    // Candidates to run in parallel
    .beginWith(Arrays.asList(filter1, filter2, filter3))  // 这里的 1,2,3 会被并行执行
    // Dependent work (only runs after all previous work in chain)
    .then(compress) // 然后执行该任务
    .then(upload) // 然后执行该任务
    // Don't forget to enqueue()
    .enqueue();

这个有点类型很多 pipline工具里面配置,可以并行,可以串行。可以先串行 1,2,3然后并行4,5,最后再串行一个 6.

取消一个未完成的任务
WorkManager.cancelWorkById(workRequest.getId());

// 还有好几个cancleXXX() 的方法,参数不同。
执行周期性任务
Constraints constraints = new Constraints.Builder()
        .setRequiresCharging(true)
        .build();

PeriodicWorkRequest saveRequest =
        new PeriodicWorkRequest.Builder(SaveImageFileWorker.class, 1, TimeUnit.HOURS)
                  .setConstraints(constraints)
                  .build();

WorkManager.getInstance()
    .enqueue(saveRequest);
执行“特殊”任务
OneTimeWorkRequest oneWorkRequest = new OneTimeWorkRequest.Builder(OneWorker.class)
                            .setInputMerger(ArrayCreatingInputMerger.class).build();
                    String uniqueWorkName = "unique";
                    WorkManager.getInstance()
                            .enqueueUniqueWork(uniqueWorkName, ExistingWorkPolicy.APPEND,
                                    oneWorkRequest);

这里的“特殊”任务,特殊的地方就是需要一个新的参数:ExistingWorkPolicy ,这是一个枚举值。分别代表不同的含义。如果上一个名字相同的特殊任务没有执行完成,那么现在要怎么做? 3个值见名知意,非常容易理解。

特殊任务也是可以串行与并行多个的。调用方式是
WorkManager.getInstance().beginUniqueWork(xxx) ...;
WorkManager.getInstance().enqueueUniqueWork(xx)...

自定义WorkManager初始化参数
  1. 去掉默认初始值:
        <provider
            android:name="androidx.work.impl.WorkManagerInitializer"
            android:authorities="${applicationId}.workmanager-init"
            tools:node="remove" />
  1. 自定义自己的初始值:
public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Configuration configuration = new Configuration.Builder()
                .setExecutor(Executors.newFixedThreadPool(8)) // 设置 Workmanager 的后台线程
                .build();
        WorkManager.initialize(this, configuration);
    }
}

// 官方文档 的操作,可是我本地死活找不到 Configuration.Provider 这个类
class MyApplication extends Application implements Configuration.Provider {
    @Override
    public Configuration getWorkManagerConfiguration() {
        return Configuration.Builder()
                .setMinimumLoggingLevel(android.util.Log.INFO)
                .build();
    }
}

ps: 从文档来看,WorkerManagerJobScheduler 没有直接的关联。使用上,也不一定要互相关联。

参考链接:

非常好,我们现在明确目标: --- ## ✅ 需求总结 使用 **App Widget(桌面小组件)** 来: 1. 触发一个通过 `WorkManager` 执行的 **超过 10 分钟的长任务**; 2. 测试该任务是否会被系统杀死(尤其是在锁屏后); 3. 检测任务在运行过程中是否会被限制联网(如 Doze 模式触发); 4. 实时记录并展示:任务执行时长、联网状态、最终结果。 --- 我们将构建一个完整的解决方案,包含: - 小组件 UI 显示状态 - 点击启动长任务 - 使用 `ListenableWorker` + 前台通知防止被杀 - 定期尝试联网测试网络可用性 - 记录日志到 `SharedPreferences` - 锁屏后观察行为(Doze 影响) --- ### ✅ 1. 创建长任务 Worker:`LongNetworkWorker.kt` ```kotlin // LongNetworkWorker.kt import android.content.Context import android.content.SharedPreferences import androidx.work.* import java.net.URL import java.util.* class LongNetworkWorker(appContext: Context, params: WorkerParameters) : ListenableWorker(appContext, params) { companion object { private const val TAG = "LongNetworkWorker" const val PREFS_NAME = "LongTaskStatus" const val KEY_START_TIME = "start_time" const val KEY_LAST_CHECK = "last_check" const val KEY_NETWORK_OK_COUNT = "network_ok_count" const val KEY_NETWORK_FAIL_COUNT = "network_fail_count" const val KEY_STATUS = "status" // running, success, failed const val CHANNEL_ID = "long_work_channel" const val NOTIFICATION_ID = 2001 } private val prefs: SharedPreferences = appContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) private var isStopped = false override fun startWork(): ListenableFuture<Result> { return try { val future = SettableFuture.create<Result>() Thread { try { val startTime = System.currentTimeMillis() Log.d(TAG, "长任务开始于: $startTime") // 保存开始时间 prefs.edit() .putLong(KEY_START_TIME, startTime) .putString(KEY_STATUS, "running") .putInt(KEY_NETWORK_OK_COUNT, 0) .putInt(KEY_NETWORK_FAIL_COUNT, 0) .apply() // 提升为前台任务(避免被杀) setForeground(createForegroundInfo()).result.get() var elapsedMinutes = 0 while (elapsedMinutes < 12 && !isStopped) { // 跑 12 分钟 val minuteStart = System.currentTimeMillis() // 每分钟做一次联网检查 val networkOk = tryNetworkRequest() recordNetworkResult(networkOk) val now = Date().toString() prefs.edit() .putString(KEY_LAST_CHECK, now) .apply() Log.d(TAG, "第 ${++elapsedMinutes} 分钟 - 联网: $networkOk | 时间: $now") // 等待 60 秒,分 60 次检查中断信号 for (i in 1..60) { if (isStopped) break Thread.sleep(1000) } } if (isStopped) { Log.w(TAG, "任务被提前终止") prefs.edit().putString(KEY_STATUS, "failed").apply() future.set(Result.failure()) } else { Log.d(TAG, "✅ 成功完成 12 分钟长任务") prefs.edit().putString(KEY_STATUS, "success").apply() future.set(Result.success()) } } catch (e: Exception) { Log.e(TAG, "任务异常中断", e) prefs.edit().putString(KEY_STATUS, "crashed").apply() future.set(Result.failure()) } }.start() future } catch (e: Exception) { Log.e(TAG, "无法启动任务", e) val result = SettableFuture.create<Result>() result.set(Result.failure()) result } } override fun stopWork() { Log.d(TAG, "stopWork() 被调用") isStopped = true } private fun tryNetworkRequest(): Boolean = try { URL("https://httpbin.org/get?ts=${System.currentTimeMillis()}").readText() true } catch (e: Exception) { Log.w(TAG, "联网失败: ${e.message}") false } private fun recordNetworkResult(success: Boolean) { with(prefs.edit()) { val key = if (success) KEY_NETWORK_OK_COUNT else KEY_NETWORK_FAIL_COUNT putInt(key, prefs.getInt(key, 0) + 1) apply() } } private fun createForegroundInfo(): ForegroundInfo { val notification = Notification.Builder(applicationContext, CHANNEL_ID) .setContentTitle("后台任务运行中") .setContentText("已运行 ${getCurrentDuration()} 分钟") .setSmallIcon(android.R.drawable.ic_dialog_info) .setOngoing(true) .build() return ForegroundInfo(NOTIFICATION_ID, notification) } private fun getCurrentDuration(): Int { val start = prefs.getLong(KEY_START_TIME, 0) return if (start == 0L) 0 else ((System.currentTimeMillis() - start) / 60_000).toInt() } } ``` --- ### ✅ 2. 创建 AppWidget 布局:`res/layout/widget_layout.xml` ```xml <!-- res/layout/widget_layout.xml --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="12dp" android:background="@android:color/holo_blue_bright"> <TextView android:id="@+id/tv_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="长任务监控" android:textStyle="bold" android:textSize="16sp" android:textColor="#FFFFFF" /> <TextView android:id="@+id/tv_status" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="状态: 空闲" android:textColor="#FFFFFF" android:layout_marginTop="4dp" /> <TextView android:id="@+id/tv_duration" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="已运行: 0 分钟" android:textColor="#FFFFFF" android:layout_marginTop="2dp" /> <TextView android:id="@+id/tv_network" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="联网成功: 0 次" android:textColor="#FFFFFF" android:layout_marginTop="2dp" /> <Button android:id="@+id/btn_start" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="启动12分钟任务" android:textColor="#FFFFFF" android:background="?android:attr/selectableItemBackgroundBorderless" android:layout_marginTop="8dp" /> </LinearLayout> ``` --- ### ✅ 3. 创建 BroadcastReceiver 处理点击事件 ```kotlin // WidgetClickReceiver.kt import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkManager class WidgetClickReceiver : BroadcastReceiver() { companion object { const val ACTION_START_WORK = "ACTION_START_WORK" } override fun onReceive(context: Context, intent: Intent?) { if (intent?.action == ACTION_START_WORK) { val work = OneTimeWorkRequestBuilder<LongNetworkWorker>().build() WorkManager.getInstance(context).enqueue(work) } } } ``` --- ### ✅ 4. 创建 Widget Provider 更新界面 ```kotlin // TaskWidgetProvider.kt import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProvider import android.content.Context import android.content.Intent import android.widget.RemoteViews import androidx.core.content.ContextCompat class TaskWidgetProvider : AppWidgetProvider() { override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { for (appWidgetId in appWidgetIds) { updateAppWidget(context, appWidgetManager, appWidgetId) } } override fun onEnabled(context: Context) { createNotificationChannel(context) } companion object { fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) { val prefs = context.getSharedPreferences(LongNetworkWorker.PREFS_NAME, Context.MODE_PRIVATE) val status = prefs.getString(LongNetworkWorker.KEY_STATUS, "idle") val duration = prefs.getLong(LongNetworkWorker.KEY_START_TIME, 0).let { if (it == 0L) 0 else (System.currentTimeMillis() - it) / 60_000 } val okCount = prefs.getInt(LongNetworkWorker.KEY_NETWORK_OK_COUNT, 0) val failCount = prefs.getInt(LongNetworkWorker.KEY_NETWORK_FAIL_COUNT, 0) val views = RemoteViews(context.packageName, R.layout.widget_layout).apply { setTextViewText(R.id.tv_status, "状态: $status") setTextViewText(R.id.tv_duration, "已运行: $duration 分钟") setTextViewText(R.id.tv_network, "联网成功: $okCount 次") // 设置按钮点击 val intent = Intent(context, WidgetClickReceiver::class.java) intent.action = WidgetClickReceiver.ACTION_START_WORK val pendingIntent = PendingIntent.getBroadcast( context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) setOnClickPendingIntent(R.id.btn_start, pendingIntent) } appWidgetManager.updateAppWidget(appWidgetId, views) } private fun createNotificationChannel(context: Context) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { val channel = NotificationChannel( LongNetworkWorker.CHANNEL_ID, "长任务通知", NotificationManager.IMPORTANCE_LOW ).apply { description = "用于保持长任务不被杀死" } ContextCompat.getSystemService(context, NotificationManager::class.java) ?.createNotificationChannel(channel) } } } } ``` --- ### ✅ 5. 清单文件注册组件 ```xml <!-- AndroidManifest.xml --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <application ... > <receiver android:name=".TaskWidgetProvider"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/widget_info" /> </receiver> <receiver android:name=".WidgetClickReceiver" /> <service android:name=".LongNetworkWorker" android:exported="false" android:permission="android.permission.BIND_JOB_SERVICE" /> </application> ``` --- ### ✅ 6. `res/xml/widget_info.xml` ```xml <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="250dp" android:minHeight="80dp" android:updatePeriodMillis="0" android:initialLayout="@layout/widget_layout" android:resizeMode="horizontal|vertical" android:widgetCategory="home_screen"> </appwidget-provider> ``` --- ## 🔍 实验步骤与预期现象 | 步骤 | 操作 | 观察点 | |------|------|--------| | 1 | 添加小组件 | 显示“状态: idle” | | 2 | 点击按钮启动任务 | 日志出现“长任务开始” | | 3 | 锁屏等待 10 分钟 | 查看 logcat 是否持续输出 | | 4 | 解锁查看小组件 | “已运行”应接近 12,“联网成功”次数变化 | | 5 | 检查是否有断网 | 若某分钟后 `networkOk=false` → 可能进入 Doze | | 6 | 是否存活? | 若最终显示 `success` → 未被杀;若中断 → 被系统杀死 | --- ## 📊 结果分析建议 | 条件 | 是否存活 | 是否联网受限 | 原因 | |------|----------|----------------|------| | 不锁屏 + 无省电 | ✅ 存活 | ❌ 无限制 | 正常运行 | | 锁屏 + AOSP 原生系统 | ✅ 存活(有前台通知) | ⚠️ 后期可能节流 | Doze 晚期阶段限制网络 | | 锁屏 + 小米/华为 | ❌ 很可能被杀 | ❌ 联网也被禁 | 厂商电池优化太激进 | | 无 `setForeground()` | ❌ 几分钟内被杀 | N/A | 缺少前台服务保活 | --- ## ✅ 关键结论 1. **只要有 `setForeground()` + 通知,AOSP 设备上 10+ 分钟任务可以跑完**。 2. **锁屏后联网可能被延迟或阻止(尤其在 Doze deep idle)**。 3. **国产 ROM 必须手动关闭电池优化才能保证存活**。 4. **小组件是理想的非侵入式监控工具**,无需打开 App 即可查看状态。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值