JobScheduler的使用和原理

1、JobScheduler的使用

1.1 簡介

JobScheduler主要用於在未來某個時間下滿足一定條件時觸發執行某項任務的情況,涉及的條件可以是網絡、電量、時間等,例如執行特定的網絡、是否只在充電時執行任務等。

1.2 相關API

1.2.1 JobScheduler

JobScheduler類負責將應用需要執行的任務發送給框架,以備對該應用Job的調度,是一個系統服務,可以通過如下方式獲取:

JobScheduler mJobScheduler = (JobScheduler) Context.getSystemService(Context.JOB_SCHEDULER_SERVICE). 

1.2.2 JobInfo 及 JobInfo.Builder

JobInfo是傳遞給JobScheduler類的數據容器,它封裝了針對調用應用程序調度任務所需的各種約束,也可以認爲一個JobInfo對象對應一個任務,JobInfo對象通過JobInfo.Builder創建。它將作爲參數傳遞給JobScheduler:

mJobScheduler.scheduler(mJobInfo);

JobInfo.Builder是JobInfo的一個內部類,用來創建JobInfo的Builder類。

JobInfo.Builder mBuilder = new JobInfo.Builder(id,new ComponentName(this, MyJobService.class));
mJobInfo = mBuilder.build();

1.2.3 JobService

JobService是JobScheduler最終回調的端點,JobScheduler將會回調該類中的onStartJob()開始執行異步任務。它是一個繼承於JobService的抽象類,做爲系統回調執行任務內容的終端,JobScheduler框架將通過bindService()方式來啓動該服務。因此,用戶必須在應用程序中創建一個JobService的子類,並實現其onStartJob()等回調方法,以及在AndroidManifest.xml中對它授予如下權限:

<service android:name=".MyJobService"
    android:permission="android.permission.BIND_JOB_SERVICE"/>

1.3 使用流程

1.3.1 創建一個JobService的子類,作爲系統回調終端:

public class MyJobService extends JobService {

    @Override
    public boolean onStartJob(final JobParameters params) {
    	//todo 執行任務
        return true;
    }
    
    @Override
    public boolean onStopJob(JobParameters params) {
        return false;//返回false表示停止後不再重試執行
    }
}

注意在AndroidManifest.xml中添加權限

<service android:name=".MyJobService"
    android:permission="android.permission.BIND_JOB_SERVICE"/>

當任務開始時會執行onStartJob(JobParameters params)方法,如果返回值是false,則系統認爲這個方法返回時,任務已經執行完畢。如果返回值是true,那麼系統認爲這個任務正要被執行,執行任務的重擔就落在了你的肩上。當任務執行完畢時你需要調用jobFinished(JobParameters params, boolean needsRescheduled)來通知系統。

當系統接收到一個取消請求時,系統會調用onStopJob(JobParameters params)方法取消正在等待執行的任務。很重要的一點是如果onStartJob(JobParameters params)返回false,那麼系統假定在接收到一個取消請求時已經沒有正在運行的任務。換句話說,onStopJob(JobParameters params)在這種情況下不會被調用。

需要注意的是這個Job Service運行在主線程,這意味着你需要使用子線程,handler,或者一個異步任務來運行耗時的操作以防止阻塞主線程。

1.3.2 創建JobInfo.Builder對象,爲Job設置約束條件

private ComponentName mServiceComponent;
//根據JobService創建一個ComponentName對象
mServiceComponent = new ComponentName(this, MyJobService.class);
JobInfo.Builder builder = new JobInfo.Builder(mJobId++, mServiceComponent);
builder.setMinimumLatency(1000);//設置延遲調度時間
builder.setOverrideDeadline(2000);//設置該Job截至時間,在截至時間前肯定會執行該Job
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);//設置所需網絡類型
builder.setRequiresDeviceIdle(true);//設置在DeviceIdle時執行Job
builder.setRequiresCharging(true);//設置在充電時執行Job
builder.setExtras(extras);//設置一個額外的附加項
//...

1.3.3 獲取JobScheduler實例

JobScheduler mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);

1.3.4 開始調度Job

mJobScheduler.schedule(builder.build());//調度Job
mJobScheduler.cancel(jobId);//取消特定Job
mJobScheduler.cancelAll();//取消應用所有的Job

具體的示例

Google官方的Sample:https://github.com/googlearchive/android-JobScheduler

2、JobScheduler的原理

2.1 JobScheduler如何執行

JobScheduler是一個抽象類,它在系統框架的實現類是android.app.JobSchedulerImpl

public class JobSchedulerImpl extends JobScheduler {
    IJobScheduler mBinder;

   JobSchedulerImpl(IJobScheduler binder) {
        mBinder = binder;
    }

    @Override
    public int schedule(JobInfo job) {
        try {
            return mBinder.schedule(job);
        } catch (RemoteException e) {
            return JobScheduler.RESULT_FAILURE;
        }
    }

    @Override
    public void cancel(int jobId) {
        try {
            mBinder.cancel(jobId);
        } catch (RemoteException e) {}

    }

    @Override
    public void cancelAll() {
        try {
            mBinder.cancelAll();
        } catch (RemoteException e) {}

    }

    @Override
    public List<JobInfo> getAllPendingJobs() {
        try {
            return mBinder.getAllPendingJobs();
        } catch (RemoteException e) {
            return null;
        }
    }
}

執行的入口是JobScheduler.scheduler,其實是調了JobSchedulerImpl中的schedule方法;然後再調了mBinder.schedule(job)。這個mBinder就是JobSchedulerService,通過Binder跨進程調用JobSchedulerService。

最後調用到JobSchedulerService中的schedule方法:

public int schedule(JobInfo job, int uId) {
    JobStatus jobStatus = new JobStatus(job, uId);
    cancelJob(uId, job.getId());
    startTrackingJob(jobStatus);
  	 //通過handler發消息
    mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
    return JobScheduler.RESULT_SUCCESS;
}

接着發送MSG_CHECK_JOB消息,消息處理的地方是

private class JobHandler extends Handler {
    ...
    @Override
    public void handleMessage(Message message) {
        ...
        switch (message.what) {
            ...
            case MSG_CHECK_JOB:
                synchronized (mJobs) {
                    //主要是遍歷將來要處理的工作任務,然後一個個加到待處理工作任務集合中去
                    maybeQueueReadyJobsForExecutionLockedH();
                }
                break;
        }
        maybeRunPendingJobsH();
    }
    ...
}   

接着執行JobHandler中的maybeRunPendingJobsH方法,處理相應的任務

private void maybeRunPendingJobsH() {
	synchronized (mJobs) {
	   ...
       if (!availableContext.executeRunnableJob(nextPending)) {
          ...        
       }
      ... 
}

availableContext是JobServiceContext,即ServiceConnection,這個是進程間通訊ServiceConnection,通過調用availableContext.executeRunnableJob(nextPending)方法,會觸發調用onServiceConnected,看到這裏應該明白了,onServiceConnected方法中的service就是Jobservice,裏面還用了WakeLock鎖,防止手機休眠。

public class JobServiceContext extends IJobCallback.Stub implements ServiceConnection {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        ...
        final PowerManager pm =
                (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, runningJob.getTag());
        mWakeLock.setWorkSource(new WorkSource(runningJob.getUid()));
        mWakeLock.setReferenceCounted(false);
        mWakeLock.acquire();
        mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget();
    }
}

接着,通過Handler發消息,調用了handleServiceBoundH()方法。

/** Start the job on the service. */
private void handleServiceBoundH() {
	 ...
    try {
        mVerb = VERB_STARTING;
        scheduleOpTimeOut();
        service.startJob(mParams);
    } catch (RemoteException e) {
		...
    }
}

從上面源碼可以看出,最終是觸發調用了JobService中的startJob方法。

2.2 JobInfo.Buidler的設置

builder.setMinimumLatency(1000);//設置延遲調度時間
builder.setOverrideDeadline(2000);//設置該Job截至時間,在截至時間前肯定會執行該Job
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);//設置所需網絡類型
builder.setRequiresDeviceIdle(true);//設置在DeviceIdle時執行Job
builder.setRequiresCharging(true);//設置在充電時執行Job
builder.setExtras(extras);//設置一個額外的附加項

從源碼看,設置的內容應用於JobStatus,例如網絡限制

public class JobStatus {

	public boolean hasConnectivityConstraint() {
	    return job.getNetworkType() == JobInfo.NETWORK_TYPE_ANY;
	}

	public boolean hasUnmeteredConstraint() {
	    return job.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED;
	}
}

而在JobSchedulerService類,相關的狀態控制在其構造函數裏:

public JobSchedulerService(Context context) {
    super(context);
    // Create the controllers.
    mControllers = new ArrayList<StateController>();
    mControllers.add(ConnectivityController.get(this));
    mControllers.add(TimeController.get(this));
    mControllers.add(IdleController.get(this));
    mControllers.add(BatteryController.get(this));
    mControllers.add(AppIdleController.get(this));

    mHandler = new JobHandler(context.getMainLooper());
    mJobSchedulerStub = new JobSchedulerStub();
    mJobs = JobStore.initAndGet(this);
}

例如網絡控制類ConnectivityController類

public class ConnectivityController extends StateController implements
	        ConnectivityManager.OnNetworkActiveListener {
	//工作任務狀態集合
	private final List<JobStatus> mTrackedJobs = new LinkedList<JobStatus>();
	//這個是手機網絡連接改變廣播,網絡發生改變,會觸發這個廣播
	private final BroadcastReceiver mConnectivityChangedReceiver =
	    new ConnectivityChangedReceiver();
    /**
     * @param userId Id of the user for whom we are updating the connectivity state.
     */
    private void updateTrackedJobs(int userId) {
    	...
       if (changed) {
           mStateChangedListener.onControllerStateChanged();
       }
    }
   
    class ConnectivityChangedReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (DEBUG) {
                Slog.d(TAG, "Received connectivity event: " + intent.getAction() + " u"
                        + context.getUserId());
            }
            final String action = intent.getAction();
            if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
                final int networkType =
                        intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE,
                                ConnectivityManager.TYPE_NONE);
                // Connectivity manager for THIS context - important!
                final ConnectivityManager connManager = (ConnectivityManager)
                        context.getSystemService(Context.CONNECTIVITY_SERVICE);
                final NetworkInfo activeNetwork = connManager.getActiveNetworkInfo();
                final int userid = context.getUserId();
                // This broadcast gets sent a lot, only update if the active network has changed.
                if (activeNetwork == null) {
                    mNetworkUnmetered = false;
                    mNetworkConnected = false;
                    updateTrackedJobs(userid);
                } else if (activeNetwork.getType() == networkType) {
                    mNetworkUnmetered = false;
                    mNetworkConnected = !intent.getBooleanExtra(
                            ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
                    if (mNetworkConnected) {  // No point making the call if we know there's no conn.
                        mNetworkUnmetered = !connManager.isActiveNetworkMetered();
                    }
                    updateTrackedJobs(userid);
                }
            } else {
                ...
            }
        }
    }	    
}

當網絡發生改變時,會調用updateTrackedJobs(userid)方法,在updateTrackedJobs方法中,會判斷網絡是否有改變,有改變的會調mStateChangedListener.onControllerStateChanged()方法;然後調用了JobSchedulerService類中onControllerStateChanged方法:

public class JobSchedulerService extends com.android.server.SystemService
        implements StateChangedListener, JobCompletedListener {      
    @Override
    public void onControllerStateChanged() {
        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
    }             
}

接着也是處理MSG_CHECK_JOB 消息,和上文一樣,最終觸發調用了JobService中的startJob方法。

2.3 JobSchedulerService的啓動

JobSchedulerService是一個系統服務,即應該在SystemServer啓動的。閱讀SystemServer的源碼:

public final class SystemServer {
	private static final String TAG = "SystemServer";
	//手機開機啓動後會走這個main方法,然後調用run方法
	public static void main(String[] args) {
    	new SystemServer().run();
  	}
}

run方法如下:

private void run() {
    ...
    // Start services.
    try {
        startBootstrapServices();
        startCoreServices();
        startOtherServices();
    } catch (Throwable ex) {
       ...
    }
}

接着看startOtherServices()

private void startOtherServices() {
	...
    mSystemServiceManager.startService(JobSchedulerService.class);
	...
}

因此,在這裏就啓動了JobSchedulerService服務。

引用

1.android 性能優化JobScheduler使用及源碼分析

2.Android 9.0 JobScheduler(一) JobScheduler的使用

3.Android 9.0 JobScheduler(二) JobScheduler框架結構簡述及JobSchedulerService的啓動

4.Android 9.0 JobScheduler(三) 從Job的創建到執行

5.Android 9.0 JobScheduler(四) Job約束條件的控制

6.理解JobScheduler機制

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章