安卓電量優化之JobScheduler使用介紹


版權聲明:本文出自汪磊的博客,轉載請務必注明出處。

一、JobScheduler概述

JobScheduler是安卓5.0版本推出的API,允許開發者在符合某些條件時創建執行在后台的任務。在Android開發中,會存在這些場景:你需要在稍后的某個時間點或者當滿足某個特定的條件時執行一個任務,例如當設備接通電源適配器或者連接到WIFI,此時就可以使用JobScheduler了,當一系列預置的條件被滿足時,JobScheduler API為你的應用執行一個操作。與AlarmManager不同的是這個執行時間是不確定的。除此之外,JobScheduler API允許同時執行多個任務。

JobSchedule的宗旨就是把一些不是特別緊急的任務放到更合適的時機批量處理。這樣做有兩個好處:避免頻繁的喚醒硬件模塊,造成不必要的電量消耗以及避免在不合適的時間(例如低電量情況下、弱網絡或者移動網絡情況下的)執行過多的任務消耗電量。

JobSchedule適用版本為5.0及以上,目前還未發現兼容庫支持。

二、JobScheduler使用

使用JobScheduler首先我們需要創建一個類繼承自JobService且必須實現兩個方法(JobService繼承自Service),分別是onStartJob(JobParameters params)onStopJob(JobParameters params),如下:

 1 public class TestJobService extends JobService {  2 
 3  @Override  4     public boolean onStartJob(JobParameters params) {  5 
 6         return false;  7  }  8 
 9  @Override 10     public boolean onStopJob(JobParameters params) { 11 
12         return false; 13  } 14 }

當任務開始時會執行onStartJob(JobParameters params)方法,這是系統用來觸發已經被執行的任務。這個方法返回一個boolean值。

如果返回值是false,系統假設這個方法返回時任務已經執行完畢。

如果返回值是true,那么系統假定這個任務正要被執行,執行任務的重擔就落在了開發者的肩上,當任務執行完畢時開發者需要自己調用jobFinished(JobParameters params, boolean needsRescheduled)來通知系統。

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

需要注意的是這個job service運行在你的主線程,這意味着你需要使用子線程,handler, 或者一個異步任務來運行耗時的操作以防止阻塞主線程。我們可以在onStartJob中利用Handler發送消息,在Handler中處理相關任務。

jobFinished(JobParameters params, boolean needsRescheduled)的兩個參數中的params參數是從JobService的onStartJob(JobParameters params)的params傳遞過來的,needsRescheduled參數是告訴系統這個任務如果由於某些原因導致執行失敗是否需要重新調度執行,true需要重新調度執行,false不需要。

最后我們需要到AndroidManifest.xml中添加一個service節點讓你的應用擁有綁定和使用這個JobService的權限。

1 <service 2    android:name=".TestJobService"
3    android:permission="android.permission.BIND_JOB_SERVICE" >
4 </service>

在創建完TestJobService后,我們就該創建JobScheduler對象了,創建JobScheduler對象需要通過getSystemService( Context.JOB_SCHEDULER_SERVICE )來初始化:

1 JobScheduler tm =(JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);

接下來我們使用JobInfo.Builder來構建一個JobInfo對象。JobInfo.Builder接收兩個參數,第一個參數是你要運行的任務的標識符,簡單理解為這個任務的ID就可以了,第二個是這個Service組件的類名。

1 JobInfo.Builder builder = new JobInfo.Builder(kJobId++, mServiceComponent);

mServiceComponent初始化如下:

1 mServiceComponent = new ComponentName(this, TestJobService.class);

builder允許你設置很多不同的選項來控制任務的執行,重要方法說明如下:

方法名 說明
setPeriodic(long intervalMillis) 設置任務每隔intervalMillis運行一次
setMinimumLatency(long minLatencyMillis) 這個函數能讓你設置任務的延遲執行時間(單位是毫秒),這個函數與setPeriodic(long time)方法不兼容,如果這兩個方法同時調用了就會引起異常
setOverrideDeadline(long maxExecutionDelayMillis) 這個方法讓你可以設置任務最晚的延遲時間。如果到了規定的時間時其他條件還未滿足,你的任務也會被啟動。與setMinimumLatency(long time)一樣,這個方法也會與setPeriodic(long time),同時調用這兩個方法會引發異常
setPersisted(boolean isPersisted) 這個方法告訴系統當你的設備重啟之后你的任務是否還要繼續執行
setRequiredNetworkType(int networkType) 這個方法讓你這個任務只有在滿足指定的網絡條件時才會被執行。默認條件是JobInfo.NETWORK_TYPE_NONE,這意味着不管是否有網絡這個任務都會被執行。另外兩個可選類型,一種是JobInfo.NETWORK_TYPE_ANY,它表明需要任意一種網絡才使得任務可以執行。另一種是JobInfo.NETWORK_TYPE_UNMETERED,它表示設備不是蜂窩網絡( 比如在WIFI連接時 )時任務才會被執行
setRequiresCharging(boolean requiresCharging) 這個方法告訴你的應用,只有當設備在充電時這個任務才會被執行
setRequiresDeviceIdle(boolean requiresDeviceIdle) 這個方法告訴你的任務只有當用戶沒有在使用該設備且有一段時間沒有使用時才會啟動該任務

需要注意的是setRequiredNetworkType(int networkType), setRequiresCharging(boolean requireCharging) and setRequiresDeviceIdle(boolean requireIdle)者幾個方法可能會使得你的任務無法執行,除非調用setOverrideDeadline(long time)設置了最大延遲時間,使得你的任務在未滿足條件的情況下也會被執行。一旦你預置的條件被設置,你就可以構建一個JobInfo對象,然后通過如下所示的代碼將它發送到你的JobScheduler中

1 if( tm.schedule( builder.build() ) <= 0 ) { 2     //something wrong
3 }

schedule方法會返回一個整型。如果schedule方法失敗了,它會返回一個小於0的錯誤碼。否則返回我們在JobInfo.Builder中定義的標識id。

停止某個任務,可以調用JobScheduler對象的cancel(int jobId)來實現;如果想取消所有的任務,可以調用JobScheduler對象的cancelAll()來實現。

以上就是JobScheduler的介紹,是不是很簡單,重要的是理解什么情況下使用JobScheduler

三、JobScheduler實例舉例

本Demo是安卓官方提供的一個例子,我們先看TestJobService類;

 1 public class TestJobService extends JobService {  2     private static final String TAG = "SyncService";  3 
 4  @Override  5     public void onCreate() {  6         super.onCreate();  7         Log.i(TAG, "Service created");  8  }  9 
10  @Override 11     public void onDestroy() { 12         super.onDestroy(); 13         Log.i(TAG, "Service destroyed"); 14  } 15 
16 
17  @Override 18     public int onStartCommand(Intent intent, int flags, int startId) { 19         Messenger callback = intent.getParcelableExtra("messenger"); 20         Message m = Message.obtain(); 21         m.what = MainActivity.MSG_SERVICE_OBJ; 22         m.obj = this; 23         try { 24  callback.send(m); 25         } catch (RemoteException e) { 26             Log.e(TAG, "Error passing service object back to activity."); 27  } 28         return START_NOT_STICKY; 29  } 30 
31  @Override 32     public boolean onStartJob(JobParameters params) { 33         // We don't do any real 'work' in this sample app. All we'll 34         // do is track which jobs have landed on our service, and 35         // update the UI accordingly.
36  jobParamsMap.add(params); 37         if (mActivity != null) { 38  mActivity.onReceivedStartJob(params); 39  } 40         Log.i(TAG, "on start job: " + params.getJobId()); 41         return true; 42  } 43 
44  @Override 45     public boolean onStopJob(JobParameters params) { 46         // Stop tracking these job parameters, as we've 'finished' executing.
47  jobParamsMap.remove(params); 48         if (mActivity != null) { 49  mActivity.onReceivedStopJob(); 50  } 51         Log.i(TAG, "on stop job: " + params.getJobId()); 52         return true; 53  } 54 
55  MainActivity mActivity; 56     private final LinkedList<JobParameters> jobParamsMap = new LinkedList<JobParameters>(); 57 
58     public void setUiCallback(MainActivity activity) { 59         mActivity = activity; 60  } 61 
62     /** Send job to the JobScheduler. */
63     public void scheduleJob(JobInfo t) { 64         Log.d(TAG, "Scheduling job"); 65         JobScheduler tm =
66  (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); 67         if(tm.schedule(t)<=0){ 68             Log.i(TAG, "something wrong"); 69  } 70  } 71     
72 
73     /**
74  * Not currently used, but as an exercise you can hook this 75  * up to a button in the UI to finish a job that has landed 76  * in onStartJob(). 77      */
78     public boolean callJobFinished() { 79         JobParameters params = jobParamsMap.poll(); 80         if (params == null) { 81             return false; 82         } else { 83             jobFinished(params, false); 84             return true; 85  } 86  } 87 }

上面提到過JobService其實也是Service,同樣具有相應生命周期方法。TestJobService先不做詳細講解,但是要看到這個類中封裝了一些JobScheduler方法供外界調用。

接下來我們結合MainActivity類一起看,MainActivity源碼如下:

 1 public class MainActivity extends Activity {  2 
 3     private static final String TAG = "MainActivity";  4 
 5     public static final int MSG_UNCOLOUR_START = 0;  6     public static final int MSG_UNCOLOUR_STOP = 1;  7     public static final int MSG_SERVICE_OBJ = 2;  8     // UI fields.
 9     int defaultColor;  10     int startJobColor;  11     int stopJobColor;  12 
 13     private TextView mShowStartView;  14     private TextView mShowStopView;  15     private TextView mParamsTextView;  16     private EditText mDelayEditText;  17     private EditText mDeadlineEditText;  18     private RadioButton mWiFiConnectivityRadioButton;  19     private RadioButton mAnyConnectivityRadioButton;  20     private CheckBox mRequiresChargingCheckBox;  21     private CheckBox mRequiresIdleCheckbox;  22  ComponentName mServiceComponent;  23  TestJobService mTestService;  24     //  25     private static int kJobId = 0;  26 
 27  @Override  28     public void onCreate(Bundle savedInstanceState) {  29         super.onCreate(savedInstanceState);  30  setContentView(R.layout.sample_main);  31         Resources res = getResources();  32         defaultColor = res.getColor(R.color.none_received);  33         startJobColor = res.getColor(R.color.start_received);  34         stopJobColor = res.getColor(R.color.stop_received);  35         // Set up UI.
 36         mShowStartView = (TextView) findViewById(R.id.onstart_textview);  37         mShowStopView = (TextView) findViewById(R.id.onstop_textview);  38         mParamsTextView = (TextView) findViewById(R.id.task_params);  39         mDelayEditText = (EditText) findViewById(R.id.delay_time);  40         mDeadlineEditText = (EditText) findViewById(R.id.deadline_time);  41         mWiFiConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_unmetered);  42         mAnyConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_any);  43         mRequiresChargingCheckBox = (CheckBox) findViewById(R.id.checkbox_charging);  44         mRequiresIdleCheckbox = (CheckBox) findViewById(R.id.checkbox_idle);  45         mServiceComponent = new ComponentName(this, TestJobService.class);  46         // Start service and provide it a way to communicate with us.
 47         Intent startServiceIntent = new Intent(this, TestJobService.class);  48         startServiceIntent.putExtra("messenger", new Messenger(mHandler));  49  startService(startServiceIntent);  50  }  51 
 52     Handler mHandler = new Handler(/* default looper */) {  53  @Override  54         public void handleMessage(Message msg) {  55             switch (msg.what) {  56                 case MSG_UNCOLOUR_START:  57  mShowStartView.setBackgroundColor(defaultColor);  58                     break;  59                 case MSG_UNCOLOUR_STOP:  60  mShowStopView.setBackgroundColor(defaultColor);  61                     break;  62                 case MSG_SERVICE_OBJ:  63                     mTestService = (TestJobService) msg.obj;  64                     mTestService.setUiCallback(MainActivity.this);  65  }  66  }  67  };  68 
 69     private boolean ensureTestService() {  70         if (mTestService == null) {  71             Toast.makeText(MainActivity.this, "Service null, never got callback?",  72  Toast.LENGTH_SHORT).show();  73             return false;  74  }  75         return true;  76  }  77 
 78     /**
 79  * UI onclick listener to schedule a job. What this job is is defined in  80  * TestJobService#scheduleJob().  81      */
 82     @SuppressLint("NewApi")  83     public void scheduleJob(View v) {  84         if (!ensureTestService()) {  85             return;  86  }  87         JobInfo.Builder builder = new JobInfo.Builder(kJobId++, mServiceComponent);  88 
 89         String delay = mDelayEditText.getText().toString();  90         if (delay != null && !TextUtils.isEmpty(delay)) {  91             builder.setMinimumLatency(Long.valueOf(delay) * 1000);  92  }  93         String deadline = mDeadlineEditText.getText().toString();  94         if (deadline != null && !TextUtils.isEmpty(deadline)) {  95             builder.setOverrideDeadline(Long.valueOf(deadline) * 1000);
 96  }  97         boolean requiresUnmetered = mWiFiConnectivityRadioButton.isChecked();  98         boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isChecked();  99         if (requiresUnmetered) { 100  builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); 101         } else if (requiresAnyConnectivity) { 102  builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); 103  } 104         builder.setRequiresDeviceIdle(mRequiresIdleCheckbox.isChecked());
105         builder.setRequiresCharging(mRequiresChargingCheckBox.isChecked());
106  mTestService.scheduleJob(builder.build()); 107  } 108     /**
109  * cancel All jobs 110  * @param v 111      */
112     @SuppressLint("NewApi") 113     public void cancelAllJobs(View v) { 114         JobScheduler tm =
115  (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); 116  tm.cancelAll(); 117  } 118 
119     /**
120  * UI onclick listener to call jobFinished() in our service. 121      */
122     public void finishJob(View v) { 123         if (!ensureTestService()) { 124             return; 125  } 126  mTestService.callJobFinished(); 127         mParamsTextView.setText(""); 128  } 129 
130     /**
131  * Receives callback from the service when a job has landed 132  * on the app. Colours the UI and post a message to 133  * uncolour it after a second. 134      */
135     @SuppressLint("NewApi") 136     public void onReceivedStartJob(JobParameters params) { 137  mShowStartView.setBackgroundColor(startJobColor); 138         Message m = Message.obtain(mHandler, MSG_UNCOLOUR_START); 139         mHandler.sendMessageDelayed(m, 1000L); // uncolour in 1 second.
140         mParamsTextView.setText("Executing: " + params.getJobId() + " " + params.getExtras()); 141  } 142 
143     /**
144  * Receives callback from the service when a job that 145  * previously landed on the app must stop executing. 146  * Colours the UI and post a message to uncolour it after a 147  * second. 148      */
149     public void onReceivedStopJob() { 150  mShowStopView.setBackgroundColor(stopJobColor); 151         Message m = Message.obtain(mHandler, MSG_UNCOLOUR_STOP); 152         mHandler.sendMessageDelayed(m, 2000L); // uncolour in 1 second.
153         mParamsTextView.setText(""); 154  } 155     
156 }

對於這個Demo我只想說一個點,MainActivity在啟動的時候onCreate()方法中47-49行啟動了TestJobService,並且傳遞了一個new Messenger(mHandler)匿名對象,接下來我們看下TestJobService中onStartCommand方法,此方法上來就獲取Messenger對象,並且調用send方法發送一個消息(實際調用的就是Handler的send方法,可自行點進源碼查看),此消息obj為TestJobService類實例對象。回到MainActivity中查看mHandler,what為MSG_SERVICE_OBJ的時候:將msg的obj賦值給MainActivity中mTestService變量,然后調用TestJobService中setUiCallback方法,這樣MainActivity與TestJobService類就互相持有彼此實例對象了,也就可以調用其內部的方法互相通信了,比如TestJobService中onStartJob方法調用MainActivity中的onReceivedStartJob方法,onReceivedStartJob方法中其實就是發送了Handler消息,最后在Handler中執行相關業務邏輯。好了Demo就說這一點,其余請自行細細查看,很簡單,最后附上Demo布局文件:

 1 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
 2     android:layout_width="match_parent"
 3     android:layout_height="match_parent"
 4     android:orientation="vertical" >
 5 
 6     <LinearLayout  7         android:layout_width="match_parent"
 8         android:layout_height="match_parent"
 9         android:layout_weight="1"
 10         android:orientation="vertical">
 11 
 12         <LinearLayout  13             android:layout_width="match_parent"
 14             android:layout_height="100dp">
 15             <TextView  16                 android:id="@+id/onstart_textview"
 17                 android:layout_width="wrap_content"
 18                 android:layout_height="match_parent"
 19                 android:layout_weight="1"
 20                 android:background="#999999"
 21                 android:gravity="center"
 22                 android:text="@string/onstarttask"/>
 23             <TextView  24                 android:id="@+id/onstop_textview"
 25                 android:layout_width="wrap_content"
 26                 android:layout_height="match_parent"
 27                 android:layout_weight="1"
 28                 android:background="@color/none_received"
 29                 android:gravity="center"
 30                 android:text="@string/onstoptask"/>
 31         </LinearLayout>
 32         <Button  33             android:id="@+id/finished_button"
 34             android:layout_width="match_parent"
 35             android:layout_height="wrap_content"
 36             android:padding="20dp"
 37             android:layout_marginBottom="5dp"
 38             android:onClick="finishJob"
 39             android:text="@string/finish_job_button_text"/>
 40 
 41         <TextView  42             android:id="@+id/task_params"
 43             android:layout_width="match_parent"
 44             android:layout_height="wrap_content"
 45             android:text="@string/defaultparamtext"
 46             android:gravity="center"
 47             android:textSize="20dp"
 48             android:padding="15dp"
 49             android:layout_marginBottom="10dp" />
 50 
 51         <TextView  52             android:layout_width="match_parent"
 53             android:layout_height="wrap_content"
 54             android:text="@string/constraints"
 55             android:layout_margin="15dp"
 56             android:textSize="18dp"/>
 57         <LinearLayout  58             android:layout_width="match_parent"
 59             android:layout_height="wrap_content"
 60             android:orientation="vertical"
 61             android:layout_marginLeft="10dp">
 62             <LinearLayout  63                 android:layout_width="match_parent"
 64                 android:layout_height="wrap_content">
 65                 <TextView  66                     android:layout_width="wrap_content"
 67                     android:layout_height="wrap_content"
 68                     android:text="@string/connectivity"
 69                     android:layout_marginRight="10dp"/>
 70                 <RadioGroup  71                     android:layout_width="wrap_content"
 72                     android:layout_height="wrap_content"
 73                     android:orientation="horizontal">
 74                     <RadioButton android:id="@+id/checkbox_any"
 75                         android:layout_width="wrap_content"
 76                         android:layout_height="wrap_content"
 77                         android:text="@string/any"
 78                         android:checked="false" />
 79                     <RadioButton android:id="@+id/checkbox_unmetered"
 80                         android:layout_width="wrap_content"
 81                         android:layout_height="wrap_content"
 82                         android:text="@string/unmetered"
 83                         android:checked="false" />
 84                 </RadioGroup>
 85 
 86             </LinearLayout>
 87             <LinearLayout  88                 android:layout_width="match_parent"
 89                 android:layout_height="wrap_content">
 90                 <TextView  91                     android:layout_width="wrap_content"
 92                     android:layout_height="wrap_content"
 93                     android:text="@string/timing"/>
 94                 <TextView  95                     android:layout_width="wrap_content"
 96                     android:layout_height="wrap_content"
 97                     android:layout_marginLeft="15dp"
 98                     android:textSize="17dp"
 99                     android:text="@string/delay"/>
100                 <EditText 101                     android:id="@+id/delay_time"
102                     android:layout_width="60dp"
103                     android:layout_height="wrap_content"
104                     android:inputType="number"/>
105                 <TextView 106                     android:layout_width="wrap_content"
107                     android:layout_height="wrap_content"
108                     android:text="@string/deadline"
109                     android:textSize="17dp"/>
110                 <EditText 111                     android:id="@+id/deadline_time"
112                     android:layout_width="60dp"
113                     android:layout_height="wrap_content"
114                     android:inputType="number"/>
115             </LinearLayout>
116             <LinearLayout 117                 android:layout_width="match_parent"
118                 android:layout_height="wrap_content">
119                 <TextView 120                     android:layout_width="wrap_content"
121                     android:layout_height="wrap_content"
122                     android:text="@string/charging_caption"
123                     android:layout_marginRight="15dp"/>
124                 <CheckBox 125                     android:layout_width="wrap_content"
126                     android:layout_height="wrap_content"
127                     android:id="@+id/checkbox_charging"
128                     android:text="@string/charging_text"/>
129             </LinearLayout>
130             <LinearLayout 131                 android:layout_width="match_parent"
132                 android:layout_height="wrap_content">
133                 <TextView 134                     android:layout_width="wrap_content"
135                     android:layout_height="wrap_content"
136                     android:text="@string/idle_caption"
137                     android:layout_marginRight="15dp"/>
138                 <CheckBox 139                     android:layout_width="wrap_content"
140                     android:layout_height="wrap_content"
141                     android:id="@+id/checkbox_idle"
142                     android:text="@string/idle_mode_text"/>
143             </LinearLayout>
144 
145         </LinearLayout>
146         <Button 147             android:id="@+id/schedule_button"
148             android:layout_width="match_parent"
149             android:layout_height="wrap_content"
150             android:layout_marginTop="20dp"
151             android:layout_marginLeft="40dp"
152             android:layout_marginRight="40dp"
153             android:onClick="scheduleJob"
154             android:text="@string/schedule_job_button_text"/>
155         <Button 156             android:id="@+id/cancel_button"
157             android:layout_width="match_parent"
158             android:layout_height="wrap_content"
159             android:layout_marginLeft="40dp"
160             android:layout_marginRight="40dp"
161             android:onClick="cancelAllJobs"
162             android:text="@string/cancel_all_jobs_button_text"/>
163     </LinearLayout>
164 </ScrollView>

好了,本篇到此結束,希望通過本篇介紹你能正確使用JobScheduler這個強大的工具,有些任務放在適合的時機來做更加適宜。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM