目錄
- google后臺(tái)任務(wù)推薦方案
- doze簡(jiǎn)介
- 從jobservice 到android-job 到workmanager
- 遇到的一些坑
- 總結(jié)
先看一下google推薦的后臺(tái)任務(wù)的解決方案
如圖后臺(tái)任務(wù)可以從三個(gè)維度去考慮:
- 是否需要立即執(zhí)行
- 是否需要執(zhí)行完畢
- 是否需要監(jiān)聽系統(tǒng)的狀態(tài) 如網(wǎng)絡(luò)狀態(tài)等
然后給出了對(duì)應(yīng)的具體的推薦方案
線程池废岂、廣播疼进、前臺(tái)任務(wù)這里暫時(shí)不提基跑,主要看一下新出的workmanager往声,從圖中可以看出workmanager的使用條件任務(wù)可以適當(dāng)延遲的,即task沒有鬧鐘那種強(qiáng)時(shí)效性。
workmanager是今年google io提出來的录平,目前版本還是1.0.0-alpha01叠聋,看見alpha就有點(diǎn)慌撕阎,后面果然出現(xiàn)bug了,這個(gè)后面再細(xì)說碌补。
背景
為什么要使用workmanager虏束,是alrammanager不好用嗎?
其實(shí)這兩者的作用并不完全一樣厦章;alarmmanager適用于類似鬧鐘那樣必須準(zhǔn)時(shí)喚醒的任務(wù)镇匀,不管是否處于doze(低耗電和應(yīng)用待機(jī)模式);不過在6.0后的doze里面,alarmmanager需要調(diào)用setAndAllowWhileIdle()
setExactAndAllowWhileIdle()
如果必須要準(zhǔn)時(shí)執(zhí)行的任務(wù)比如股票每日開市提醒只能強(qiáng)行喚醒系統(tǒng)袜啃,但是也有的是不是要求時(shí)間這么準(zhǔn)確的坑律,比如每隔2天彈一個(gè)notification提示打開app啥的,這種實(shí)效性不那么高的就可以換成jobservice囊骤。
簡(jiǎn)單介紹一下doze
這個(gè)是android6.0后出來的晃择,6.0+的設(shè)備上都有這個(gè),目的是為了省電也物。簡(jiǎn)單的說就是手機(jī)系統(tǒng)發(fā)現(xiàn)一段時(shí)間用戶都沒有在用手機(jī)了宫屠,就會(huì)進(jìn)入省電模式,期間不會(huì)處理alarm滑蚯、網(wǎng)絡(luò)狀態(tài)等浪蹂,等省電了一段時(shí)間后再給一小段時(shí)間讓所有應(yīng)用統(tǒng)一處理alram等task,然后又進(jìn)入省電狀態(tài)告材。如果中途一直沒有亮屏坤次、充電等表示用戶開始用手機(jī)了的操作,doze的省電周期會(huì)越來越長(zhǎng)斥赋,如上圖缰猴。
老實(shí)說,按照國(guó)人的習(xí)慣疤剑,白天手機(jī)想進(jìn)入doze滑绒,基本上上不可能的。沒有誰會(huì)好幾個(gè)小時(shí)不碰手機(jī)吧隘膘。所以基本上都是晚上進(jìn)入doze疑故,這也說明了為啥早上亮屏的瞬間手機(jī)好多app會(huì)在同一時(shí)間彈出notification(亮屏就退出了doze,被延遲的alram馬上開始工作)弯菊。
那么使用jobservice的優(yōu)勢(shì)在哪兒纵势,我為什么不全部用alarmManager?
使用jobservice的優(yōu)勢(shì)就是doze下,不會(huì)喚醒系統(tǒng),耗電量會(huì)減小钦铁。耗電量減小到一定程度扫茅,才能達(dá)到googleplay的推薦要求;在國(guó)內(nèi)育瓜,兩種類型差不多的app葫隙,用戶通過電量消耗統(tǒng)計(jì)軟件發(fā)現(xiàn)耗電量低的那個(gè)app,應(yīng)該會(huì)對(duì)它更有好感吧(卸載也是先卸載競(jìng)爭(zhēng)對(duì)手的app 哈哈)躏仇。
從jobservice 到android-job 到workmanager
前面說的時(shí)效性不是很強(qiáng)的任務(wù)可以用jobservice(JobScheduler)恋脚,但是這個(gè)是android5.0+才出來的,要兼容5.0之前的焰手,額外再用alramager;而且國(guó)外的還可以使用googleservice(Firebase JobDispatcher)實(shí)現(xiàn)糟描。這就需要開發(fā)者自己來兼容;
后面發(fā)現(xiàn)印象筆記的android-job已經(jīng)實(shí)現(xiàn)了類似的功能书妻;
github上印象筆記的文檔
androidjob的相關(guān)api
任務(wù)調(diào)度類型
- 普通任務(wù)
setExecutionWindow(start,end) - 特定時(shí)間執(zhí)行的任務(wù)
setExact(time) - 周期任務(wù)
setPeriodic(long intervalMs, long flexMs)
任務(wù)的執(zhí)行時(shí)間范圍public Builder setExecutionWindow(long startInMs, long endInMs)
mStartMs
Earliest point from which your task is eligible to run.endInMs
Latest point at which your task must be run. eligible to run.
這兩個(gè)是時(shí)間窗口的起始時(shí)間船响,意思是System.currentTimeMillis() + startInMs到System.currentTimeMillis() + endInMs之間執(zhí)行,endInMs對(duì)應(yīng)后面的deadline躲履。
在setPeriodic(long)和setExact(long)時(shí)不起作用
和周期任務(wù)相關(guān)的參數(shù)Builder setPeriodic(long intervalMs, long flexMs)
long mIntervalMs
這個(gè)是周期執(zhí)行有關(guān)的參數(shù)见间,每隔多久執(zhí)行一次,最低是15分鐘工猜。-
long mFlexMs
這個(gè)是周期執(zhí)行有關(guān)的參數(shù)米诉,離周期末尾多久這個(gè)job應(yīng)該被執(zhí)行,最低5分鐘篷帅。JobManager.instance().getConfig().setAllowSmallerIntervalsForMarshmallow(true); // Don't use this in production
debug模式下設(shè)置這個(gè)史侣,可以在api<24的機(jī)子上周期減小到60s,倒數(shù)減小到30s;正式環(huán)境魏身,最低周期15分鐘惊橱,倒數(shù)5分鐘。
和任務(wù)執(zhí)行失敗箭昵,重新調(diào)度有關(guān)的參數(shù)public Builder setBackoffCriteria(long backoffMs, @NonNull BackoffPolicy backoffPolicy)
- backoffMs
非周期性的任務(wù)税朴,執(zhí)行失敗后被重新調(diào)度需要等待的時(shí)間,配合著下面的策略使用。
默認(rèn)開始30s宙枷,逐漸增加最多5小時(shí) - backoffPolicy
枚舉掉房,只有線性和指數(shù)增長(zhǎng)兩種方式。
一些二級(jí)條件吧慰丛,如果沒有強(qiáng)制要求,會(huì)在deadline時(shí)忽略這些條件執(zhí)行job
boolean mRequiresCharging
是否需要插入設(shè)備(是指充電吧)
默認(rèn)false,但是如果之前的endInMS到了&&mRequirementsEnforced=false瘾杭,就會(huì)忽略這個(gè)條件執(zhí)行jobboolean mRequiresDeviceIdle
是否需要等到設(shè)備空閑诅病。同上面一樣,當(dāng)deadline到來&&沒有mRequirementsEnforced=false時(shí),就會(huì)忽略這個(gè)條件執(zhí)行Jobboolean mRequiresBatteryNotLow
是否禁止低電量贤笆。同上蝇棉,deadline&&有mRequirementsEnforced=false會(huì)忽略。boolean mRequiresStorageNotLow
是否禁止低存儲(chǔ)空間芥永。這個(gè)只有在android o才有效果NetworkType mNetworkType
需要等待的網(wǎng)絡(luò)狀態(tài)篡殷,是netWorkType的枚舉,有好幾種埋涧,類似wify 板辽、流量等,默認(rèn)是不需要關(guān)心網(wǎng)絡(luò)狀態(tài)棘催。
同上條件劲弦。boolean mRequirementsEnforced
這個(gè)就是前面提到的那個(gè)參數(shù),如果設(shè)為false醇坝,當(dāng)job到deadline還沒執(zhí)行的時(shí)候邑跪,就可以搶救一下。
傳參相關(guān)
- PersistableBundleCompat mExtras
帶的額外參數(shù)呼猪,必須是這種類型画畅,類似bundle∷尉啵可以在onRunJob方法那里通過傳過去的param取出來夜赵。
可以說android-job很良心了,暴露的api簡(jiǎn)單易用乡革,功能強(qiáng)大寇僧。
workmanager的使用
本來已經(jīng)使用android-job了,但是后面在github文檔的后面看到了官方的說明:由于google出了workmanager沸版,Android-job后面可能不再更新嘁傀。然后又換到workmanager。
developer上workmanager的官方文檔
使用起來還是很方便的视粮,workmanager.enque(workRequest)就可以了细办,看起來類似google之前出的volley的風(fēng)格,不知道是不是同一批程序員開發(fā)的哈哈蕾殴。
public class DemoWorker extends Worker {
public static final String TAG = "work_demo_tag";
@NonNull @Override public WorkerResult doWork() {
Data data = getInputData();
int requestCode = data.getInt(AlarmMgr.ALARM_REQUEST_CODE, -1);
String strData = data.getString(AlarmMgr.ALARM_NOTIFICATION_DATA, "");
boolean bNotiClick = data.getBoolean(AlarmMgr.ALARM_NOTIFICATION_CLICK, false);
Context context = getApplicationContext();
//your job to do
//可以再這里調(diào)用下一個(gè)一次性任務(wù)笑撞,形成周期循環(huán)。
//現(xiàn)在默認(rèn)執(zhí)行成功钓觉,有需要再添加retry back-off相關(guān)邏輯
return WorkerResult.SUCCESS;
}
/**
* @param requestCode requestCode
* @param strData strData
* @param bNotiClick bNotiClick
*/
public static void schedule(final int requestCode, String strData, boolean bNotiClick) {
WorkManager manager = WorkManager.getInstance();
//下一次任務(wù)開始時(shí)茴肥,取消上一次相關(guān)tag類型的任務(wù)(避免網(wǎng)絡(luò)狀態(tài)不太好反復(fù)觸發(fā)網(wǎng)絡(luò)切換廣播的情況)
//這里如果queue里面存在100個(gè)以上job會(huì)crash,所以需要處理一下
manager.cancelAllWorkByTag(String.valueOf(requestCode));
OneTimeWorkRequest oneTimeWorkRequest =
new OneTimeWorkRequest.Builder(AlarmReceiverWorker.class)
.addTag(String.valueOf(requestCode))
.setInputData(new Data.Builder()
.putInt(AlarmMgr.ALARM_REQUEST_CODE, requestCode)
.putString(AlarmMgr.ALARM_NOTIFICATION_DATA, strData)
.putBoolean(AlarmMgr.ALARM_NOTIFICATION_CLICK, bNotiClick)
.build())
.build();
manager.enqueue(oneTimeWorkRequest);
}
}
需要注意的是隊(duì)列里面任務(wù)(還在等待調(diào)度 未執(zhí)行的那種)不能超過100個(gè)荡灾,不然會(huì)crash瓤狐,這是workmanager代碼的限制
workRequest子類有oneTime和periodic瞬铸,對(duì)應(yīng)一次性任務(wù)和周期性任務(wù);因?yàn)榛旧贤瑯邮菍?duì)jobservice操作础锐,所以方法很類似android-job嗓节,可以對(duì)照上面anroid-job用法或者查看developer文檔workmanger簡(jiǎn)介
列一下常見用法
- 多任務(wù)調(diào)度順序
WorkManager.getInstance()
// First, run all the A tasks (in parallel):
.beginWith(workA1, workA2, workA3)
// ...when all A tasks are finished, run the single B task:
.then(workB)
// ...then run the C tasks (in any order):
.then(workC1, workC2)
.enqueue();
- 設(shè)置任務(wù)執(zhí)行的約束條件
// Create a Constraints that defines when the task should run
Constraints myConstraints = new Constraints.Builder()
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
// Many other constraints are available, see the
// Constraints.Builder reference
.build();
// ...then create a OneTimeWorkRequest that uses those constraints
OneTimeWorkRequest compressionWork =
new OneTimeWorkRequest.Builder(CompressWorker.class)
.setConstraints(myConstraints)
.build();
- 取消任務(wù)除了前面提到的cancleByTag,也可以用uniquework
OneTimeWorkRequest oneTimeWorkRequest =
new OneTimeWorkRequest.Builder(AlarmReceiverWorker.class).build();
WorkManager.getInstance()
.beginUniqueWork("downloadQueue", ExistingWorkPolicy.REPLACE, oneTimeWorkRequest);
說說坑吧
workermanager的周期任務(wù)皆警,有時(shí)不能取消掉之前放到隊(duì)列的任務(wù)拦宣。通過cancleByTag也不行,這個(gè)在stackoverflow和github上面的issue也看到有人提過信姓,而且確實(shí)回復(fù)是個(gè)bug鸵隧,應(yīng)該是alpha版還是有些問題的。
既然周期任務(wù)有坑财破,我們也可以采用一次性任務(wù)開啟下一個(gè)一次性任務(wù)掰派,像鏈表那樣。而且可以避開周期任務(wù)里面源碼對(duì)周期設(shè)置最小15分鐘的限制左痢,不過一般也沒那么流氓要隔幾分鐘就喚醒吧靡羡。
但是使用一次性任務(wù)循環(huán)觸發(fā),發(fā)現(xiàn)在小米上測(cè)試俊性,打開app退到后臺(tái)時(shí)略步,alarmmanager android-job workmanager正常,殺掉app定页,workmanager就不行了趟薄;使小米達(dá)到doze的條件,再亮屏典徊,anroid-job也不行了杭煎。在小米上暫時(shí)只有alramanager適用(如果有哪位朋友發(fā)現(xiàn)小米上用workmanager有方法可以避開這個(gè)坑,請(qǐng)分享一下)。
還有就是前面提到的卒落,要避開queue里面出現(xiàn)100個(gè)待調(diào)度的job的case羡铲。
總結(jié)
最后的做法定時(shí)任務(wù)使用alrammanager,等收到alram廣播后交給worker處理;網(wǎng)絡(luò)狀態(tài)監(jiān)聽任務(wù)直接再接收到廣播后交給worker處理儡毕。保證定時(shí)也切,但是收到廣播放到隊(duì)列里面不像之前那樣接收到廣播就串行馬上執(zhí)行了,等系統(tǒng)決定統(tǒng)一處理腰湾。
等后面workmanager版本號(hào)把a(bǔ)lpha去掉就更好了雷恃;可以先用android-job,等workamanager出穩(wěn)定版本再替換成worker费坊,因?yàn)閍pi太像了倒槐,替換的成本也不大。
這篇文章有些細(xì)節(jié)需要后面再進(jìn)行補(bǔ)充葵萎。