【疑难杂症】WorkManager在Widget使用中产生的负影响

目录

1. 现象

2. 原因

3. 方案


1. 现象

前段时间在开发某个应用的Widget时,由于数据集是一个集合,所以remoteViews采用了AdapterViewFlipper这么一个有轮播功能的View去展示列表数据。当数据集有变更时,原则上只需要调用AppWidgetManager.notifyAppWidgetViewDataChanged这个方法就可以走到我们本地重写的RemoteViewsFactory.onDataSetChanged方法,在这里面开始重新拉取数据并更新数据集,并会自动刷新桌面上的Widget。

但是实际上大概率会出现调用了notifyAppWidgetViewDataChanged方法但不会走到onDataSetChanged方法,导致数据一直无法更新,Widget内容也就没有更新。

2. 原因

经过一行一行代码排查,发现是在调用notifyAppWidgetViewDataChanged方法的前后由于业务需要同时也使用了WorkManager.enqueueUniqueWork来插入了一个延时任务,如果把WorkManager相关的代码注掉就没有问题了。

这时候我就好奇了,为毛WorkManager的插入任务操作会影响到小部件的刷新???

我在网上找了半天,中文互联网上我是没有搜到任何相关的信息,没办法只能求助万能的Google了,最后终于找到了有一篇文章说了类似的现象。

WorkManager, App Widgets, and the Cost of Side Effects (commonsware.com)

经过他的提醒,我顺着WorkManager源码和Widget源码一路查找,发现确实大致如这篇文章所说,那我就先说简单的结论:

WorkManager的插入任务会触发Intent.ACTION_PACKAGE_CHANGED广播,而AppWidgetServiceImpl类会监听这个广播,并采触发AppWidgetProvider的onUpdate方法。

从代码上看,

WorkManager插入任务时,会enable一个自定义的广播接收器组件:

public class EnqueueRunnable implements Runnable {
    @Override
public void run() {
    ...
    PackageManagerHelper.setComponentEnabled(context, RescheduleReceiver.class, true);
    ...
}

public class PackageManagerHelper {
    public static void setComponentEnabled(@NonNull Context context,@NonNull Class<?> klazz,boolean enabled) {
        ...
        packageManager.setComponentEnabledSetting(componentName,
            enabled
                ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
                : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
            PackageManager.DONT_KILL_APP);
        ...
    }
}

而在PackageManagerService的实现方法中,会发送一个Intent.ACTION_PACKAGE_CHANGED广播

private void setEnabledSettings(List<ComponentEnabledSetting> settings, int userId,String callingPackage) {
    ...
    try {
        final Computer newSnapshot = snapshotComputer();
        for (int i = 0; i < sendNowBroadcasts.size(); i++) {
            final String packageName = sendNowBroadcasts.keyAt(i);
            final ArrayList<String> components = sendNowBroadcasts.valueAt(i);
            final int packageUid = UserHandle.getUid(
                userId, pkgSettings.get(packageName).getAppId());
                sendPackageChangedBroadcast(newSnapshot, packageName, false /* dontKillApp */,
                    components, packageUid, null /* reason */);
        }
        ...
}

AppWidgetServiceImpl恰好监听了这个广播:

private void onPackageBroadcastReceived(Intent intent, int userId) {
    switch (action) {
        ...
        default: {
            ...
            changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
            ...
        }
    }
    ...
    synchronized (mLock) {
        ...
        if (added || changed) {
            ...
            for (String pkgName : pkgList) {
                componentsModified |= updateProvidersForPackageLocked(pkgName, userId, null);
                ...
            }
        }
        ...
    }
}

private boolean updateProvidersForPackageLocked(String packageName, int userId,
    Set<ProviderId> removedProviders) {
    ...
    if (packageName.equals(ai.packageName)) {
        ...
        AppWidgetProviderInfo info =
                createPartialProviderInfo(providerId, ri, provider);
        if (info != null) {
            ...
            final int M = provider.widgets.size();
            if (M > 0) {
                int[] appWidgetIds = getWidgetIds(provider.widgets);
                ...
                for (int j = 0; j < M; j++) {
                    Widget widget = provider.widgets.get(j);
                    widget.views = null;
                    scheduleNotifyProviderChangedLocked(widget);
                }
                // Now that we've told the host, push out an update.
                sendUpdateIntentLocked(provider, appWidgetIds);
            }
        }
    }
}

    private void scheduleNotifyProviderChangedLocked(Widget widget) {
        long requestId = UPDATE_COUNTER.incrementAndGet();
        if (widget != null) {
            // When the provider changes, reset everything else.
            widget.updateSequenceNos.clear();
            widget.updateSequenceNos.append(ID_PROVIDER_CHANGED, requestId);
        }
        ...
    }

在收到广播后触发这个应用所有Widget的onUpdate方法重新添加新的RemoteViews,并清除Widget里缓存的RemoteViews对象以及更新请求,而这个清除也就会导致清除掉我们调用notifyAppWidgetViewDataChanged方法添加的数据更新请求,所以回到桌面后,只会触发onUpdate,不会触发onDataSetChanged,所以我们的数据一直得不到更新。

尤其是文章中提到的,如果直接在AppWidgetProvider的onUpdate方法里面使用WorkManager,可能会陷入循环,造成不好的结果。

3. 方案

目前Google官方并没有给出解决方案,我们只能弃用WorkManager,还是直接使用AlarmManager或者JobSchedule来实现定时器效果,毕竟WorkManager本质上也是对这两种框架的包装而已,那我们干脆还是直接使用这两个即可。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值