Android Widget(小部件) 小部件版本升级更新分析
本文是 Android Widget(小部件) 系列的第九篇,主要从源码角度是梳理 widget 升级时,系统是如何感知,以及视图是如何更新的。
本系列的目的是通过对 Android 小部件的梳理,了解小部件刷新流程、添加、删除、恢复流程、以及系统发生变化时,小部件是如何适配的,解决在开发小部件过程中遇到的问题。系列文章大部份来自源码的解读,内容非常多,也非常容易遗忘,因此记录分享。
系列文章
一、描述
二、小部件版本升级流程
1、AppWidgetServiceImpl 监听 ACTION_PACKAGE_CHANGED 广播
2、AppWidgetServiceImpl 收到 ACTION_PACKAGE_CHANGED 广播
3、ensureGroupStateLoadedLocked确保小部件相关信息已经加载到内存中
4、根据包名找到找到对应的provider
5、取消已经注册的定是刷新广播,重新注册定时刷新广播
6、找到相应的小部件回调AppHostView 重置视图
7、发送刷新广播,后续得更新流程同《Android Widget (小部件)刷新源码解析一非列表》《Android Widget (小部件)刷新源码解析一列表》
8、saveGroupStateAsync 存储相关状态
三、详细流程
1、 注册ACTION_PACKAGE_CHANGED 广播监听
- 描述:AppWidgetService 初始化时在onStart() 方法中注册 package 相关的监听
- 详细代码
public void onStart() {
...
registerBroadcastReceiver();
...
}
private void registerBroadcastReceiver() {
// Register for broadcasts about package install, etc., so we can
// update the provider list.
IntentFilter packageFilter = new IntentFilter();
packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
packageFilter.addDataScheme("package");
mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
packageFilter, null, null);
...
}
2、AppWidgetServiceImpl 收到ACTION_PACKAGE_CHANGED 广播。
- 描述:AppWidgetServiceImpl 收到ACTION_PACKAGE_CHANGED广播,进行相应的安全性校验,找到对应的 widget ,发送刷新广播。
- 详细代码
class AppWidgetServiceImpl {
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
...
switch (action) {
...
default:
// 走默认逻辑
onPackageBroadcastReceived(intent, getSendingUserId());
break;
}
}
}
}
3、onPackageBroadcastReceived()广播处理
- 描述:检查小部件加载状态,然后更新provider,保存widget信息
- 详细代码
private void onPackageBroadcastReceived(Intent intent, int userId) {
final String action = intent.getAction();
boolean added = false;
boolean changed = false;
boolean componentsModified = false;
final String pkgList[];
switch (action) {
...
default: {
Uri uri = intent.getData();
if (uri == null) {
return;
}
String pkgName = uri.getSchemeSpecificPart();
if (pkgName == null) {
return;
}
pkgList = new String[] { pkgName };
added = Intent.ACTION_PACKAGE_ADDED.equals(action);
//设置组件可用和不可用时,也会触发ACTION_PACKAGE_CHANGED
changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
}
}
if (pkgList == null || pkgList.length == 0) {
return;
}
synchronized (mLock) {
if (!mUserManager.isUserUnlockingOrUnlocked(userId) ||
isProfileWithLockedParent(userId)) {
return;
}
// !!!确定小部件加载状态
ensureGroupStateLoadedLocked(userId, false);
Bundle extras = intent.getExtras();
if (added || changed) {
final boolean newPackageAdded = added && (extras == null
|| !extras.getBoolean(Intent.EXTRA_REPLACING, false));
for (String pkgName : pkgList) {
// !!! 更新provider
componentsModified |= updateProvidersForPackageLocked(pkgName, userId, null);
if (newPackageAdded && userId == UserHandle.USER_SYSTEM) {
final int uid = getUidForPackage(pkgName, userId);
if (uid >= 0 ) {
// 为 mHosts 中 uid 为 UNKNOWN_UID 重新赋值
resolveHostUidLocked(pkgName, uid);
}
}
}
} else {
// 包被移除
// If the package is being updated, we'll receive a PACKAGE_ADDED
// shortly, otherwise it is removed permanently.
final boolean packageRemovedPermanently = (extras == null
|| !extras.getBoolean(Intent.EXTRA_REPLACING, false));
if (packageRemovedPermanently) {
for (String pkgName : pkgList) {
componentsModified |= removeHostsAndProvidersForPackageLocked(
pkgName, userId);
}
}
}
// 组件是否被修改
if (componentsModified) {
// 存储状态
saveGroupStateAsync(userId);
// If the set of providers has been modified, notify each active AppWidgetHost
//回调AppWidgetHost 的onProvidersChanged,默认方法没有。逻辑
scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
// Possibly notify any new components of widget id changes
// 通知widgetId 改变的widget
mBackupRestoreController.widgetComponentsChanged(userId);
}
}
}
updateProvidersForPackageLocked
- 描述:以下代码包含
根据包名找到找到对应的provider
- 取消已经注册的定时刷新广播,重新注册定时刷新广播
- 找到相应的小部件回调AppHostView 重置视图
- 发送更新广播
- 后续得更新流程同《Android Widget (小部件)刷新源码解析一非列表》《Android Widget (小部件)刷新源码解析一列表》
- saveGroupStateAsync 存储相关状态 详细代码
private boolean updateProvidersForPackageLocked(String packageName, int userId,
Set<ProviderId> removedProviders) {
boolean providersUpdated = false;
// 查询包名对应应用中含有ACTION_APPWIDGET_UPDATE 的provider
HashSet<ProviderId> keep = new HashSet<>();
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.setPackage(packageName);
List<ResolveInfo> broadcastReceivers = queryIntentReceivers(intent, userId);
// add the missing ones and collect which ones to keep
int N = broadcastReceivers == null ? 0 : broadcastReceivers.size();
for (int i = 0; i < N; i++) {
ResolveInfo ri = broadcastReceivers.get(i);
ActivityInfo ai = ri.activityInfo;
// 应用被卸载的情况
if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
continue;
}
if (packageName.equals(ai.packageName)) {
ProviderId providerId = new ProviderId(ai.applicationInfo.uid,
new ComponentName(ai.packageName, ai.name));
// mProviders 查找provider
Provider provider = lookupProviderLocked(providerId);
if (provider == null) {
// 不存在着新建一个
if (addProviderLocked(ri)) {
keep.add(providerId);
providersUpdated = true;
}
} else {
// 重新创建info
AppWidgetProviderInfo info =
createPartialProviderInfo(providerId, ri, provider);
if (info != null) {
keep.add(providerId);
// Use the new AppWidgetProviderInfo.
provider.setPartialInfoLocked(info);
// If it's enabled
final int M = provider.widgets.size();
if (M > 0) {
int[] appWidgetIds = getWidgetIds(provider.widgets);
// Reschedule for the new updatePeriodMillis (don't worry about handling
// it specially if updatePeriodMillis didn't change because we just sent
// an update, and the next one will be updatePeriodMillis from now).
// 取消广播
cancelBroadcastsLocked(provider);
// 重新注册广播
registerForBroadcastsLocked(provider, appWidgetIds);
// If it's currently showing, call back with the new
// AppWidgetProviderInfo.
for (int j = 0; j < M; j++) {
Widget widget = provider.widgets.get(j);
widget.views = null;
// 通知providerChange,回调AppWidgetHost中方法
scheduleNotifyProviderChangedLocked(widget);
}
// Now that we've told the host, push out an update.
// 发送更新广播
sendUpdateIntentLocked(provider, appWidgetIds);
}
}
providersUpdated = true;
}
}
}
// prune the ones we don't want to keep
// 没在keep容器中,说明已经不需要,因此移除
N = mProviders.size();
for (int i = N - 1; i >= 0; i--) {
Provider provider = mProviders.get(i);
if (packageName.equals(provider.id.componentName.getPackageName())
&& provider.getUserId() == userId
&& !keep.contains(provider.id)) {
if (removedProviders != null) {
removedProviders.add(provider.id);
}
deleteProviderLocked(provider);
providersUpdated = true;
}
}
return providersUpdated;
}
4、根据包名找到找到对应的provider
- 描述:查询包名对应应用中含有ACTION_APPWIDGET_UPDATE 的provider
- 详细代码:
private boolean updateProvidersForPackageLocked(String packageName, int userId,
Set<ProviderId> removedProviders) {
boolean providersUpdated = false;
// 查询包名对应应用中含有ACTION_APPWIDGET_UPDATE 的provider
HashSet<ProviderId> keep = new HashSet<>();
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.setPackage(packageName);
List<ResolveInfo> broadcastReceivers = queryIntentReceivers(intent, userId);
// add the missing ones and collect which ones to keep
int N = broadcastReceivers == null ? 0 : broadcastReceivers.size();
for (int i = 0; i < N; i++) {
ResolveInfo ri = broadcastReceivers.get(i);
ActivityInfo ai = ri.activityInfo;
// 应用被卸载的情况
if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
continue;
}
if (packageName.equals(ai.packageName)) {
ProviderId providerId = new ProviderId(ai.applicationInfo.uid,
new ComponentName(ai.packageName, ai.name));
// mProviders 查找provider
Provider provider = lookupProviderLocked(providerId);
...
}
}
}
return providersUpdated;
4.1、queryIntentReceivers()
描述:查询对应的广播接收者信息 详细代码
// 这里的intent 为Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
private List<ResolveInfo> queryIntentReceivers(Intent intent, int userId) {
final long identity = Binder.clearCallingIdentity();
try {
int flags = PackageManager.GET_META_DATA;
// We really need packages to be around and parsed to know if they
// provide widgets.
flags |= PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
// Widget hosts that are non-crypto aware may be hosting widgets
// from a profile that is still locked, so let them see those
// widgets.
// 如果还是未解锁状态,过滤带直接启动的组件https://developer.android.com/training/articles/direct-boot?hl=zh-cn
if (isProfileWithUnlockedParent(userId)) {
flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
}
// 引用共享库的小部件需要加载它们的依赖项
flags |= PackageManager.GET_SHARED_LIBRARY_FILES;
return mPackageManager.queryIntentReceivers(intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
flags, userId).getList();
} catch (RemoteException re) {
return Collections.emptyList();
} finally {
Binder.restoreCallingIdentity(identity);
}
}
4.2、lookupProviderLocked在mProviders查找对应的provider
描述: 详细代码:
private Provider lookupProviderLocked(ProviderId id) {
final int N = mProviders.size();
for (int i = 0; i < N; i++) {
Provider provider = mProviders.get(i);
if (provider.id.equals(id)) {
return provider;
}
}
return null;
}
5、取消已经注册的定时刷新广播,重新注册定时刷新广播
描述:根据找到的provider ,取消已经注册的定时广播,并且重新注册 详细代码:
private boolean updateProvidersForPackageLocked(String packageName, int userId,
Set<ProviderId> removedProviders) {
...
// 取消广播
cancelBroadcastsLocked(provider);
// 重新注册广播
registerForBroadcastsLocked(provider, appWidgetIds);
...
}
6、 重置视图
6.1、AIDL通过回调,回调到host的的providerChanged
描述:通过widget host 的callback 回调 host 的providerChanged 方法。调用过程都是通过message 以及handler 完成,可能主要是保证一个顺序 详细 代码
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);
}
if (widget == null || widget.provider == null || widget.provider.zombie
|| widget.host.callbacks == null || widget.host.zombie) {
return;
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = widget.host;
args.arg2 = widget.host.callbacks;
args.arg3 = widget.provider.getInfoLocked(mContext);
args.arg4 = requestId;
args.argi1 = widget.appWidgetId;
mCallbackHandler.obtainMessage(
CallbackHandler.MSG_NOTIFY_PROVIDER_CHANGED,
args).sendToTarget();
}
private void handleNotifyProviderChanged(Host host, IAppWidgetHost callbacks,
int appWidgetId, AppWidgetProviderInfo info, long requestId) {
try {
callbacks.providerChanged(appWidgetId, info);
host.lastWidgetUpdateSequenceNo = requestId;
} catch (RemoteException re) {
synchronized (mLock){
Slog.e(TAG, "Widget host dead: " + host.id, re);
host.callbacks = null;
}
}
}
protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
AppWidgetHostView v;
// Convert complex to dp -- we are getting the AppWidgetProviderInfo from the
// AppWidgetService, which doesn't have our context, hence we need to do the
// conversion here.
appWidget.updateDimensions(mDisplayMetrics);
synchronized (mViews) {
v = mViews.get(appWidgetId);
}
if (v != null) {
v.resetAppWidget(appWidget);
}
}
6.2、重置视图
描述:调用updateAppWidget并传递一个空参数,这样就将host设置为默认视图 详细代码:
void resetAppWidget(AppWidgetProviderInfo info) {
setAppWidget(mAppWidgetId, info);
mViewMode = VIEW_MODE_NOINIT;
updateAppWidget(null);
}
public void updateAppWidget(RemoteViews remoteViews) {
mLastInflatedRemoteViews = remoteViews;
applyRemoteViews(remoteViews, true);
}
protected void applyRemoteViews(@Nullable RemoteViews remoteViews, boolean useAsyncIfPossible) {
boolean recycled = false;
View content = null;
Exception exception = null;
// Block state restore until the end of the apply.
mLastInflatedRemoteViewsId = -1;
if (mLastExecutionSignal != null) {
mLastExecutionSignal.cancel();
mLastExecutionSignal = null;
}
if (remoteViews == null) {
if (mViewMode == VIEW_MODE_DEFAULT) {
// We've already done this -- nothing to do.
return;
}
content = getDefaultView();
mLayoutId = -1;
mViewMode = VIEW_MODE_DEFAULT;
} else {
// 该部分逻辑省略
}
applyContent(content, recycled, exception);
}
7、发送刷新广播
private boolean updateProvidersForPackageLocked(String packageName, int userId,
Set<ProviderId> removedProviders) {
boolean providersUpdated = false;
...
sendUpdateIntentLocked(provider, appWidgetIds);
...
return providersUpdated;
}
8、saveGroupStateAsync 存储相关状态 描述:使用存储hander ,在子线程中 widget 相关信息存入文件中 详细代码:
private void saveGroupStateAsync(int groupId) {
mSaveStateHandler.post(new SaveStateRunnable(groupId));
}
private final class SaveStateRunnable implements Runnable {
final int mUserId;
public SaveStateRunnable(int userId) {
mUserId = userId;
}
@Override
public void run() {
synchronized (mLock) {
// No need to enforce unlocked state when there is no caller. User can be in the
// stopping state or removed by the time the message is processed
ensureGroupStateLoadedLocked(mUserId, false /* enforceUserUnlockingOrUnlocked */ );
saveStateLocked(mUserId);
}
}
}
private void saveStateLocked(int userId) {
tagProvidersAndHosts();
final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId);
final int profileCount = profileIds.length;
for (int i = 0; i < profileCount; i++) {
final int profileId = profileIds[i];
AtomicFile file = getSavedStateFile(profileId);
FileOutputStream stream;
try {
stream = file.startWrite();
if (writeProfileStateToFileLocked(stream, profileId)) {
file.finishWrite(stream);
} else {
file.failWrite(stream);
Slog.w(TAG, "Failed to save state, restoring backup.");
}
} catch (IOException e) {
Slog.w(TAG, "Failed open state file for write: " + e);
}
}
}
到这里,小部件升级视图刷新的过程。应用升级时小部件会先恢复默认视图,再刷新真实视图。因此尽量不要加载视图的过程过长,否则会经常在默认视图,导致体验不好。同时 WorkManager、设置组件状态等 也会导致触发ACTION_PACKAGE_CHANGED ,导致视图重置,因此在有小部件时,使用这些功能需要慎重。
转载自:https://juejin.cn/post/7297160453427429430