JobScheduler与WorkManager:任务调度机制

Android 15 核心子系统系列 - 第25篇

本篇深入分析Android的任务调度系统,理解JobScheduler的约束控制机制和WorkManager的封装实现,掌握后台任务的最佳实践。

引言

想象一个场景:你的应用需要在WiFi连接电量充足设备空闲时上传日志到服务器。如果自己实现,需要监听网络变化、电池状态、空闲检测...代码会非常复杂。

这就是为什么Android提供了JobScheduler------一个智能的任务调度系统,让你用简单的API描述"什么条件下执行什么任务",系统自动帮你处理所有复杂的条件判断和优化。

WorkManager则是Google推荐的现代化封装,它在JobScheduler基础上提供了更友好的API、自动降级支持和协程集成。

在上一篇通知管理中,我们看到Android如何管理前台交互;今天我们将看到,Android如何智能调度后台任务,在保证功能的同时最大化省电。

一、JobScheduler整体架构

1.1 架构设计哲学

JobScheduler的设计遵循几个核心原则:

延迟执行 :非紧急任务延迟到合适时机批量执行 约束控制 :只在满足所有约束条件时才执行任务 系统优化 :自动合并同类任务,减少设备唤醒次数 电池友好:与Doze模式、App Standby深度集成

1.2 五层架构

核心流程

  1. 应用层:通过JobInfo描述任务和约束
  2. Framework层:JobScheduler作为Binder代理
  3. System Server层:JobSchedulerService核心调度逻辑
  4. Controllers层:各种约束控制器监听系统状态
  5. Execution层:满足条件时通过AMS启动应用执行任务

1.3 核心组件分析

JobStore - 持久化存储

java 复制代码
// frameworks/base/services/core/java/com/android/server/job/JobStore.java
public class JobStore {
    // XML文件存储任务信息
    private static final String XML_TAG_JOB = "job";

    // 持久化JobStatus到磁盘
    public void writePersistentJobs() {
        synchronized (mLock) {
            for (JobStatus job : mJobSet.getAllJobs()) {
                if (job.isPersisted()) {
                    writeJobToXml(job);
                }
            }
        }
    }

    // 启动时恢复任务
    public ArraySet<JobStatus> readJobMapFromDisk() {
        ArraySet<JobStatus> jobs = new ArraySet<>();
        File jobsFile = new File(JOB_PERSIST_DIR, "jobs.xml");
        if (jobsFile.exists()) {
            jobs.addAll(parseJobsFromXml(jobsFile));
        }
        return jobs;
    }
}

特点

  • 使用XML格式存储在 /data/system/job/jobs.xml
  • 设备重启后自动恢复未完成任务
  • 支持任务持久化标志(JobInfo.setPersisted())

JobConcurrencyManager - 并发控制

java 复制代码
// frameworks/base/services/core/java/com/android/server/job/JobConcurrencyManager.java
public class JobConcurrencyManager {
    // 最大并发任务数
    private static final int MAX_REGULAR_JOB_COUNT = 16;
    private static final int MAX_BGUSER_IMPORTANT_JOB_COUNT = 4;

    // 任务分配策略
    void assignJobsToContextsLocked() {
        // 1. 优先级排序
        List<JobStatus> jobs = getPendingJobs();
        Collections.sort(jobs, mJobComparator);

        // 2. 检查可用执行上下文
        for (JobServiceContext context : mActiveServices) {
            if (!context.isAvailable()) continue;

            // 3. 匹配最高优先级任务
            JobStatus job = findNextJobLocked(jobs);
            if (job != null) {
                context.executeRunnableJob(job);
                jobs.remove(job);
            }
        }
    }
}

Android 15优化

  • 根据设备性能动态调整并发数
  • 前台应用任务优先级提升
  • 低内存时自动降低并发数

二、约束控制器(Controllers)机制

2.1 Controller基类设计

java 复制代码
// frameworks/base/services/core/java/com/android/server/job/controllers/StateController.java
public abstract class StateController {
    protected final JobSchedulerService mService;

    // 当系统状态改变时调用
    public abstract void maybeStartTrackingJobLocked(JobStatus jobStatus);
    public abstract void maybeStopTrackingJobLocked(JobStatus jobStatus);

    // 通知JobSchedulerService状态变化
    protected void onControllerStateChanged() {
        mService.onControllerStateChanged();
    }
}

2.2 核心约束控制器详解

BatteryController - 电池约束

java 复制代码
// frameworks/base/services/core/java/com/android/server/job/controllers/BatteryController.java
public class BatteryController extends StateController {
    private boolean mBatteryNotLow;  // 电量非低电
    private boolean mCharging;       // 充电中

    @Override
    public void maybeStartTrackingJobLocked(JobStatus jobStatus) {
        if (jobStatus.hasBatteryNotLowConstraint() ||
            jobStatus.hasChargingConstraint()) {
            mTrackedJobs.add(jobStatus);
            // 立即检查当前状态
            updateStatusLocked(jobStatus);
        }
    }

    private void updateStatusLocked(JobStatus jobStatus) {
        boolean satisfied = true;

        // 检查充电约束
        if (jobStatus.hasChargingConstraint()) {
            satisfied &= mCharging;
        }

        // 检查电量约束
        if (jobStatus.hasBatteryNotLowConstraint()) {
            satisfied &= mBatteryNotLow;
        }

        if (jobStatus.setBatteryConstraintSatisfied(satisfied)) {
            onControllerStateChanged();  // 触发重新调度
        }
    }

    // 监听电池状态变化
    private final BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
                int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
                int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);

                mCharging = (status == BatteryManager.BATTERY_STATUS_CHARGING);
                mBatteryNotLow = (level > LOW_BATTERY_THRESHOLD);

                // 更新所有跟踪任务
                for (JobStatus job : mTrackedJobs) {
                    updateStatusLocked(job);
                }
            }
        }
    };
}

约束条件

  • BATTERY_NOT_LOW:电量 > 15%
  • CHARGING:正在充电(包括USB/AC/无线)

ConnectivityController - 网络约束

java 复制代码
// frameworks/base/services/core/java/com/android/server/job/controllers/ConnectivityController.java
public class ConnectivityController extends StateController {
    private final ConnectivityManager mConnectivityManager;

    @Override
    public void maybeStartTrackingJobLocked(JobStatus jobStatus) {
        if (jobStatus.hasConnectivityConstraint()) {
            NetworkRequest request = jobStatus.getJob().getRequiredNetwork();

            // 注册网络回调
            mConnectivityManager.registerNetworkCallback(
                request,
                new JobNetworkCallback(jobStatus)
            );
        }
    }

    private class JobNetworkCallback extends NetworkCallback {
        private final JobStatus mJobStatus;

        @Override
        public void onAvailable(Network network) {
            // 检查网络是否满足要求
            NetworkCapabilities caps =
                mConnectivityManager.getNetworkCapabilities(network);

            boolean satisfied = evaluateNetworkConstraints(mJobStatus, caps);
            if (mJobStatus.setConnectivityConstraintSatisfied(satisfied)) {
                onControllerStateChanged();
            }
        }

        @Override
        public void onLost(Network network) {
            mJobStatus.setConnectivityConstraintSatisfied(false);
            onControllerStateChanged();
        }
    }

    private boolean evaluateNetworkConstraints(
            JobStatus job, NetworkCapabilities caps) {
        NetworkRequest request = job.getJob().getRequiredNetwork();

        // 检查网络类型
        if (request.hasCapability(NET_CAPABILITY_NOT_METERED)) {
            // 要求非计费网络(WiFi)
            return caps.hasCapability(NET_CAPABILITY_NOT_METERED);
        }

        if (request.hasCapability(NET_CAPABILITY_VALIDATED)) {
            // 要求已验证网络(能上网)
            return caps.hasCapability(NET_CAPABILITY_VALIDATED);
        }

        return true;  // 任意网络即可
    }
}

网络约束类型(Android 15增强):

  • NETWORK_TYPE_NONE:无需网络
  • NETWORK_TYPE_ANY:任意网络连接
  • NETWORK_TYPE_UNMETERED:非计费网络(WiFi)
  • NETWORK_TYPE_NOT_ROAMING:非漫游网络
  • NETWORK_TYPE_CELLULAR:蜂窝网络(Android 15新增)

IdleController - 空闲约束

java 复制代码
// frameworks/base/services/core/java/com/android/server/job/controllers/IdleController.java
public class IdleController extends StateController {
    private boolean mIdleState;  // 设备空闲状态

    IdleController(JobSchedulerService service) {
        super(service);

        // 监听Doze模式的IDLE状态
        IntentFilter filter = new IntentFilter();
        filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
        mContext.registerReceiver(mIdleReceiver, filter);
    }

    private final BroadcastReceiver mIdleReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            PowerManager pm = context.getSystemService(PowerManager.class);
            boolean idle = pm.isDeviceIdleMode();

            if (mIdleState != idle) {
                mIdleState = idle;
                updateAllTrackedJobsLocked();
            }
        }
    };

    private void updateAllTrackedJobsLocked() {
        for (JobStatus job : mTrackedJobs) {
            if (job.setIdleConstraintSatisfied(mIdleState)) {
                onControllerStateChanged();
            }
        }
    }
}

触发条件

  • 屏幕关闭 + 无充电 + 静止超过30分钟(进入Doze IDLE状态)
  • 主要用于低优先级后台同步任务

TimeController - 时间约束

java 复制代码
// frameworks/base/services/core/java/com/android/server/job/controllers/TimeController.java
public class TimeController extends StateController {
    private final AlarmManager mAlarmManager;

    @Override
    public void maybeStartTrackingJobLocked(JobStatus jobStatus) {
        if (jobStatus.hasTimingDelayConstraint()) {
            // 设置延迟执行闹钟
            long delayTime = jobStatus.getEarliestRunTime();
            setAlarm(jobStatus, delayTime);
        }

        if (jobStatus.hasDeadlineConstraint()) {
            // 设置截止时间闹钟
            long deadlineTime = jobStatus.getLatestRunTimeElapsed();
            setDeadlineAlarm(jobStatus, deadlineTime);
        }
    }

    private void setAlarm(JobStatus job, long triggerTime) {
        Intent intent = new Intent(ACTION_JOB_DELAY_EXPIRED);
        intent.putExtra(EXTRA_JOB_ID, job.getJobId());

        PendingIntent pi = PendingIntent.getBroadcast(
            mContext, job.getJobId(), intent, 0);

        // 使用非唤醒闹钟,节省电量
        mAlarmManager.set(
            AlarmManager.ELAPSED_REALTIME,  // 不唤醒设备
            triggerTime,
            pi
        );
    }

    private void setDeadlineAlarm(JobStatus job, long deadlineTime) {
        // 截止时间必须执行,使用唤醒闹钟
        mAlarmManager.setExact(
            AlarmManager.ELAPSED_REALTIME_WAKEUP,  // 唤醒设备
            deadlineTime,
            getDeadlinePendingIntent(job)
        );
    }
}

时间约束API

java 复制代码
JobInfo.Builder builder = new JobInfo.Builder(jobId, componentName);

// 最早执行时间(延迟)
builder.setMinimumLatency(5 * 60 * 1000);  // 5分钟后

// 最晚执行时间(截止)
builder.setOverrideDeadline(15 * 60 * 1000);  // 15分钟内必须执行

// 周期性任务
builder.setPeriodic(24 * 60 * 60 * 1000);  // 每24小时

2.3 约束满足判定

java 复制代码
// frameworks/base/services/core/java/com/android/server/job/JobStatus.java
public class JobStatus {
    // 各种约束状态标志
    private boolean mChargingConstraintSatisfied;
    private boolean mBatteryNotLowConstraintSatisfied;
    private boolean mConnectivityConstraintSatisfied;
    private boolean mIdleConstraintSatisfied;
    private boolean mStorageNotLowConstraintSatisfied;

    // 判断是否所有约束都满足
    public boolean isReady() {
        // 1. 检查应用是否被限制(App Standby)
        if (isAppStandbyRestricted()) {
            return false;
        }

        // 2. 检查所有约束
        return (!hasChargingConstraint() || mChargingConstraintSatisfied)
            && (!hasBatteryNotLowConstraint() || mBatteryNotLowConstraintSatisfied)
            && (!hasConnectivityConstraint() || mConnectivityConstraintSatisfied)
            && (!hasIdleConstraint() || mIdleConstraintSatisfied)
            && (!hasStorageNotLowConstraint() || mStorageNotLowConstraintSatisfied)
            && isTimingConstraintSatisfied();  // 时间约束
    }

    private boolean isTimingConstraintSatisfied() {
        long now = SystemClock.elapsedRealtime();

        // 检查最早执行时间
        if (mEarliestRunTimeElapsedMillis > 0 && now < mEarliestRunTimeElapsedMillis) {
            return false;
        }

        // 超过截止时间,强制执行
        if (mLatestRunTimeElapsedMillis > 0 && now >= mLatestRunTimeElapsedMillis) {
            return true;
        }

        return true;
    }
}

三、任务调度与执行流程

3.1 任务注册流程

java 复制代码
// 应用代码示例
class UploadJobService : JobService() {
    override fun onStartJob(params: JobParameters): Boolean {
        // 在后台线程执行任务
        CoroutineScope(Dispatchers.IO).launch {
            try {
                uploadLogs()
                jobFinished(params, false)  // 成功完成
            } catch (e: Exception) {
                jobFinished(params, true)   // 失败,需要重试
            }
        }
        return true  // 返回true表示任务在后台执行
    }

    override fun onStopJob(params: JobParameters): Boolean {
        // 系统要求停止任务(如条件不再满足)
        return true  // 返回true表示需要重新调度
    }
}

// 调度任务
fun scheduleUploadJob(context: Context) {
    val jobScheduler = context.getSystemService(JobScheduler::class.java)

    val jobInfo = JobInfo.Builder(
        UPLOAD_JOB_ID,
        ComponentName(context, UploadJobService::class.java)
    ).apply {
        setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)  // WiFi
        setRequiresCharging(true)                                // 充电中
        setRequiresDeviceIdle(true)                              // 设备空闲
        setPersisted(true)                                       // 重启后保留
        setBackoffCriteria(30000, JobInfo.BACKOFF_POLICY_EXPONENTIAL)  // 失败重试
    }.build()

    val result = jobScheduler.schedule(jobInfo)
    if (result == JobScheduler.RESULT_SUCCESS) {
        Log.d(TAG, "Job scheduled successfully")
    }
}

3.2 任务调度核心逻辑

java 复制代码
// frameworks/base/services/core/java/com/android/server/job/JobSchedulerService.java
public class JobSchedulerService extends SystemService {
    private final JobStore mJobs;
    private final List<StateController> mControllers = new ArrayList<>();

    // 应用调用JobScheduler.schedule()
    public int schedule(JobInfo job) {
        synchronized (mLock) {
            // 1. 创建JobStatus
            JobStatus jobStatus = JobStatus.createFromJobInfo(
                job, callingUid, sourcePkg, userId);

            // 2. 持久化存储
            if (job.isPersisted()) {
                mJobs.add(jobStatus);
            }

            // 3. 通知所有Controller开始跟踪
            for (StateController controller : mControllers) {
                controller.maybeStartTrackingJobLocked(jobStatus);
            }

            // 4. 立即尝试调度
            mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();

            return JobScheduler.RESULT_SUCCESS;
        }
    }

    // Controller状态变化回调
    @Override
    public void onControllerStateChanged() {
        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
    }

    // 检查并运行准备好的任务
    private void maybeRunPendingJobsLocked() {
        // 1. 获取所有准备好的任务
        List<JobStatus> readyJobs = new ArrayList<>();
        for (JobStatus job : mJobs.getJobs()) {
            if (job.isReady()) {
                readyJobs.add(job);
            }
        }

        if (readyJobs.isEmpty()) {
            return;
        }

        // 2. 分配执行上下文
        mConcurrencyManager.assignJobsToContextsLocked(readyJobs);
    }
}

3.3 任务执行流程

java 复制代码
// frameworks/base/services/core/java/com/android/server/job/JobServiceContext.java
public class JobServiceContext {
    private JobStatus mRunningJob;
    private IJobCallback mCallback;

    // 执行任务
    boolean executeRunnableJob(JobStatus job) {
        synchronized (mLock) {
            if (!mAvailable) {
                return false;
            }

            mRunningJob = job;
            mAvailable = false;

            // 1. 绑定JobService
            Intent intent = new Intent();
            intent.setComponent(job.getServiceComponent());

            boolean bound = mContext.bindServiceAsUser(
                intent,
                this,  // ServiceConnection
                Context.BIND_AUTO_CREATE,
                UserHandle.of(job.getUserId())
            );

            if (!bound) {
                return false;
            }

            // 2. 启动超时检测(10分钟)
            mHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MILLIS);

            return true;
        }
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        IJobService jobService = IJobService.Stub.asInterface(service);

        try {
            // 3. 调用JobService.onStartJob()
            jobService.startJob(mRunningJob.getJobParams());
        } catch (RemoteException e) {
            handleJobFinished(false, "remote exception");
        }
    }

    // 任务完成回调
    @Override
    public void jobFinished(int jobId, boolean needsReschedule) {
        synchronized (mLock) {
            mHandler.removeCallbacks(mTimeoutRunnable);

            if (needsReschedule) {
                // 应用请求重新调度(失败)
                mJobSchedulerService.rescheduleJobLocked(mRunningJob);
            } else {
                // 任务成功完成,移除
                mJobSchedulerService.removeJobLocked(mRunningJob);
            }

            // 解绑服务
            mContext.unbindService(this);
            mRunningJob = null;
            mAvailable = true;

            // 尝试运行下一个任务
            mJobSchedulerService.maybeRunPendingJobsLocked();
        }
    }
}

四、WorkManager架构与实现

4.1 WorkManager设计理念

WorkManager是Google推荐的现代任务调度方案,它在JobScheduler基础上提供:

简洁API :Builder模式,链式调用 自动降级 :API 23+用JobScheduler,API 14-22用AlarmManager 协程支持 :CoroutineWorker原生支持协程 链式任务 :支持任务依赖和顺序执行 LiveData观察:实时监听任务状态

4.2 WorkManager核心架构

4.3 WorkManager核心实现

WorkManagerImpl - 单例实现

java 复制代码
// androidx/work/impl/WorkManagerImpl.java
public class WorkManagerImpl extends WorkManager {
    private static WorkManagerImpl sInstance;

    private final WorkDatabase mWorkDatabase;
    private final Processor mProcessor;
    private final Scheduler mScheduler;

    public static WorkManagerImpl getInstance(Context context) {
        if (sInstance == null) {
            synchronized (WorkManagerImpl.class) {
                if (sInstance == null) {
                    sInstance = new WorkManagerImpl(
                        context,
                        new Configuration.Builder().build()
                    );
                }
            }
        }
        return sInstance;
    }

    @Override
    public Operation enqueue(WorkRequest request) {
        // 1. 写入数据库
        WorkSpec workSpec = request.getWorkSpec();
        mWorkDatabase.workSpecDao().insertWorkSpec(workSpec);

        // 2. 调度任务
        mScheduler.schedule(workSpec);

        return new OperationImpl();
    }
}

Scheduler选择策略

java 复制代码
// androidx/work/impl/Schedulers.java
public class Schedulers {
    public static Scheduler createBestAvailableScheduler(Context context) {
        if (Build.VERSION.SDK_INT >= 23) {
            // API 23+ 使用JobScheduler
            return new SystemJobScheduler(context);
        } else {
            // API 14-22 使用AlarmManager
            return new SystemAlarmScheduler(context);
        }
    }
}

SystemJobScheduler - 适配JobScheduler

java 复制代码
// androidx/work/impl/background/systemjob/SystemJobScheduler.java
public class SystemJobScheduler implements Scheduler {
    private final JobScheduler mJobScheduler;

    @Override
    public void schedule(WorkSpec... workSpecs) {
        for (WorkSpec workSpec : workSpecs) {
            JobInfo jobInfo = convertToJobInfo(workSpec);
            mJobScheduler.schedule(jobInfo);
        }
    }

    private JobInfo convertToJobInfo(WorkSpec workSpec) {
        JobInfo.Builder builder = new JobInfo.Builder(
            workSpec.id.hashCode(),
            mJobServiceComponent
        );

        // 转换约束条件
        Constraints constraints = workSpec.constraints;

        if (constraints.requiresCharging()) {
            builder.setRequiresCharging(true);
        }

        if (constraints.requiresDeviceIdle()) {
            builder.setRequiresDeviceIdle(true);
        }

        NetworkType networkType = constraints.getRequiredNetworkType();
        builder.setRequiredNetworkType(convertNetworkType(networkType));

        // 设置触发时间
        if (workSpec.initialDelay > 0) {
            builder.setMinimumLatency(workSpec.initialDelay);
        }

        // 周期性任务
        if (workSpec.isPeriodic()) {
            builder.setPeriodic(workSpec.intervalDuration);
        }

        return builder.build();
    }

    private int convertNetworkType(NetworkType workNetworkType) {
        switch (workNetworkType) {
            case NOT_REQUIRED:
                return JobInfo.NETWORK_TYPE_NONE;
            case CONNECTED:
                return JobInfo.NETWORK_TYPE_ANY;
            case UNMETERED:
                return JobInfo.NETWORK_TYPE_UNMETERED;
            case NOT_ROAMING:
                return JobInfo.NETWORK_TYPE_NOT_ROAMING;
            default:
                return JobInfo.NETWORK_TYPE_NONE;
        }
    }
}

4.4 Worker实现

Worker基类

java 复制代码
// androidx/work/Worker.java
public abstract class Worker extends ListenableWorker {

    @WorkerThread
    public abstract Result doWork();

    @Override
    public final ListenableFuture<Result> startWork() {
        // 在后台线程执行doWork()
        return mWorkExecutor.executeAsync(() -> {
            return doWork();
        });
    }
}

// 结果类型
public abstract static class Result {
    public static Result success() { ... }
    public static Result success(Data outputData) { ... }
    public static Result retry() { ... }  // 重试
    public static Result failure() { ... }
}

CoroutineWorker - 协程支持

kotlin 复制代码
// androidx/work/CoroutineWorker.kt
abstract class CoroutineWorker(
    context: Context,
    params: WorkerParameters
) : ListenableWorker(context, params) {

    // 协程上下文(默认Dispatchers.Default)
    open val coroutineContext: CoroutineContext
        get() = Dispatchers.Default

    // 挂起函数,子类实现
    abstract suspend fun doWork(): Result

    // 封装为ListenableFuture
    final override fun startWork(): ListenableFuture<Result> {
        val future = SettableFuture.create<Result>()

        CoroutineScope(coroutineContext).launch {
            try {
                val result = doWork()
                future.set(result)
            } catch (e: CancellationException) {
                future.cancel(true)
            } catch (e: Exception) {
                future.setException(e)
            }
        }

        return future
    }

    // 取消支持
    final override fun onStopped() {
        super.onStopped()
        future.cancel(true)
    }
}

4.5 WorkManager使用示例

一次性任务

kotlin 复制代码
// 定义Worker
class UploadWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        return try {
            // 从输入数据获取参数
            val fileUri = inputData.getString(KEY_FILE_URI)

            // 执行上传
            uploadFile(fileUri)

            // 返回成功结果
            Result.success()
        } catch (e: IOException) {
            // 网络错误,重试
            Result.retry()
        } catch (e: Exception) {
            // 其他错误,失败
            Result.failure()
        }
    }

    private suspend fun uploadFile(uri: String?) {
        // 显示进度通知
        setForeground(createForegroundInfo())

        // 上传逻辑
        withContext(Dispatchers.IO) {
            // ... 实际上传代码
        }
    }

    private fun createForegroundInfo(): ForegroundInfo {
        val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
            .setContentTitle("上传文件")
            .setContentText("正在上传...")
            .setSmallIcon(R.drawable.ic_upload)
            .build()

        return ForegroundInfo(NOTIFICATION_ID, notification)
    }
}

// 创建并入队任务
fun scheduleUpload(fileUri: String) {
    // 1. 构建输入数据
    val inputData = workDataOf(KEY_FILE_URI to fileUri)

    // 2. 构建WorkRequest
    val uploadRequest = OneTimeWorkRequestBuilder<UploadWorker>()
        .setInputData(inputData)
        .setConstraints(
            Constraints.Builder()
                .setRequiredNetworkType(NetworkType.UNMETERED)  // WiFi
                .setRequiresCharging(true)                      // 充电
                .build()
        )
        .setBackoffCriteria(
            BackoffPolicy.EXPONENTIAL,
            30, TimeUnit.SECONDS  // 失败后30秒重试,指数退避
        )
        .addTag("upload")  // 添加标签便于管理
        .build()

    // 3. 入队
    WorkManager.getInstance(context).enqueue(uploadRequest)

    // 4. 观察任务状态
    WorkManager.getInstance(context)
        .getWorkInfoByIdLiveData(uploadRequest.id)
        .observe(lifecycleOwner) { workInfo ->
            when (workInfo.state) {
                WorkInfo.State.ENQUEUED -> Log.d(TAG, "任务已入队")
                WorkInfo.State.RUNNING -> Log.d(TAG, "任务执行中")
                WorkInfo.State.SUCCEEDED -> Log.d(TAG, "任务成功")
                WorkInfo.State.FAILED -> Log.d(TAG, "任务失败")
                WorkInfo.State.CANCELLED -> Log.d(TAG, "任务取消")
                else -> {}
            }
        }
}

周期性任务

kotlin 复制代码
class SyncWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        return try {
            syncData()
            Result.success()
        } catch (e: Exception) {
            Result.retry()
        }
    }

    private suspend fun syncData() {
        // 同步逻辑
    }
}

// 调度周期性任务
fun scheduleDailySync() {
    val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(
        1, TimeUnit.DAYS  // 每天执行一次
    )
        .setConstraints(
            Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .build()
        )
        .setInitialDelay(2, TimeUnit.HOURS)  // 首次执行延迟2小时
        .build()

    // 使用唯一名称,避免重复调度
    WorkManager.getInstance(context).enqueueUniquePeriodicWork(
        "daily_sync",
        ExistingPeriodicWorkPolicy.KEEP,  // 已存在则保留
        syncRequest
    )
}

链式任务

kotlin 复制代码
fun scheduleImageProcessing(imageUri: String) {
    val downloadWork = OneTimeWorkRequestBuilder<DownloadWorker>()
        .setInputData(workDataOf("uri" to imageUri))
        .build()

    val compressWork = OneTimeWorkRequestBuilder<CompressWorker>()
        .build()

    val uploadWork = OneTimeWorkRequestBuilder<UploadWorker>()
        .build()

    // 链式执行:下载 -> 压缩 -> 上传
    WorkManager.getInstance(context)
        .beginWith(downloadWork)
        .then(compressWork)
        .then(uploadWork)
        .enqueue()
}

五、Doze模式下的任务调度

5.1 Doze对JobScheduler的影响

在Doze模式下,JobScheduler的行为会受到限制:

java 复制代码
// frameworks/base/services/core/java/com/android/server/job/JobSchedulerService.java
private boolean shouldRunInDoze(JobStatus job) {
    // 1. 系统任务不受限制
    if (job.getUid() < Process.FIRST_APPLICATION_UID) {
        return true;
    }

    // 2. 白名单应用可以执行
    DeviceIdleController idleController = LocalServices.getService(
        DeviceIdleController.class);
    if (idleController.isAppIdleWhitelisted(job.getUid())) {
        return true;
    }

    // 3. 设置了IMPORTANT_WHILE_FOREGROUND标志
    if (job.getJob().isImportantWhileForeground() &&
        job.getSourceUid() == mActivityManagerInternal.getForegroundUid()) {
        return true;
    }

    return false;
}

限制策略

  • Light Doze:延迟执行普通任务,仅白名单应用可执行
  • Deep Doze:维护窗口内才能执行任务
  • Maintenance Window:30分钟窗口,指数退避(30min→1h→2h→4h→6h)

5.2 绕过Doze限制的方式

使用白名单

kotlin 复制代码
// 检查是否在白名单
fun isIgnoringBatteryOptimizations(context: Context): Boolean {
    val pm = context.getSystemService(PowerManager::class.java)
    return pm.isIgnoringBatteryOptimizations(context.packageName)
}

// 请求加入白名单(需要用户确认)
fun requestIgnoreBatteryOptimizations(context: Context) {
    val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
        data = Uri.parse("package:${context.packageName}")
    }
    context.startActivity(intent)
}

⚠️ 注意:仅在必要时使用,Google Play审核严格限制白名单权限。

使用setImportantWhileForeground

kotlin 复制代码
val jobInfo = JobInfo.Builder(jobId, componentName)
    .setImportantWhileForeground(true)  // 前台时重要
    .build()

当应用在前台时,Doze不影响该任务。

六、调试与问题诊断

6.1 dumpsys查看任务状态

bash 复制代码
# 查看JobScheduler状态
adb shell dumpsys jobscheduler

# 输出示例
JOB #u0a123/1001 from uid 10123: com.example.app/.UploadJobService
  Source package: com.example.app
  Constraints: CHARGING UNMETERED IDLE TIMING_DELAY
  Required constraints: CHARGING UNMETERED
  Satisfied constraints: CHARGING TIMING_DELAY
  Unsatisfied constraints: UNMETERED IDLE
  Earliest run time: 2026-02-28 15:30:00
  Latest run time: 2026-02-28 16:00:00
  Ready: false (waiting for UNMETERED, IDLE)

# 查看所有待执行任务
adb shell dumpsys jobscheduler | grep "Pending queue"

# 查看WorkManager任务
adb shell dumpsys jobscheduler | grep "androidx.work"

6.2 强制触发任务

bash 复制代码
# 运行指定任务
adb shell cmd jobscheduler run -f <package> <job-id>

# 示例
adb shell cmd jobscheduler run -f com.example.app 1001

# 进入Doze模式测试任务行为
adb shell dumpsys deviceidle force-idle

# 退出Doze模式
adb shell dumpsys deviceidle unforce

# 模拟维护窗口
adb shell dumpsys deviceidle step

6.3 WorkManager调试

kotlin 复制代码
// 开启WorkManager日志
WorkManager.getInstance(context).setConfiguration(
    Configuration.Builder()
        .setMinimumLoggingLevel(Log.DEBUG)
        .build()
)

// 查询任务状态
val workInfo = WorkManager.getInstance(context)
    .getWorkInfoById(workRequest.id)
    .get()

Log.d(TAG, "State: ${workInfo.state}")
Log.d(TAG, "Progress: ${workInfo.progress}")
Log.d(TAG, "Run attempt: ${workInfo.runAttemptCount}")

// 取消任务
WorkManager.getInstance(context).cancelWorkById(workRequest.id)

// 取消所有任务
WorkManager.getInstance(context).cancelAllWork()

6.4 常见问题诊断

任务不执行

症状:任务已调度但从不执行

排查步骤

bash 复制代码
# 1. 检查任务状态
adb shell dumpsys jobscheduler | grep -A 20 "com.example.app"

# 2. 查看未满足的约束
# Unsatisfied constraints: UNMETERED IDLE

# 3. 检查应用是否被限制
adb shell dumpsys deviceidle whitelist
adb shell dumpsys usagestats | grep -A 10 "com.example.app"

# 4. 查看是否在Doze模式
adb shell dumpsys deviceidle

常见原因

  • 约束条件未满足(如要求WiFi但使用移动网络)
  • 应用处于App Standby RESTRICTED bucket
  • 设备在Doze模式且应用不在白名单

任务执行超时

症状:任务被系统强制停止

kotlin 复制代码
class LongRunningWorker(context: Context, params: WorkerParameters)
    : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        // 对于长时间运行的任务,使用setForeground提升为前台任务
        setForeground(createForegroundInfo())

        // 执行耗时操作
        performLongTask()

        return Result.success()
    }
}

解决方案

  • JobScheduler任务限制10分钟,使用setForeground()
  • 将大任务拆分为多个小任务
  • 使用前台服务处理真正长时间运行的任务

七、最佳实践

7.1 选择合适的调度方案

场景 推荐方案 原因
简单后台任务 WorkManager API简洁,自动降级
复杂约束控制 JobScheduler 更底层,控制更精细
周期性同步 WorkManager PeriodicWork 支持灵活时间窗口
即时执行 Foreground Service 不适合JobScheduler
精确定时 AlarmManager JobScheduler不保证精确时间

7.2 约束条件配置建议

kotlin 复制代码
fun scheduleOptimalJob() {
    val constraints = Constraints.Builder()
        // ✅ 优先使用WiFi节省流量
        .setRequiredNetworkType(NetworkType.UNMETERED)

        // ✅ 充电时执行节省电量
        .setRequiresCharging(true)

        // ⚠️ 谨慎使用IDLE约束(可能长时间不满足)
        .setRequiresDeviceIdle(false)

        // ✅ 检查存储空间
        .setRequiresStorageNotLow(true)

        .build()

    val workRequest = OneTimeWorkRequestBuilder<SyncWorker>()
        .setConstraints(constraints)
        .build()

    WorkManager.getInstance(context).enqueue(workRequest)
}

7.3 任务重试策略

kotlin 复制代码
val retryWork = OneTimeWorkRequestBuilder<UploadWorker>()
    .setBackoffCriteria(
        BackoffPolicy.EXPONENTIAL,  // 指数退避
        WorkRequest.MIN_BACKOFF_MILLIS,  // 初始延迟10秒
        TimeUnit.MILLISECONDS
    )
    .build()

// 重试时间:10s -> 20s -> 40s -> 80s -> ... (最长5小时)

7.4 避免电量消耗

kotlin 复制代码
// ❌ 错误:频繁周期性任务
val badWork = PeriodicWorkRequestBuilder<SyncWorker>(
    15, TimeUnit.MINUTES  // 每15分钟一次,太频繁!
).build()

// ✅ 正确:合理的周期 + 合并请求
val goodWork = PeriodicWorkRequestBuilder<SyncWorker>(
    6, TimeUnit.HOURS  // 每6小时一次
)
    .setConstraints(
        Constraints.Builder()
            .setRequiredNetworkType(NetworkType.UNMETERED)
            .setRequiresBatteryNotLow(true)
            .build()
    )
    .build()

7.5 链式任务最佳实践

kotlin 复制代码
// 并行执行多个下载任务,然后合并处理
val download1 = OneTimeWorkRequestBuilder<DownloadWorker>().build()
val download2 = OneTimeWorkRequestBuilder<DownloadWorker>().build()
val download3 = OneTimeWorkRequestBuilder<DownloadWorker>().build()
val merge = OneTimeWorkRequestBuilder<MergeWorker>().build()

WorkManager.getInstance(context)
    .beginWith(listOf(download1, download2, download3))  // 并行
    .then(merge)  // 合并
    .enqueue()

八、Android 15新特性

8.1 改进的网络约束

Android 15增强了网络约束控制:

kotlin 复制代码
// Android 15新增:蜂窝网络约束
val cellularConstraint = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CELLULAR)  // 新增
    .build()

// 结合网络计费判断
val unmeteredOrCellular = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.TEMPORARILY_UNMETERED)  // 临时免费
    .build()

8.2 优化的Doze集成

java 复制代码
// frameworks/base/services/core/java/com/android/server/job/JobSchedulerService.java
// Android 15改进:更智能的维护窗口预测
private long predictNextMaintenanceWindow() {
    // 基于用户使用习惯预测最佳执行时间
    UsageStatsManager usm = getService(UsageStatsManager.class);
    long[] idlePeriods = usm.queryIdlePeriods(
        System.currentTimeMillis() - DAY_IN_MILLIS,
        System.currentTimeMillis()
    );

    // 选择用户通常不使用的时间段
    return calculateOptimalWindow(idlePeriods);
}

8.3 增强的并发控制

java 复制代码
// Android 15:根据设备性能动态调整并发数
private int calculateMaxConcurrentJobs() {
    ActivityManager am = getService(ActivityManager.class);
    long totalRam = am.getMemoryInfo().totalMem;
    int cpuCores = Runtime.getRuntime().availableProcessors();

    // 6GB+内存且8核+: 20个并发
    if (totalRam >= 6 * GB && cpuCores >= 8) {
        return 20;
    }

    // 4GB+内存: 16个并发
    if (totalRam >= 4 * GB) {
        return 16;
    }

    // 默认: 12个并发
    return 12;
}

九、总结

核心要点回顾

  1. JobScheduler架构

    • 五层架构:Application → Framework → System Server → Controllers → Execution
    • 核心组件:JobStore持久化、JobConcurrencyManager并发控制
    • 约束控制器:Battery、Connectivity、Idle、Storage、Time等
  2. 约束机制

    • 每个约束由独立Controller监听系统状态
    • 必须所有约束满足才能执行任务
    • 支持最早执行时间和最晚执行时间(deadline强制执行)
  3. WorkManager封装

    • 自动选择最优调度器(JobScheduler/AlarmManager)
    • 简洁的Builder API和协程支持
    • Room数据库持久化 + LiveData状态观察
  4. Doze模式集成

    • Light/Deep Doze限制任务执行
    • 维护窗口指数退避(30min→6h)
    • 白名单或setImportantWhileForeground绕过限制
  5. 最佳实践

    • 优先使用WorkManager(除非需要精细控制)
    • 合理设置约束条件(WiFi+充电最佳)
    • 使用指数退避重试策略
    • 避免频繁周期性任务
    • 链式任务实现复杂工作流

与其他系统的协作

  • PowerManagerService:Doze模式限制任务执行时机
  • NotificationManagerService:前台Worker显示进度通知
  • ActivityManagerService:启动应用进程执行JobService
  • NetworkPolicyManagerService:网络约束判定

参考源码(基于Android 15 AOSP):

  • frameworks/base/services/core/java/com/android/server/job/
  • frameworks/base/core/java/android/app/job/
  • androidx/work/

调试命令速查

bash 复制代码
# JobScheduler状态
adb shell dumpsys jobscheduler

# 强制运行任务
adb shell cmd jobscheduler run -f <package> <job-id>

# Doze模式测试
adb shell dumpsys deviceidle force-idle
adb shell dumpsys deviceidle step

系列导航


本文基于Android 15 (API Level 35)源码分析,不同厂商的定制ROM可能存在差异。 欢迎来我中的个人主页找到更多有用的知识和有趣的产品

相关推荐
赏金术士1 小时前
Compose 教学项目
android·kotlin·compose
晓梦林2 小时前
ximai靶场学习笔记
android·笔记·学习
十六年开源服务商6 小时前
2026服务器配置优化与WordPress运维实战指南
android·运维·服务器
音视频牛哥8 小时前
大牛直播SDK(SmartMediaKit)Android平台Unity3D RTSP/RTMP播放器集成实践
android·unity3d·rtsp播放器·rtmp播放器·unity3d rtmp播放器·安卓unity rtsp播放器·安卓unity rtmp播放器
w1wi8 小时前
安卓抓包完全指南(一):从入门到 SSL Pinning 绕过
android·网络协议·ssl
aqi0010 小时前
一文理清 HarmonyOS 6.0.2 涵盖的十个升级点
android·华为·harmonyos·鸿蒙·harmony
赏金术士11 小时前
Jetpack Compose 状态提升(State Hoisting)完全指南
android·kotlin·compose
BoomHe11 小时前
git Rebase 为任意一笔提交补上 Change-Id
android·git·android studio
TDengine (老段)11 小时前
TDengine 超级表/子表/普通表 — 设计理念与内部表示
android·大数据·数据库·物联网·时序数据库·tdengine·涛思数据