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可能存在差异。 欢迎来我中的个人主页找到更多有用的知识和有趣的产品

相关推荐
summerkissyou19873 小时前
Android-view-绘制流程及自定义例子
android·app
常利兵3 小时前
Android “解锁”屏幕方向:APP适配新征程
android·gitee
红藕香残玉簟秋6 小时前
【安卓学习】配置开发环境
android·学习
用户69371750013846 小时前
Android R8 深度解析:为什么 Google 用R8取代 ProGuard?
android·android studio·android jetpack
seabirdssss6 小时前
联想拯救者Y7000P上使用ADB无法监听到通过USB连接的安卓设备
android·adb
2501_916008897 小时前
iPhone 上怎么抓 App 的网络请求,在 iOS 设备上捕获网络请求
android·网络·ios·小程序·uni-app·iphone·webview
工业甲酰苯胺7 小时前
PHP闭包中static关键字的核心作用与底层原理解析
android·开发语言·php