Android 9.0 JobScheduler(四) Job约束条件的控制

上一篇文章:JobScheduler源码分析(三) Job从创建到执行

在前两篇文章中,对JobSchedulerService的启动和Job的调度过程大致做了个梳理,通过前几篇的分析我们知道,要使得客户端Job被JSS调度执行,必须满足该Job在创建时所设置的约束,而这些约束何时满足,这将由StateController进行控制,本篇中将对所有StateController类的控制流程进行分析。

通过前面文章的分析得,共有八个StateController的子类,各自单独地记录每个Job的状态,并在Job准备好运行时通知JSS进行调度执行,或者在约束条件不满足时通知JSS停止Job的执行。所有的StateController类有如下共性:

  • 1.在JobSchedulerService的构造方法中进行实例化;
  • 2.每向JSS中加入一个Job,都会调用StateController.maybeStartTrackingJobLocked()方法开始记录跟踪此Job;
  • 3.当Job调度执行完毕后、或当Job被取消时,都会调用StateController.maybeStopTrackingJobLocked()方法停止此Job的记录;

除了以上共同特性外,各个状态控制器中对约束的设置,大部分是通过广播实现的,状态控制器内部会注册一个广播,当广播接收器收到广播后,将根据得到的状态,通过setXXXConstraintSatisfied()方法,对satisfiedConstraints进行按位运算,比如在DeviceIdleJobsController中会调用setDeviceNotDozingConstraintSatisfied(true/false)来设置是否Doze的约束条件满足。第三篇文章分析时说过:JobStatus中有一个全局变量satisfiedConstraints,这个表示该Job当前满足的约束,如果当前状态控制器所检测到的系统状态满足某个约束,则按位或,否则清零即可。最终在判断Job约束条件是否满足时,将拿satisfiedConstraintsrequiredConstraints进行比较,requiredConstraints变量上记录的是创建Job时在JobInfo设置的所有约束条件,也是通过按位或运算进行标记的:

    public boolean isConstraintsSatisfied() {
        //该Job所有的约束条件
        final int req = requiredConstraints & CONSTRAINTS_OF_INTEREST;
        //该Job现在所满足的约束条件
        int sat = satisfiedConstraints & CONSTRAINTS_OF_INTEREST;
        //相等,说明当前所满足约束已是所需所有约束
        return (sat & req) == req;
    }

下面我们就具体来看看,各个状态控制器如何获取并将结果设置给satisfiedConstraints的,这部分逻辑比较清晰易懂,就不再啰哩啰嗦了,点到为止即可。

1. DeviceIdleJobsController

DeviceIdleJobsController用来控制Job对Doze的依赖条件,或者也可以说Doze对Job的限制,当设备进入Doze模式的IDLE状态时,将会限制除了Doze白名单外的所有应用的Job调度,当Doze退出IDLE状态进入维护状态后,将会对所有应用的Job解除限制。而DeviceIdleJobsController中则是通过广播的形式来感知Doze模式的状态变化,在其构造方法中可以看到广播的注册和监听:

    public DeviceIdleJobsController(JobSchedulerService service) {
        super(service);

        // ......
        final IntentFilter filter = new IntentFilter();
        // Deep Doze状态发生改变后发送
        filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
        // Light Doze状态发生改变后发送
        filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
        // 白名单列表发生变化时发送
        filter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
        // i临时白名单列表发生变化时发送
        filter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED);
        mContext.registerReceiverAsUser(
                mBroadcastReceiver, UserHandle.ALL, filter, null, null);
    }

来看看关于广播接收器中的逻辑,其中只看当Doze的状态发生变化的部分:


    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            switch (intent.getAction()) {
                // Deep Doze和Light Doze进入/退出IDLE后发送
                case PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED:
                case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
                    //根据Doze是否进入IDLE状态更新状态
                    updateIdleMode(mPowerManager != null && (mPowerManager.isDeviceIdleMode()
                            || mPowerManager.isLightDeviceIdleMode()));
                    break;
            }
        }
    };

以上逻辑中可以看到,不管是Deep Doze还是Light Doze,当进入或者退出IDLE后,执行的是同样的流程,这一方面也说明两种Doze对Job都是有延迟的。然后通过PowerManager获取到当前Doze的状态,并调用updateIdleMode()更新,updateIdleMode()方法中的处理主要如下:

  • 1.Doze退出/进入时,都会进行Job的遍历,并最终会调用updateTaskStateLocked()方法更新Job的Doze约束;
  • 2.如果退出Doze后,立即会处理处于前台的进程的Job,其余进程通过Handler延时3s处理;
  • 3.一旦Doze状态发生改变,将通过onDeviceIdleStateChanged()回调进入JSS中。

来看看updateTaskStateLocked():

    private boolean updateTaskStateLocked(JobStatus task) {
        //是否豁免Doze模式的限制:设置了标记且(对应app处于前台或app处于临时白名单列表中)
        final boolean allowInIdle = ((task.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0)
                && (mForegroundUids.get(task.getSourceUid()) || isTempWhitelistedLocked(task));
        //Job所属应用是否处于Doze白名单中
        final boolean whitelisted = isWhitelistedLocked(task);
        //Job是否允许调度:Doze模式不处于IDLE状态或应用处于白名单中或应用处于前台且带有FLAG_IMPORTANT_WHILE_FOREGROUND
        final boolean enableTask = !mDeviceIdleMode || whitelisted || allowInIdle;
        //设置Doze模式约束条件
        return task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted);
    }

在以上方法中,判断是否这个Job可以在Doze下调度呢?并将结果通过setDeviceNotDozingConstraintSatisfied()设置给JobStatus:

    boolean setDeviceNotDozingConstraintSatisfied(boolean state, boolean whitelisted) {
        dozeWhitelisted = whitelisted;
        //该方法用来设置或这取消约束条件
        return setConstraintSatisfied(CONSTRAINT_DEVICE_NOT_DOZING, state);
    }

最终,通过setDeviceNotDozingConstraintSatisfied()方法将CONSTRAINT_DEVICE_NOT_DOZING标记设置给satisfiedConstraints

以上流程说明,当Doze模式不处于IDLE状态,或者Job所属应用处于白名单,或者Job所属应用被豁免,那么将设置为satisfiedConstraints设置CONSTRAINT_DEVICE_NOT_DOZING标记,表示此时已满足Doze的约束要求了,将不会被Doze模式所限制。在Job调度过程中,会看到关于他的判断:

//JobStatus.java:
    public boolean isReady() {
        //Doze模式是否处于非IDLE状态:不处于IDLE状态&&没有设置FLAG_WILL_BE_FOREGROUND标记
        final boolean notDozing = (satisfiedConstraints & CONSTRAINT_DEVICE_NOT_DOZING) != 0
                || (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
        return (isConstraintsSatisfied() || deadlineSatisfied) && notDozing && notRestrictedInBg;
    }

在JSS的回调方法onDeviceIdleStateChanged()中,将会根据Doze状态的变化决定重新检查Job队列还是取消当前正在运行的Job。

2. IdleController

IdleController用来控制设备闲置状态对Job的约束,仅仅对设置过setRequiresDeviceIdle(true)的Job有效。

注意,这个闲置状态和Doze模式中的IDLE状态不是同一个概念,在IdleController中,会注册三类广播:亮灭屏广播、开始/结束屏保广播和无线充电广播,同时在配置文件config.xml中配置了一个阀值,规定当进入收到灭屏等广播后,到达这个阀值时,认定设备处于空闲状态。下面来看具体实现逻辑。

首先来看其构造方法:

    public IdleController(JobSchedulerService service) {
        super(service);
        //初始化
        initIdleStateTracking();
    }

    private void initIdleStateTracking() {
        //处于闲置状态的阀值
        mInactivityIdleThreshold = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_jobSchedulerInactivityIdleThreshold);
        //设置Alarm的窗口时间
        mIdleWindowSlop = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_jobSchedulerIdleWindowSlop);
        //BroadcastReceiver子类
        mIdleTracker = new IdlenessTracker();
        mIdleTracker.startTracking();
    }

在以上构造方法中,主要做了以下工作:

  • 1.读取定义进入闲置状态的阀值,其定义如下:

<integer name="config_jobSchedulerInactivityIdleThreshold">4260000integer>

<integer name="config_jobSchedulerIdleWindowSlop">300000integer>
  • 2.初始化广播接收器并进行注册,这是在startTracking()中完成的:
        public void startTracking() {
            IntentFilter filter = new IntentFilter();

            // 屏幕状态广播
            filter.addAction(Intent.ACTION_SCREEN_ON);
            filter.addAction(Intent.ACTION_SCREEN_OFF);

            // 屏保状态广播
            filter.addAction(Intent.ACTION_DREAMING_STARTED);
            filter.addAction(Intent.ACTION_DREAMING_STOPPED);

            // AMS发送的广播
            filter.addAction(ActivityManagerService.ACTION_TRIGGER_IDLE);

            // 无线充电状态
            filter.addAction(Intent.ACTION_DOCK_IDLE);
            filter.addAction(Intent.ACTION_DOCK_ACTIVE);

            mContext.registerReceiver(this, filter);
        }

在广播接收器中的逻辑,将根据是否是限制状态来设置相关约束,并回调onControllerStateChanged()进入JSS中,重新check Job队列。:

    void reportNewIdleState(boolean isIdle) {
        synchronized (mLock) {
            for (int i = mTrackedTasks.size()-1; i >= 0; i--) {
                mTrackedTasks.valueAt(i).setIdleConstraintSatisfied(isIdle);
            }
        }
        mStateChangedListener.onControllerStateChanged();
    }

setIdleConstraintSatisfied()方法中:

    boolean setIdleConstraintSatisfied(boolean state) {
        //设置CONSTRAINT_IDLE标记
        return setConstraintSatisfied(CONSTRAINT_IDLE, state);
    }

在以上方法中,将根据当前是否处于闲置状态,对JobStatus中的全局变量satisfiedConstraints设置CONSTRAINT_IDLE标记。

3. TimeController

TimeController是用来控制截止时间和延迟时间对Job的约束,仅对设置了setOverrideDeadline()setMinimumLatency()的Job有效。

TimeController中,会根据以上两个方法设置的延迟时间和截至时间,分别设置一个Alarm,当到达警报时间后,在AlarmListener.onAlarm()方法中去更新,并在evaluateDeadlineConstraint()中更新相关标记:

    private boolean evaluateDeadlineConstraint(JobStatus job, long nowElapsedMillis) {
        final long jobDeadline = job.getLatestRunTimeElapsed();

        //到达截至时间
        if (jobDeadline <= nowElapsedMillis) {
            //到达延迟调度时间
            if (job.hasTimingDelayConstraint()) {
                job.setTimingDelayConstraintSatisfied(true);
            }
            job.setDeadlineConstraintSatisfied(true);
            return true;
        }
        return false;
    }

最终,会在setTimingDelayConstraintSatisfied()中设置CONSTRAINT_TIMING_DELAY标记给satisfiedConstraints:

    boolean setTimingDelayConstraintSatisfied(boolean state) {
        return setConstraintSatisfied(CONSTRAINT_TIMING_DELAY, state);
    }

会在setDeadlineConstraintSatisfied()中设置CONSTRAINT_DEADLINE标记给satisfiedConstraints:

    boolean setDeadlineConstraintSatisfied(boolean state) {
        return setConstraintSatisfied(CONSTRAINT_DEADLINE, state);
    }

4. BatteryController

BatteryController用来控制充电状态和低电量模式对Job的约束,仅仅对设置过setRequiresBatteryNotLow(true)setRequiresCharging(true)的Job有效。

充电状态和低电量模式这两个状态也是通过广播进行检测的,在其构造方法中,调用startTracking()方法注册广播:

        public void startTracking() {
            IntentFilter filter = new IntentFilter();

            // 低电量时发送
            filter.addAction(Intent.ACTION_BATTERY_LOW);
            // 退出低电量时发送
            filter.addAction(Intent.ACTION_BATTERY_OKAY);
            // 充电/放电时发送
            filter.addAction(BatteryManager.ACTION_CHARGING);
            filter.addAction(BatteryManager.ACTION_DISCHARGING);
            mContext.registerReceiver(this, filter);

            BatteryManagerInternal batteryManagerInternal =
                    LocalServices.getService(BatteryManagerInternal.class);
            // 是否处于低电量模式
            mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow();
            // 是否在进行充电
            mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
        }

在其广播接收器中,调用maybeReportNewChargingStateLocked()方法:

    private void maybeReportNewChargingStateLocked() {
        // 是否 充电且不处于低电量模式
        final boolean stablePower = mChargeTracker.isOnStablePower();
        // 是否 不处于低电量模式
        final boolean batteryNotLow = mChargeTracker.isBatteryNotLow();
        boolean reportChange = false;
        // 遍历跟踪的Job列表
        for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
            final JobStatus ts = mTrackedTasks.valueAt(i);
            // 设置充电约束条件
            boolean previous = ts.setChargingConstraintSatisfied(stablePower);
            if (previous != stablePower) {
                reportChange = true;
            }
            // 设置低电量约束条件
            previous = ts.setBatteryNotLowConstraintSatisfied(batteryNotLow);
            if (previous != batteryNotLow) {
                reportChange = true;
            }
        }
        if (stablePower || batteryNotLow) {
            // 电源状态稳定后,立即调度执行Job
            mStateChangedListener.onRunJobNow(null);
        } else if (reportChange) {
            // 状态发生变化,通知JSS
            mStateChangedListener.onControllerStateChanged();
        }
    }

在以上方法中,将根据当前电池状态,分别调用JobStatus的setChargingConstraintSatisfied()setBatteryNotLowConstraintSatisfied()来设置充电约束和低电量约束:

    boolean setChargingConstraintSatisfied(boolean state) {
        return setConstraintSatisfied(CONSTRAINT_CHARGING, state);
    }

    boolean setBatteryNotLowConstraintSatisfied(boolean state) {
        return setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, state);
    }

最终,将标记值CONSTRAINT_CHARGINGCONSTRAINT_BATTERY_NOT_LOW设置给satisfiedConstraints

5. BackgroundJobsController后台限制

BackgroundJobsController用来控制Job后台的运行。由于AppStandby机制,当应用处于后台时,会进行一些功能的限制,以达到优化功耗的目的,BackgroundJobsController中正是对"当应用处于后台时限制它的Job"实现的地方。

和前几个控制器不同的是,BackgroundJobsController中监测某应用能否在后台运行Job,不是通过广播实现的,而是通过AppStateTracker和其内部接口AppStateTracker.Listener实现,而AppStateTracker是和AppStandby相关联的用来记录应用状态的类,当AppStandby中状态发生变化时,AppStateTracker.Listener的几个方法将被回调,从而更新各个应用的Job的后台约束:


//frameworks/base/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
    private final Listener mForceAppStandbyListener = new Listener() {
        @Override
        public void updateAllJobs() {
            synchronized (mLock) {
                updateAllJobRestrictionsLocked();
            }
        }

        @Override
        public void updateJobsForUid(int uid, boolean isActive) {
            synchronized (mLock) {
                updateJobRestrictionsForUidLocked(uid, isActive);
            }
        }

        @Override
        public void updateJobsForUidPackage(int uid, String packageName, boolean isActive) {
            synchronized (mLock) {
                updateJobRestrictionsForUidLocked(uid, isActive);
            }
        }
    };
}

而这几个方法中,最终都会调到updateSingleJobRestrictionLocked()中,来看看这个方法:

    boolean updateSingleJobRestrictionLocked(JobStatus jobStatus, int activeState) {

        final int uid = jobStatus.getSourceUid();
        final String packageName = jobStatus.getSourcePackageName();

        // AppStandby是否对该Uid的Job有限制
        final boolean canRun = !mAppStateTracker.areJobsRestricted(uid, packageName,
                (jobStatus.getInternalFlags() & JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION)
                        != 0);

        final boolean isActive;
        if (activeState == UNKNOWN) {
            isActive = mAppStateTracker.isUidActive(uid);
        } else {
            isActive = (activeState == KNOWN_ACTIVE);
        }
        //设置后台约束
        boolean didChange = jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun);
        //设置Uid是否处于活动状态
        didChange |= jobStatus.setUidActive(isActive);
        return didChange;
    }

最终,将会在setBackgroundNotRestrictedConstraintSatisfied()设置标记CONSTRAINT_BACKGROUND_NOT_RESTRICTEDsatisfiedConstraints:

    boolean setBackgroundNotRestrictedConstraintSatisfied(boolean state) {
        return setConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED, state);
    }

6. ConnectivityController

ConnectivityController用来控制网络对Job的约束,仅仅对设置过setRequiredNetwork()setRequiredNetworkType()的Job有效。

ConnectivityController中通过接口回调的方式获取新的网络状态:

    public ConnectivityController(JobSchedulerService service) {
        super(service);

        // ConnectivityService提供数据连接管理服务
        // NetworkPolicyManagerService提供网络策略管理服务
        mConnManager = mContext.getSystemService(ConnectivityManager.class);
        mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);

        // 注册回调接口
        final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
        mConnManager.registerNetworkCallback(request, mNetworkCallback);
        mNetPolicyManager.registerListener(mNetPolicyListener);
    }

如此一来,当网络状态发生变化后,将回调方法从而通知ConnectivityController,关于何时触发毁掉,我不清楚,这里直接来看它回调后的内容,最终会进入updateConstraintsSatisfied()中:

    private boolean updateConstraintsSatisfied(JobStatus jobStatus, Network network,
            NetworkCapabilities capabilities) {
        // ......
        final boolean changed = jobStatus
                .setConnectivityConstraintSatisfied(connected && satisfied);

        jobStatus.network = network;
        return changed;
    }

关于这部分内容,我也是不知道啊,留着以后补充吧。总之,最后通过setConnectivityConstraintSatisfied()设置标记CONSTRAINT_CONNECTIVITYsatisfiedConstraints:

    boolean setConnectivityConstraintSatisfied(boolean state) {
        return setConstraintSatisfied(CONSTRAINT_CONNECTIVITY, state);
    }

7. StorageController

StorageController用来控制存储空间对Job的约束,仅仅对设置过setRequiresStorageNotLow(true)的Job有效。其内部也是通过广播的形式获取设备是否处于低存储状态:

        public void startTracking() {
            IntentFilter filter = new IntentFilter();

            // 存储空间不足时发送
            filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
            // 存储空间正常时发送
            filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
            mContext.registerReceiver(this, filter);
        }

在广播接收器onReceive()方法中,将通过具体广播,得到表示当前是否处于低存储的bool值mStorageLow,然后调用maybeReportNewStorageState()方法来更新Job的存储约束:

    private void maybeReportNewStorageState() {
        // 是否处于非低存储空间状态
        final boolean storageNotLow = mStorageTracker.isStorageNotLow();
        boolean reportChange = false;
        synchronized (mLock) {
            for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
                final JobStatus ts = mTrackedTasks.valueAt(i);
                // 更新Job约束
                boolean previous = ts.setStorageNotLowConstraintSatisfied(storageNotLow);
                if (previous != storageNotLow) {
                    reportChange = true;
                }
            }
        }
        if (reportChange) {
            // 存储空间发生变化
            mStateChangedListener.onControllerStateChanged();
        }
        if (storageNotLow) {
            // 当又低存储恢复正常存储状态后,立即运行Job
            mStateChangedListener.onRunJobNow(null);
        }
    }

最终,在setStorageNotLowConstraintSatisfied()中设置标记CONSTRAINT_STORAGE_NOT_LOWsatisfiedConstraints:

    boolean setStorageNotLowConstraintSatisfied(boolean state) {
        return setConstraintSatisfied(CONSTRAINT_STORAGE_NOT_LOW, state);
    }

8. ContentObserverController

ContentObserverController用来监测content URIS对Job的约束,仅仅对设置过addTriggerContentUri(Uri)的Job有效,当该URI发生变化后,将运行Job。这个暂时还不了解,就不分析了。

你可能感兴趣的:(Android系统开发)