Android进阶——更节电的后台任务JobScheduler 机制使用详解

Posted CrazyMo_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android进阶——更节电的后台任务JobScheduler 机制使用详解相关的知识,希望对你有一定的参考价值。

文章大纲

引言

android 5.0系统以后,Google为了提高使用流畅度以及延长电池续航,引入了在应用后台/锁屏时,系统会回收应用并自动销毁应用拉起的Service的机制。同时为了满足在特定条件下(比如网络、充电状态、电量、时间、周期等)触发执行某些任务的需求,于是乎JobScheduler 机制应运而生。总之,对于一定预定条件而触发的任务,JobScheduler是绝佳选择。

一、JobScheduler 机制概述

JobScheduler 机制中把每个需要后台的业务抽象为一个Job,通过系统管理Job,来提高资源的利用率和减少不必要的唤醒,从而提高性能,节省电源。当系统启动时会通过system_server进程启动**JobSchedulerService**服务,然后当使用该机制时,首先通过JobInfo构造具体的后台任务对象,并通过Jobscheduler 传入到后台任务调度器,当满足配置的条件时系统便会在对应的JobService上执行对应的作业。简而言之,系统提供了一种条件周期性执行的后台任务,无需开发者自己去唤醒,达到配置的条件便会自动执行。

二、JobSchedulerService 服务

The JobSchedulerService knows nothing about constraints, or the state of active jobs. It receives callbacks from the various controllers and completed jobs and operates accordingly.

从通过 (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE)的方式获取JobScheduler实例可以得知JobSchedulerService 也是以系统服务形式运行在后台,JobSchedulerService对Job的状态和约束都不了解,完全是通过各种controller的回调去处理各种Job。

1、触发JobSchedulerService 的启动

在com.android.server.SystemServer#startOtherServices方法里

 mSystemServiceManager.startService(JobSchedulerService.class);

SystemServiceManager启动所有系统核心服务的方式都大同小异,基本上都是首先根据传入的class字节码类型newInstance反射构造相关的对象,注册到系统服务列表后再触发其相应的onStart方法启动对应的服务。

com.android.server.SystemServiceManager#startService(java.lang.Class)

public <T extends SystemService> T startService(Class<T> serviceClass) 
     try 
         final String name = serviceClass.getName(); 
         final T service;
             Constructor<T> constructor = serviceClass.getConstructor(Context.class);
             service = constructor.newInstance(mContext);//传入SystemServiceManager的mContext 反射构造JobSchedulerService 对象
				...
         // @ link ArrayList<SystemService> mServicesRegister it.
         mServices.add(service);
         // Start it. 启动JobSchedulerService 
         service.onStart();
         return service;
     
 

2、JobSchedulerService 对象的构造

 public final class JobSchedulerService extends com.android.server.SystemService
        implements StateChangedListener, JobCompletedListener
        ...
 public JobSchedulerService(Context context) 
        super(context);
        mHandler = new JobHandler(context.getMainLooper());//使用system_server进程中主线程的Looper初始化JobHandler
        mConstants = new Constants(mHandler);
        mJobSchedulerStub = new JobSchedulerStub();//创建对应Binder服务端
        mJobs = JobStore.initAndGet(this);

        // Create the controllers.
        mControllers = new ArrayList<StateController>();
        mControllers.add(ConnectivityController.get(this));//注册监听网络连接状态的广播
        mControllers.add(TimeController.get(this));//注册监听Job时间到期的广播
        mControllers.add(IdleController.get(this));//注册监听屏幕亮/灭,dream进入/退出,状态改变的广播
        mBatteryController = BatteryController.get(this);//注册监听电池是否充电,电量状态的广播
        mControllers.add(mBatteryController);
        mStorageController = StorageController.get(this);
        mControllers.add(mStorageController);
        mControllers.add(AppIdleController.get(this));//监听app是否空闲
        mControllers.add(ContentObserverController.get(this));//监听ContentObserver事件广播
        mControllers.add(DeviceIdleJobsController.get(this));//监听设备空闲广播
    
     
         @Override
    public void onControllerStateChanged() 
        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
    

    @Override
    public void onRunJobNow(JobStatus jobStatus) 
        mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
    
    @Override
    public void onStart() 
        publishLocalService(JobSchedulerInternal.class, new LocalService());
        publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
    

JobSchedulerService 继承自SystemService类并实现了StateChangedListener、JobCompletedListener接口,构造方法执行时主要完成四件事。

2.1、使用system_server进程的主线程Looper初始化了JobHandler

该过程运行在主线程,因此不能做耗时操作。

//com.android.server.job.JobSchedulerService.JobHandler 
final private class JobHandler extends Handler 
        public JobHandler(Looper looper) 
            super(looper);
        
        @Override
        public void handleMessage(Message message) 
            synchronized (mLock) 
                if (!mReadyToRock) //phase == PHASE_THIRD_PARTY_APPS_CAN_START 时mReadyToRock为true运行运行第三方App
                    return;
                
                switch (message.what) 
                    case MSG_JOB_EXPIRED: 
							...
                     break;
                    case MSG_CHECK_JOB:
                        if (mReportedActive) 
                            // if jobs are currently being run, queue all ready jobs for execution.
                            queueReadyJobsForExecutionLocked();
                         else 
                            // Check the list of jobs and run some of them if we feel inclined.
                            maybeQueueReadyJobsForExecutionLocked();
                        
                        break;
                    case MSG_CHECK_JOB_GREEDY:
                        queueReadyJobsForExecutionLocked();
                        break;
                    case MSG_STOP_JOB:
                        cancelJobImplLocked((JobStatus) message.obj, null,
                                "app no longer allowed to run");
                        break;
                
                maybeRunPendingJobsLocked();
                // Don't remove JOB_EXPIRED in case one came along while processing the queue.
                removeMessages(MSG_CHECK_JOB);
            
        
    

2.2、创建了JobSchedulerService 的对应Binder服务端

    /**
     * Binder stub trampoline implementation
     */
    final class JobSchedulerStub extends IJobScheduler.Stub 
      
        private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();

        // IJobScheduler implementation
        @Override
        public int schedule(JobInfo job) throws RemoteException 
			。。。
            try 
                return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, -1, null);
             finally 
                Binder.restoreCallingIdentity(ident);
            
        

        // IJobScheduler implementation
        @Override
        public int enqueue(JobInfo job, JobWorkItem work) throws RemoteException 
			...
            long ident = Binder.clearCallingIdentity();
            try 
                return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, -1, null);
             finally 
                Binder.restoreCallingIdentity(ident);
            
        

        @Override
        public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag)
                throws RemoteException 
			...
            try 
                return JobSchedulerService.this.scheduleAsPackage(job, null, callerUid,
                        packageName, userId, tag);
             finally 
                Binder.restoreCallingIdentity(ident);
            
        

        @Override
        public List<JobInfo> getAllPendingJobs() throws RemoteException 

            try 
                return JobSchedulerService.this.getPendingJobs(uid);
             finally 
                Binder.restoreCallingIdentity(ident);
            
        

        @Override
        public JobInfo getPendingJob(int jobId) throws RemoteException 
            final int uid = Binder.getCallingUid();
            long ident = Binder.clearCallingIdentity();
            try 
                return JobSchedulerService.this.getPendingJob(uid, jobId);
             finally 
                Binder.restoreCallingIdentity(ident);
            
        

        @Override
        public void cancelAll() throws RemoteException 
			...
            try 
                JobSchedulerService.this.cancelJobsForUid(uid, "cancelAll() called by app");
             finally 
                Binder.restoreCallingIdentity(ident);
            
        

        @Override
        public void cancel(int jobId) throws RemoteException 
			...
            try 
                JobSchedulerService.this.cancelJob(uid, jobId);
             finally 
                Binder.restoreCallingIdentity(ident);
            
        
    ;

2.3、创建了持久化相关的JobStore

JobStore对象构造时会在创建/data/system/job/jobs.xml文件,同时可能之前已经存储过,还会解析XML文件创建JobInfo和,并转化为对应的JobStatus,最后把所有的JobStatus并保存到JobSet集合中,也是为什么JobScheduler可以持久化的原因。

JobStatus对象记录着任务的jobId, ComponentName, uid以及标签和失败次数信息。

  private JobStore(Context context, Object lock, File dataDir) 
        File systemDir = new File(dataDir, "system");
        File jobDir = new File(systemDir, "job");
        jobDir.mkdirs();
        mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"));
        mJobSet = new JobSet();
        readJobMapFromDisk(mJobSet);
    

// frameworks/base/services/core/java/com/android/server/job/JobStore.java
/** Used by the @link JobSchedulerService to instantiate the JobStore. */
static JobStore initAndGet(JobSchedulerService jobManagerService) 
  synchronized (sSingletonLock) 
    if (sSingleton == null) 
      sSingleton = new JobStore(jobManagerService.getContext(),
                                jobManagerService.getLock(), 									Environment.getDataDirectory());
    
    return sSingleton;
  

2.4、创建和注册预置条件的监听器StateController

创建和注册预置条件的监听器,StateControler内部依然是通过广播实现的,监听到相应广播然后通知到监听者,当满足条件后,就会通过Handler 发送相应的消息触发任务执行。

StateControler类型说明
ConnectivityController注册监听网络连接状态的广播
TimeController注册监听job时间到期的广播
IdleController注册监听屏幕亮/灭,dream进入/退出,状态改变的广播
BatteryController注册监听电池是否充电,电量状态的广播
AppIdleController监听app是否空闲
ContentObserverController通过ContentObserver监测content URIs的变化
DeviceIdleJobsController根据doze状态为app设置约束。
public class ConnectivityController extends StateController implements
        ConnectivityManager.OnNetworkActiveListener 

    private final ConnectivityManager mConnManager;
    /** Singleton. */
    private static ConnectivityController mSingleton;
    private ConnectivityController(StateChangedListener stateChangedListener, Context context,
            Object lock) 
        super(stateChangedListener, context, lock);

        mConnManager = mContext.getSystemService(ConnectivityManager.class);
        mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);

        final IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
        mContext.registerReceiverAsUser(
                mConnectivityReceiver, UserHandle.SYSTEM, intentFilter, null, null);

        mNetPolicyManager.registerListener(mNetPolicyListener);//主动监听网络相关广播
    
	...
    /**
     * Update all jobs tracked by this controller.
     * @param uid only update jobs belonging to this UID, or @code -1 to update all tracked jobs.
     */
    private void updateTrackedJobs(int uid) 
        synchronized (mLock) 
            boolean changed = false;
            for (int i = 0; i < mTrackedJobs.size(); i++) 
                final JobStatus js = mTrackedJobs.get(i);
                if (uid == -1 || uid == js.getSourceUid()) 
                    changed |= updateConstraintsSatisfied(js);
                
            
            if (changed) 
                mStateChangedListener.onControllerStateChanged();
            
        
    

    /**
     * We know the network has just come up. We want to run any jobs that are ready.
     */
    @Override
    public synchronized void onNetworkActive() 
        synchronized (mLock) 
            for (int i = 0; i < mTrackedJobs.size(); i++) 
                final JobStatus js = mTrackedJobs.get(i);
                if (js.isReady()) 
                    mStateChangedListener.onRunJobNow(js);
                
            
        
    
    private BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() 
        @Override
        public void onReceive(Context context, Intent intent) 
            updateTrackedJobs(-1);
        
    ;

Android O以后禁止了一些广播的发送后,都是由这些Controller进行动态注册广播,由这些Controller触发相应的预置回调接口,从而转交给JobScheduler进行处理

/**
 * 是否正在充电
 */
public static boolean isCharging(Context context)
    //注册个包含充电状态的广播,并且是一个持续的广播
    IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
    Intent intent = context.registerReceiver(null,filter);
    //获取充电状态
    int isPlugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
    boolean acPlugged = isPlugged == BatteryManager.BATTERY_PLUGGED_AC;
    boolean usbPlugged = isPlugged == BatteryManager.BATTERY_PLUGGED_USB;
    boolean wifiPlugged = isPlugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;
    return acPlugged || usbPlugged || wifiPlugged;

3、JobSchedulerService的真正启动

与其他系统服务一样,执行发布,这样其他应用就可以直接通过Binder使用这个服务的能力了。

    @Override
    public void onStart() 
        publishLocalService(JobSchedulerInternal.class, new LocalService());
        publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
    

最后由SystemServiceRegistry的静态代码块中完成注册工作,可以看到当客户端请求获取JOB_SCHEDULER_SERVICE服务, 返回的是继承自JobScheduler 的JobSchedulerImpl实例。

        registerService(Context.JOB_SCHEDULER_SERVICE, JobScheduler.class,
                new StaticServiceFetcher<JobScheduler>() 
            @Override
            public JobScheduler createService() 
                IBinder b = ServiceManager.getService(Context.JOB_SCHEDULER_SERVICE);
                return new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b));
            );

至此JobSchedulerService 服务启动完成。

三、JobService

Entry point for the callback from the @link android.app.job.JobScheduler

抽象类JobService 继承自Service类,在JobScheduler监测到系统状态达到对应启动条件时,会启动JobService执行任务。所以我们需要继承JobService创建一个继承自JobService的Service,并必须实现两个方法:onStartJob(JobParameters params)onStopJob(JobParameters params)

public abstract class JobService extends Service 
    final JobHandler mHandler;
    final JobSchedulerStub mJobSchedulerStub;
    
    IJobService mBinder = new IJobService.Stub() 
        public void startJob(JobParameters jobParams) 
            ensureHandler();
            //向主线程的Handler发送MSG_EXECUTE_JOB消息
            Message m = Message.obtain(mHandler, MSG_EXECUTE_JOBAndroid进阶——更节电的后台任务JobScheduler 机制使用详解

[Android进阶]Android性能优化

优化 Android 线程和后台任务开发

Linux(Android):如何禁用Intel DPST(显示节电技术) - i915驱动程序

在 Android 12 中使用 WorkManager

使用 WorkManager 处理需要立刻执行的后台任务