有哪些工具可以用来测试 JobScheduler?
What tools are available to test JobScheduler?
我们正在通过 JobScheduler 实现作业以在后台加载数据。这项工作大约每天触发一次。我们可以使用哪些工具来测试此功能(可能是 ADB)?
用例能够模拟作业成为 运行 所需的条件,或者只是具体地说 "Run this job" 作为我们自动化测试套件的一部分。
//更新:
Android Studio 2020.3.1 中有一个新的 GUI 工具:https://developer.android.com/studio/preview/features#workmanager-inspector
// 旧答案:
使用命令 adb shell dumpsys jobscheduler
您可以获得有关当前计划和活动作业的信息。
我注意到命令的输出在 Android 6 和 7 之间有很大差异。对于 Android 5 设备,输出非常短而且有时是神秘的。注册作业的有趣部分是构建 here 并在下面重复以方便,这应该有助于破译:
@Override
public String toString() {
return String.valueOf(hashCode()).substring(0, 3) + ".."
+ ":[" + job.getService()
+ ",jId=" + job.getId()
+ ",u" + getUserId()
+ ",R=(" + formatRunTime(earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME)
+ "," + formatRunTime(latestRunTimeElapsedMillis, NO_LATEST_RUNTIME) + ")"
+ ",N=" + job.getNetworkType() + ",C=" + job.isRequireCharging()
+ ",I=" + job.isRequireDeviceIdle() + ",F=" + numFailures
+ ",P=" + job.isPersisted()
+ (isReady() ? "(READY)" : "")
+ "]";
}
另一方面,Android 7 设备的输出很长,信息更详细、可读性更好。还有更多功能,例如历史记录。缺点是要先找到感兴趣的部分
虽然我还没有找到强制工作 运行 的方法,there is a feature request for it. 请参阅 p4u144 的答案。
从 Android 7.0 开始,adb shell 有一个新的 cmd
。在 7.1 中(目前仅在预览版中),添加了 adb shell cmd jobscheduler
as you can see here 以强制 运行 您的 JobScheduler。
帮助说:
Job scheduler (jobscheduler) commands: help
Print this help text.
run [-f | --force] [-u | --user USER_ID] PACKAGE JOB_ID
Trigger immediate execution of a specific scheduled job.
Options:
-f or --force: run the job even if technical constraints such as
connectivity are not currently met
-u or --user: specify which user's job is to be run; the default is
the primary or system user
没错。
Henning 和 P4u144 让我走上了更详细地回答这个问题的正确轨道。
识别所有注册的职位
使用 adb shell dumpsys jobscheduler
命令确定您的任务。
这将为您提供以下类别的巨大输出。
- 设置
- 注册了 XX 个职位
- 连通性
- 警报
- 空闲
- 电池
- AppIdle
- 内容
- 工作经历
- 待定队列
您最有可能感兴趣的类别是注册的 XX 职位。这会告诉您设备上安排了多少作业。
例如,您的包名是 com.foo.bar.application
您应该看到这样的条目:
JOB #u0a93/17: eec3709 com.foo.bar.application/com.evernote.android.job.v21.PlatformJobService
u0a93 tag=*job*/com.foo.bar.application/com.evernote.android.job.v21.PlatformJobService
Source: uid=u0a93 user=0 pkg=com.foo.bar.application
JobInfo:
Service: com.foo.bar.application/com.evernote.android.job.v21.PlatformJobService
PERIODIC: interval=+15m0s0ms flex=+5m0s0ms
PERSISTED
Requires: charging=false deviceIdle=false
Network type: 2
Backoff: policy=1 initial=+30s0ms
Has early constraint
Has late constraint
Required constraints: TIMING_DELAY DEADLINE UNMETERED
Satisfied constraints: CONNECTIVITY NOT_ROAMING APP_NOT_IDLE DEVICE_NOT_DOZING
Unsatisfied constraints: TIMING_DELAY DEADLINE UNMETERED
Earliest run time: 07:23
Latest run time: 12:23
Ready: false (job=false pending=false active=false user=true)
Tip: Use adb shell dumpsys jobscheduler | grep com.foo.bar.application
to quickly filter the list.
现在您可以轻松确定您的职位是否已按照正确的标准注册。
FireBaseJobdispatcher
如果你使用 FirebaseJobDispatcher
库你可以使用
adb shell dumpsys activity service GcmService | grep com.foo.bar.debug
com.foo.bar.debug:0 v853
u0|com.foo.bar.debug: 3
(scheduled) com.foo.bar.debug/com.firebase.jobdispatcher.GooglePlayReceiver{u=0 tag="com.foo.bar.debug.job.FetchArticlesJob" trigger=window{start=10800s,end=11700s,earliest=10448s,latest=11348s} requirements=[NET_UNMETERED,DEVICE_IDLE] attributes=[PERSISTED,RECURRING] scheduled=-351s last_run=N/A jid=N/A status=PENDING retries=0 client_lib=FIREBASE_JOB_DISPATCHER-1}
(scheduled) com.foo.bar.debug/com.firebase.jobdispatcher.GooglePlayReceiver{u=0 tag="com.foo.bar.debug.job.FetchNotificationGroupsJob" trigger=window{start=86400s,end=129600s,earliest=86048s,latest=129248s} requirements=[NET_CONNECTED,CHARGING] attributes=[PERSISTED,RECURRING] scheduled=-351s last_run=N/A jid=N/A status=PENDING retries=0 client_lib=FIREBASE_JOB_DISPATCHER-1}
(scheduled) com.foo.bar.debug/com.firebase.jobdispatcher.GooglePlayReceiver{u=0 tag="com.foo.bar.debug.job.RemoveUnusedRealmArticlesJob" trigger=window{start=577980s,end=608400s,earliest=521961s,latest=552381s} requirements=[NET_ANY] attributes=[PERSISTED,RECURRING] scheduled=-56018s last_run=N/A jid=N/A status=PENDING retries=0 client_lib=FIREBASE_JOB_DISPATCHER-1}
(finished) [com.foo.bar.debug/com.firebase.jobdispatcher.GooglePlayReceiver:com.foo.bar.debug.job.UpdateNotificationGroupJob,u0]
(finished) [com.foo.bar.debug/com.firebase.jobdispatcher.GooglePlayReceiver:com.foo.bar.debug.job.UpdatePushTokenJob,u0]
(finished) [com.foo.bar.debug/com.firebase.jobdispatcher.GooglePlayReceiver:com.foo.bar.debug.job.FetchArticlesJob,u0]
检查您的服务是否已安排或运行。
强制你的任务运行
创建 Job
时,您会返回 JOB_ID
。
使用此 JOB_ID
将作业强制为 运行。
您可以使用 adb shell cmd jobscheduler run
命令执行此操作(需要 Android 7.1 或更高版本)。
例如,您的包名是 com.foo.bar.application
而 JOB_ID
是 1。
您现在可以通过 adb
运行 您的任务
adb shell cmd jobscheduler run -f com.foo.bar.application 1
不要忘记 -f
选项,因为即使不满足设置的限制,这也会强制作业 运行。
Evernote Android 工作库
最后但同样重要的一点。
为此使用 the wonderful library from Evernote。
它允许使用 JobScheduler
、GcmNetworkManager
或 AlarmManager
在较低的 API 级别上轻松向后移植 JobScheduler
,具体取决于您的 API 级别。
编辑 24/08
更好地使用 firebase 作业调度程序库。
The Firebase JobDispatcher is a library for scheduling background jobs in your Android app. It provides a JobScheduler-compatible API that works on all recent versions of Android (API level 9+) that have Google Play services installed.
希望对您有所帮助。
谢谢
编辑 28/04/2019
这两种工作都 Evernote Android-Job and Firebase JobDispatcher are now in maintenance only mode and they both suggest to use jetpack's WorkManager。
编辑 10/06/202
Android Jetpack WorkManager 让诊断变得稍微容易一些。
首先,您需要为您的包裹启用诊断功能
adb shell am broadcast -a 'androidx.work.diagnostics.REQUEST_DIAGNOSTICS' -p 'com.foo.bar.application'
之后你可以WorkManager将信息转储到ADB Logcat
中,你可以通过输入观察。
adb logcat
或通过 Android Studio Logcat
工具 window
这对我有用,不需要使用 adb 命令。它需要 minSdk 21
@RunWith(AndroidJUnit4.class)
@TargetApi(VERSION_CODES.LOLLIPOP)
public abstract class BaseJobServiceTest {
protected final Context context() {
return InstrumentationRegistry.getTargetContext();
}
protected final void launchJobAndWait(JobInfo jobInfo) throws InterruptedException {
JobScheduler scheduler = (JobScheduler) context().getSystemService(Context.JOB_SCHEDULER_SERVICE);
scheduler.schedule(jobInfo);
while (jobExecutionPending(scheduler, jobInfo)) {
Thread.sleep(50);
}
}
private boolean jobExecutionPending(JobScheduler scheduler, JobInfo jobInfo) {
if (VERSION.SDK_INT >= VERSION_CODES.N) {
return scheduler.getPendingJob(jobInfo.getId()) != null;
}
List<JobInfo> scheduledJobs = scheduler.getAllPendingJobs();
for (int i = 0, size = scheduledJobs.size(); i < size; i++) {
if (scheduledJobs.get(i).getId() == jobInfo.getId()) {
return true;
}
}
return false;
}
}
我们正在通过 JobScheduler 实现作业以在后台加载数据。这项工作大约每天触发一次。我们可以使用哪些工具来测试此功能(可能是 ADB)?
用例能够模拟作业成为 运行 所需的条件,或者只是具体地说 "Run this job" 作为我们自动化测试套件的一部分。
//更新: Android Studio 2020.3.1 中有一个新的 GUI 工具:https://developer.android.com/studio/preview/features#workmanager-inspector
// 旧答案:
使用命令 adb shell dumpsys jobscheduler
您可以获得有关当前计划和活动作业的信息。
我注意到命令的输出在 Android 6 和 7 之间有很大差异。对于 Android 5 设备,输出非常短而且有时是神秘的。注册作业的有趣部分是构建 here 并在下面重复以方便,这应该有助于破译:
@Override
public String toString() {
return String.valueOf(hashCode()).substring(0, 3) + ".."
+ ":[" + job.getService()
+ ",jId=" + job.getId()
+ ",u" + getUserId()
+ ",R=(" + formatRunTime(earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME)
+ "," + formatRunTime(latestRunTimeElapsedMillis, NO_LATEST_RUNTIME) + ")"
+ ",N=" + job.getNetworkType() + ",C=" + job.isRequireCharging()
+ ",I=" + job.isRequireDeviceIdle() + ",F=" + numFailures
+ ",P=" + job.isPersisted()
+ (isReady() ? "(READY)" : "")
+ "]";
}
另一方面,Android 7 设备的输出很长,信息更详细、可读性更好。还有更多功能,例如历史记录。缺点是要先找到感兴趣的部分
虽然我还没有找到强制工作 运行 的方法,there is a feature request for it. 请参阅 p4u144 的答案。
从 Android 7.0 开始,adb shell 有一个新的 cmd
。在 7.1 中(目前仅在预览版中),添加了 adb shell cmd jobscheduler
as you can see here 以强制 运行 您的 JobScheduler。
帮助说:
Job scheduler (jobscheduler) commands: help Print this help text.
run [-f | --force] [-u | --user USER_ID] PACKAGE JOB_ID Trigger immediate execution of a specific scheduled job. Options: -f or --force: run the job even if technical constraints such as connectivity are not currently met -u or --user: specify which user's job is to be run; the default is the primary or system user
没错。
Henning 和 P4u144 让我走上了更详细地回答这个问题的正确轨道。
识别所有注册的职位
使用 adb shell dumpsys jobscheduler
命令确定您的任务。
这将为您提供以下类别的巨大输出。
- 设置
- 注册了 XX 个职位
- 连通性
- 警报
- 空闲
- 电池
- AppIdle
- 内容
- 工作经历
- 待定队列
您最有可能感兴趣的类别是注册的 XX 职位。这会告诉您设备上安排了多少作业。
例如,您的包名是 com.foo.bar.application
您应该看到这样的条目:
JOB #u0a93/17: eec3709 com.foo.bar.application/com.evernote.android.job.v21.PlatformJobService
u0a93 tag=*job*/com.foo.bar.application/com.evernote.android.job.v21.PlatformJobService
Source: uid=u0a93 user=0 pkg=com.foo.bar.application
JobInfo:
Service: com.foo.bar.application/com.evernote.android.job.v21.PlatformJobService
PERIODIC: interval=+15m0s0ms flex=+5m0s0ms
PERSISTED
Requires: charging=false deviceIdle=false
Network type: 2
Backoff: policy=1 initial=+30s0ms
Has early constraint
Has late constraint
Required constraints: TIMING_DELAY DEADLINE UNMETERED
Satisfied constraints: CONNECTIVITY NOT_ROAMING APP_NOT_IDLE DEVICE_NOT_DOZING
Unsatisfied constraints: TIMING_DELAY DEADLINE UNMETERED
Earliest run time: 07:23
Latest run time: 12:23
Ready: false (job=false pending=false active=false user=true)
Tip: Use
adb shell dumpsys jobscheduler | grep com.foo.bar.application
to quickly filter the list.
现在您可以轻松确定您的职位是否已按照正确的标准注册。
FireBaseJobdispatcher
如果你使用 FirebaseJobDispatcher
库你可以使用
adb shell dumpsys activity service GcmService | grep com.foo.bar.debug
com.foo.bar.debug:0 v853
u0|com.foo.bar.debug: 3
(scheduled) com.foo.bar.debug/com.firebase.jobdispatcher.GooglePlayReceiver{u=0 tag="com.foo.bar.debug.job.FetchArticlesJob" trigger=window{start=10800s,end=11700s,earliest=10448s,latest=11348s} requirements=[NET_UNMETERED,DEVICE_IDLE] attributes=[PERSISTED,RECURRING] scheduled=-351s last_run=N/A jid=N/A status=PENDING retries=0 client_lib=FIREBASE_JOB_DISPATCHER-1}
(scheduled) com.foo.bar.debug/com.firebase.jobdispatcher.GooglePlayReceiver{u=0 tag="com.foo.bar.debug.job.FetchNotificationGroupsJob" trigger=window{start=86400s,end=129600s,earliest=86048s,latest=129248s} requirements=[NET_CONNECTED,CHARGING] attributes=[PERSISTED,RECURRING] scheduled=-351s last_run=N/A jid=N/A status=PENDING retries=0 client_lib=FIREBASE_JOB_DISPATCHER-1}
(scheduled) com.foo.bar.debug/com.firebase.jobdispatcher.GooglePlayReceiver{u=0 tag="com.foo.bar.debug.job.RemoveUnusedRealmArticlesJob" trigger=window{start=577980s,end=608400s,earliest=521961s,latest=552381s} requirements=[NET_ANY] attributes=[PERSISTED,RECURRING] scheduled=-56018s last_run=N/A jid=N/A status=PENDING retries=0 client_lib=FIREBASE_JOB_DISPATCHER-1}
(finished) [com.foo.bar.debug/com.firebase.jobdispatcher.GooglePlayReceiver:com.foo.bar.debug.job.UpdateNotificationGroupJob,u0]
(finished) [com.foo.bar.debug/com.firebase.jobdispatcher.GooglePlayReceiver:com.foo.bar.debug.job.UpdatePushTokenJob,u0]
(finished) [com.foo.bar.debug/com.firebase.jobdispatcher.GooglePlayReceiver:com.foo.bar.debug.job.FetchArticlesJob,u0]
检查您的服务是否已安排或运行。
强制你的任务运行
创建 Job
时,您会返回 JOB_ID
。
使用此 JOB_ID
将作业强制为 运行。
您可以使用 adb shell cmd jobscheduler run
命令执行此操作(需要 Android 7.1 或更高版本)。
例如,您的包名是 com.foo.bar.application
而 JOB_ID
是 1。
您现在可以通过 adb
adb shell cmd jobscheduler run -f com.foo.bar.application 1
不要忘记 -f
选项,因为即使不满足设置的限制,这也会强制作业 运行。
Evernote Android 工作库
最后但同样重要的一点。
为此使用 the wonderful library from Evernote。
它允许使用 JobScheduler
、GcmNetworkManager
或 AlarmManager
在较低的 API 级别上轻松向后移植 JobScheduler
,具体取决于您的 API 级别。
编辑 24/08
更好地使用 firebase 作业调度程序库。
The Firebase JobDispatcher is a library for scheduling background jobs in your Android app. It provides a JobScheduler-compatible API that works on all recent versions of Android (API level 9+) that have Google Play services installed.
希望对您有所帮助。
谢谢
编辑 28/04/2019
这两种工作都 Evernote Android-Job and Firebase JobDispatcher are now in maintenance only mode and they both suggest to use jetpack's WorkManager。
编辑 10/06/202
Android Jetpack WorkManager 让诊断变得稍微容易一些。
首先,您需要为您的包裹启用诊断功能
adb shell am broadcast -a 'androidx.work.diagnostics.REQUEST_DIAGNOSTICS' -p 'com.foo.bar.application'
之后你可以WorkManager将信息转储到ADB Logcat
中,你可以通过输入观察。
adb logcat
或通过 Android Studio Logcat
工具 window
这对我有用,不需要使用 adb 命令。它需要 minSdk 21
@RunWith(AndroidJUnit4.class)
@TargetApi(VERSION_CODES.LOLLIPOP)
public abstract class BaseJobServiceTest {
protected final Context context() {
return InstrumentationRegistry.getTargetContext();
}
protected final void launchJobAndWait(JobInfo jobInfo) throws InterruptedException {
JobScheduler scheduler = (JobScheduler) context().getSystemService(Context.JOB_SCHEDULER_SERVICE);
scheduler.schedule(jobInfo);
while (jobExecutionPending(scheduler, jobInfo)) {
Thread.sleep(50);
}
}
private boolean jobExecutionPending(JobScheduler scheduler, JobInfo jobInfo) {
if (VERSION.SDK_INT >= VERSION_CODES.N) {
return scheduler.getPendingJob(jobInfo.getId()) != null;
}
List<JobInfo> scheduledJobs = scheduler.getAllPendingJobs();
for (int i = 0, size = scheduledJobs.size(); i < size; i++) {
if (scheduledJobs.get(i).getId() == jobInfo.getId()) {
return true;
}
}
return false;
}
}