WorkManager基本使用及源码分析(七) - BroadcastReceiver

2021年02月15日 99 字 Jetpack


源码篇 - 广播接收者

此篇将重点介绍WorkManager使用的重要组件:广播接收者,主要涉及意外停止监听广播ForceStopRunnable.BroadcastReceiver、约束状态监听广播ConstraintProxy.*、启动重新规划服务的广播RescheduleReceiver、代理约束更新广播ConstraintProxyUpdateReceiver、测试诊断广播DiagnosticsReceiver。此类组件为WorkManager稳定运行、重新规划、约束更新提供了支持。

ForceStopRunnable

WorkManager is restarted after an app was force stopped. Alarms and Jobs get cancelled when an application is force-stopped. To reschedule, we create a pending alarm that will not survive force stops.

强制停止应用程序后,WorkManager将重新启动。当应用被强制停止时,AlarmsJobs将被取消。为了重新安排,我们要创建一个无法通过强制停止的Alarm

在介绍ForceStopRunnable.BroadcastReceiver前,我们先简单看一下它的外部类ForceStopRunnable

ForceStopRunnable是一个Runnable对象,核心方法是run, 其在WorkManager初始化时调用WorkManagerImpl.internalInit(), 构造并调用了ForceStopRunnable.run()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Override
public void run() {
// 如果需要,将数据库迁移到不备份的目录。
WorkDatabasePathHelper.migrateDatabase(mContext);
// 清除属于WorkManager的无效作业,以及由于应用程序崩溃(运行状态)而可能被中断的作业。
try {
boolean needsScheduling = cleanUp();
if (shouldRescheduleWorkers()) {
mWorkManager.rescheduleEligibleWork();
// 将WorkManager标记为已迁移。
mWorkManager.getPreferenceUtils().setNeedsReschedule(false);
} else if (isForceStopped()) {
// 异常退出,重新规划任务
mWorkManager.rescheduleEligibleWork();
} else if (needsScheduling) {
// 发现未完成任务,规划它们
Schedulers.schedule(
mWorkManager.getConfiguration(),
mWorkManager.getWorkDatabase(),
mWorkManager.getSchedulers());
}
mWorkManager.onForceStopRunnableCompleted();
} catch (SQLiteCantOpenDatabaseException
| SQLiteDatabaseCorruptException
| SQLiteAccessPermException exception) {
// ForceStopRunnable通常是访问数据库(或应用程序的内部数据目录)的第一件事。
// 这意味着奇怪的PackageManager错误被归咎于ForceStopRunnable,这是不幸的。
// 这为开发人员提供了更好的错误消息。
String message =
"The file system on the device is in a bad state. WorkManager cannot access "
+ "the app's internal data store.";
Logger.get().error(TAG, message, exception);
throw new IllegalStateException(message, exception);
}
}

我们发现ForceStopRunable主要职责是处理异常中断后对WorkManager中任务进行清理和重新规划,run()主要干了3件事:

  • 如果需要,将数据库迁移到不备份的目录
  • 取消无效的JobScheduler作业/重新调度以前正在运行的作业
  • 针对异常中断时WorkManager的状态进行不同的调度操作

那么他是如何判断是否是强制中断的呢,我们可以看下强制中断判断代码isForceStopped():

1
2
3
4
5
6
7
8
9
10
11
12
13
@VisibleForTesting
public boolean isForceStopped() {
// 当应用程序在Eclair MR1强制停止启动时,Alarm被取消。
// 在N-MR1中引入了强制停止作业的取消(SDK 25)。
// 尽管API 23、24可能是安全的,但oem可能会选择采用不同的方法。
PendingIntent pendingIntent = getPendingIntent(mContext, FLAG_NO_CREATE);
if (pendingIntent == null) {
setAlarm(mContext);
return true;
} else {
return false;
}
}

通过判断闹钟广播是否存在来确定应用是否被强行停止,若不存在即闹钟Alarm被取消即是强制中断,此时将重新设置一个闹钟Alarm;

让我们来看看上文中的ForceStopRunnable.BroadcastReceiver闹钟广播。

BroadcastReceiver

A {@link android.content.BroadcastReceiver} which takes care of recreating the long lived alarm which helps track force stops for an application. This is the target of the alarm set by ForceStopRunnable in {@link #setAlarm(Context)}. 一个{@link android.content.BroadcastReceiver}负责重新创建长寿命的Alarm,这有助于跟踪应用程序的强制停止。这是在{@link #setAlarm(Context)}中由forceoprunnable设置的Alarm目标。

从描述可知,此广播的主要目的是负责创建长期存活的闹钟,用以追踪应用程序的强制停止。

其代码相对简单,若收到action为ACTION_FORCE_STOP_RESCHEDULE的广播,则设置一个长达十年的可唤醒设备的闹钟,然后再来一次(发送action为ACTION_FORCE_STOP_RESCHEDULE的广播)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// ForceStopRunnable

private static PendingIntent getPendingIntent(Context context, int flags) {
Intent intent = getIntent(context);
// 发送广播的PendingIntent
return PendingIntent.getBroadcast(context, ALARM_ID, intent, flags);
}

@VisibleForTesting
static Intent getIntent(Context context) {
Intent intent = new Intent();
intent.setComponent(new ComponentName(context, ForceStopRunnable.BroadcastReceiver.class));
// 广播intent
intent.setAction(ACTION_FORCE_STOP_RESCHEDULE);
return intent;
}

static void setAlarm(Context context) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
//使用FLAG_UPDATE_CURRENT,因为我们只需要这个Alarm的一次实例
PendingIntent pendingIntent = getPendingIntent(context, FLAG_UPDATE_CURRENT);
// 设置执行时间为十年
long triggerAt = System.currentTimeMillis() + TEN_YEARS;
if (alarmManager != null) {
if (Build.VERSION.SDK_INT >= 19) {
// RTC_WAKEUP:绝对时间可唤醒类型,十年后,干啥
alarmManager.setExact(RTC_WAKEUP, triggerAt, pendingIntent);
} else {
alarmManager.set(RTC_WAKEUP, triggerAt, pendingIntent);
}
}
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static class BroadcastReceiver extends android.content.BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Our alarm somehow got triggered, so make sure we reschedule it. This should really never happen because we set it so far in the future.
// 我们的闹钟不知怎么被触发了,所以一定要重新安排时间。这是不应该发生的,因为我们将它设置在遥远的未来(嘿,十年后再见)。
if (intent != null) {
String action = intent.getAction();
if (ACTION_FORCE_STOP_RESCHEDULE.equals(action)) {
Logger.get().verbose(
TAG,
"Rescheduling alarm that keeps track of force-stops.");
ForceStopRunnable.setAlarm(context);
}
}
}
}

有前文可知,应用启动时会初始化WorkManager,WorkManager初始化则会执行ForceStopRunnable.run(), 此时一般会调用setAlarm(), 创建Alarm,极端情况下会存在应用存活十年的情况,此BroadcastReceiver即是用于处理这种情况的,当十年后又收到了这个广播,那么我们在创建一个十年期的闹钟Alarm,哦,天呐。

ConstraintProxy

BatteryNotLowProxy
BatteryChargingProxy
StorageNotLowProxy
NetworkStateProxy

上述BatteryNotLowProxyBatteryChargingProxyStorageNotLowProxyNetworkStateProxy均是ConstraintProxy的子类,实现均一致,仅有注册action不同,用以针对不同action的系统广播更新约束状态, 此处一并分析之。

NetworkStateProxy为例,当网络状态变化,应用收到"android.net.conn.CONNECTIVITY_CHANGE"广播,触发NetworkStateProxy.onReceive():

1
2
3
4
5
6
@Override
public void onReceive(Context context, Intent intent) {
Logger.get().debug(TAG, String.format("onReceive : %s", intent));
Intent constraintChangedIntent = CommandHandler.createConstraintsChangedIntent(context);
context.startService(constraintChangedIntent);
}

调用CommandHandler.createConstraintsChangedIntent(context)调起SystemAlarmService, 其intent的action为"ACTION_CONSTRAINTS_CHANGED"

1
2
3
4
5
static Intent createConstraintsChangedIntent(@NonNull Context context) {
Intent intent = new Intent(context, SystemAlarmService.class);
intent.setAction(ACTION_CONSTRAINTS_CHANGED);
return intent;
}

WorkManager基本使用及源码分析(三) - SystemAlarmService讲述流程可知,最终会调用到CommandHandler.onHandleIntent()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void onHandleIntent(@NonNull Intent intent, int startId, @NonNull SystemAlarmDispatcher dispatcher) {
String action = intent.getAction();

if (ACTION_CONSTRAINTS_CHANGED.equals(action)) {
handleConstraintsChanged(intent, startId, dispatcher);
}
// 其他
}

private void handleConstraintsChanged(
@NonNull Intent intent, int startId,
@NonNull SystemAlarmDispatcher dispatcher) {
ConstraintsCommandHandler changedCommandHandler =
new ConstraintsCommandHandler(mContext, startId, dispatcher);
changedCommandHandler.handleConstraintsChanged();
}

此时intent的action为"ACTION_CONSTRAINTS_CHANGED",最终会调用到ConstraintsCommandHandler.handleConstraintsChanged()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void handleConstraintsChanged() {
List<WorkSpec> candidates = mDispatcher.getWorkManager().getWorkDatabase()
.workSpecDao()
.getScheduledWork();

//更新约束代理以潜在地禁用先前完成的WorkSpecs的代理。
ConstraintProxy.updateAll(mContext, candidates);

// 这需要在每个约束控制器中填充匹配的WorkSpec id。标记正在更新这些工作的状态
mWorkConstraintsTracker.replace(candidates);

List<WorkSpec> eligibleWorkSpecs = new ArrayList<>(candidates.size());
// 筛选候选人应该已经规划好了。
long now = System.currentTimeMillis();
for (WorkSpec workSpec : candidates) {
String workSpecId = workSpec.id;
long triggerAt = workSpec.calculateNextRunTime();
// 时间条件符合且无约束或约束已符合,则加入符合条件集合
if (now >= triggerAt && (!workSpec.hasConstraints()
|| mWorkConstraintsTracker.areAllConstraintsMet(workSpecId))) {
eligibleWorkSpecs.add(workSpec);
}
}

for (WorkSpec workSpec : eligibleWorkSpecs) {
String workSpecId = workSpec.id;
Intent intent = CommandHandler.createDelayMetIntent(mContext, workSpecId);
// 告诉SystemAlarmDispatcher这个工作已经符合条件了,请给安排上
mDispatcher.postOnMainThread(
new SystemAlarmDispatcher.AddRunnable(mDispatcher, intent, mStartId));
}
// 重置正在更新这些工作状态的标记
mWorkConstraintsTracker.reset();
}

此方法先获取了规划中的工作生成集合,调用了ConstraintProxy.updateAll()方法更新约束代理以潜在地禁用先前完成的WorkSpecs的代理,遍历判断每个WorkSpec是否符合条件,遍历符合约束条件的列表,告诉SystemAlarmDispatcher添加任务,若SystemAlarmDispatcher中无任务则分发执行任务(还记得processCommand()是一个环状的轮询吗?)。

SystemAlarmDispatcher.AddRunnable()后续调用SystemAlarmDispatcher.add()WorkManager基本使用及源码分析(三) - SystemAlarmService中有详细说明,此处仅摘要流程。
SystemAlarmDispatcher.AddRunnable() -> SystemAlarmDispatcher.add()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// SystemAlarmDispatcher
public boolean add(@NonNull final Intent intent, final int startId) {

String action = intent.getAction();
if (TextUtils.isEmpty(action)) {
return false;
}

if (CommandHandler.ACTION_CONSTRAINTS_CHANGED.equals(action)
&& hasIntentWithAction(CommandHandler.ACTION_CONSTRAINTS_CHANGED)) {
return false;
}

intent.putExtra(KEY_START_ID, startId);
synchronized (mIntents) {
boolean hasCommands = !mIntents.isEmpty();
mIntents.add(intent);
if (!hasCommands) {
processCommand();
}
}
return true;
}

static class AddRunnable implements Runnable {
@Override
public void run() {
mDispatcher.add(mIntent, mStartId);
}
}

ConstraintProxyUpdateReceiver

上面讲到ConstraintsCommandHandler.handleConstraintsChanged()第一步会调用ConstraintProxy.updateAll(mContext, candidates)最终会发送广播给ConstraintProxyUpdateReceiver更新约束代理以潜在地禁用先前完成的WorkSpecs的代理,我们简单过一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static void updateAll(Context context, List<WorkSpec> workSpecs) {
boolean batteryNotLowProxyEnabled = false;
boolean batteryChargingProxyEnabled = false;
boolean storageNotLowProxyEnabled = false;
boolean networkStateProxyEnabled = false;
// 查找每个WorkSpec(是否有)约束条件状态
for (WorkSpec workSpec : workSpecs) {
Constraints constraints = workSpec.constraints;
batteryNotLowProxyEnabled |= constraints.requiresBatteryNotLow();
batteryChargingProxyEnabled |= constraints.requiresCharging();
storageNotLowProxyEnabled |= constraints.requiresStorageNotLow();
networkStateProxyEnabled |= constraints.getRequiredNetworkType() != NOT_REQUIRED;
// 知道需要全部广播接收者,就不用判断了
if (batteryNotLowProxyEnabled && batteryChargingProxyEnabled
&& storageNotLowProxyEnabled && networkStateProxyEnabled) {
break;
}
}

Intent updateProxyIntent =
ConstraintProxyUpdateReceiver.newConstraintProxyUpdateIntent(
context,
batteryNotLowProxyEnabled,
batteryChargingProxyEnabled,
storageNotLowProxyEnabled,
networkStateProxyEnabled);

// ConstraintProxies are being updated via a separate broadcast receiver.
// For more information on why we do this look at b/73549299
context.sendBroadcast(updateProxyIntent);
}

这里先是判断需要哪些广播接收者,会发送广播给ConstraintProxyUpdateReceiver,最终会调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Override
public void onReceive(@NonNull final Context context, @Nullable final Intent intent) {
String action = intent != null ? intent.getAction() : null;
if (!ACTION.equals(action)) {

} else {
final PendingResult pendingResult = goAsync();
WorkManagerImpl workManager = WorkManagerImpl.getInstance(context);
TaskExecutor taskExecutor = workManager.getWorkTaskExecutor();
taskExecutor.executeOnBackgroundThread(new Runnable() {
@Override
public void run() {
try {
// 在后台线程上执行此操作,因为使用PackageManager来启用或禁用代理涉及到对文件系统的写操作。
boolean batteryNotLowProxyEnabled = intent.getBooleanExtra(
KEY_BATTERY_NOT_LOW_PROXY_ENABLED, false);
boolean batteryChargingProxyEnabled = intent.getBooleanExtra(
KEY_BATTERY_CHARGING_PROXY_ENABLED, false);
boolean storageNotLowProxyEnabled = intent.getBooleanExtra(
KEY_STORAGE_NOT_LOW_PROXY_ENABLED, false);
boolean networkStateProxyEnabled = intent.getBooleanExtra(
KEY_NETWORK_STATE_PROXY_ENABLED, false);

Logger.get().debug(
TAG,
String.format("Updating proxies: BatteryNotLowProxy enabled (%s), "
+ "BatteryChargingProxy enabled (%s), "
+ "StorageNotLowProxy (%s), "
+ "NetworkStateProxy enabled (%s)",
batteryNotLowProxyEnabled,
batteryChargingProxyEnabled,
storageNotLowProxyEnabled,
networkStateProxyEnabled));

PackageManagerHelper.setComponentEnabled(context, BatteryNotLowProxy.class,
batteryNotLowProxyEnabled);
PackageManagerHelper.setComponentEnabled(context,
BatteryChargingProxy.class,
batteryChargingProxyEnabled);
PackageManagerHelper.setComponentEnabled(context, StorageNotLowProxy.class,
storageNotLowProxyEnabled);
PackageManagerHelper.setComponentEnabled(context, NetworkStateProxy.class,
networkStateProxyEnabled);
} finally {
pendingResult.finish();
}
}
});
}
}

主要执行了获取前文提供的需要哪些约束广播接收器,调用PackageManagerHelper.setComponentEnabled(context, StorageNotLowProxy.class, storageNotLowProxyEnabled)更改广播接收者注册信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 使用{@link PackageManager} 去开/关manifest声明的组件
*
* @param context {@link Context}
* @param klazz The class of component
* @param enabled {@code true} 开关组件
*/
public static void setComponentEnabled(
@NonNull Context context,
@NonNull Class<?> klazz,
boolean enabled) {
try {
PackageManager packageManager = context.getPackageManager();
ComponentName componentName = new ComponentName(context, klazz.getName());
packageManager.setComponentEnabledSetting(componentName,
enabled
? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);

} catch (Exception exception) {
}
}

RescheduleReceiver

Reschedules alarms on BOOT_COMPLETED and other similar scenarios.

在BOOT_COMPLETED和其他类似场景下重新调度Alarm。

DiagnosticsReceiver

The {@link android.content.BroadcastReceiver} which dumps out useful diagnostics information. 输出有用的诊断信息