本文是 Android Widget(小部件) 系列的第6⃣篇,主要从源码角度是对 Android widget 添加过程进行分析 。
本系列的目的是通过对Android 小部件的梳理,了解小部件刷新流程、恢复流程、以及系统发生变化时,小部件是如何适配的,解决在开发小部件过程中遇到的问题。系列文章大部份来自源码的解读,内容非常多,也非常容易遗忘,因此记录分享。
系列文章
Android Widget (小部件)基础介绍以及常见问题
Android Widget (小部件)刷新源码解析一非列表
Android Widget (小部件) option刷新源码解析
# Android Widget(小部件)添加源码分析一--申请widgetId
一、描述
添加小部件通常是 host 的逻辑,但了解该过程能够帮助我们熟悉小部件体系,分析小部件问题。由于每个 host 对小部件的逻辑处理都不一样,因此本文不梳理 host 中添加逻辑,仅从 AppWidgetHost .allocateAppWidgetId 开始。添加widget分三步、本篇文章讲解第二步绑定 widgetId
1. 申请widgetId
2. 绑定widgetId
3. 创建WidgetHostView
//1、申请
int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
//2、绑定
bound = mAppWidgetManager.bindAppWidgetIdIfAllowed(
appWidgetId, providerInfo.getProfile(), providerInfo.provider,bundle)
//3、创建视图
WidgetHostView hostView = (WidgetHostView) mAppWidgetHost.createView(mContext, appWidgetId ,
appWidgetProviderInfo);
二、绑定widgetId 流程
1、Host 进程通过 AIDL 回调到 system_server 中的AppWidgetServiceImpl
2、进行安全校验和小部件加载确定
3、找到对应的 widget provider,将对应packageName 放入包名集合中
4、如果是该小部件第一次添加,发送enable 广播
5、存储widget 信息
三、详细流程
1、AppWidgetManager.bindAppWidgetIdIfAllowed()
描述:通过AIDL 调用系统服务AppWidgetServiceImpl 详细代码:
class AppWidgetManager{
private boolean bindAppWidgetIdIfAllowed(int appWidgetId, int profileId,
ComponentName provider, Bundle options) {
if (mService == null) {
return false;
}
try {
return mService.bindAppWidgetId(mPackageName, appWidgetId,
profileId, provider, options);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
2、AppWidgetServiceImpl. bindAppWidgetId()
- 描述:包校验、确定已加载小部件、找到对应的 widget 和 provider,关联在一块;发送刷新广播,注册刷新广播;存储 widget 状态。
- 详细代码:
class AppWidgetServiceImpl{
@Override
public boolean bindAppWidgetId(String callingPackage, int appWidgetId,
int providerProfileId, ComponentName providerComponent, Bundle options) {
final int userId = UserHandle.getCallingUserId();
if (DEBUG) {
Slog.i(TAG, "bindAppWidgetId() " + userId);
}
// 包安全性校验,之前已解析过
// Make sure the package runs under the caller uid
mSecurityPolicy.enforceCallFromPackage(callingPackage);
// Check that if a cross-profile binding is attempted, it is allowed.
if (!mSecurityPolicy.isEnabledGroupProfile(providerProfileId)) {
return false;
}
// If the provider is not under the calling user, make sure this
// provider is allowlisted for access from the parent.
if (!mSecurityPolicy.isProviderInCallerOrInProfileAndWhitelListed(
providerComponent.getPackageName(), providerProfileId)) {
return false;
}
// 上面部分主要是为了确认权限
synchronized (mLock) {
// 确定小部件已经加载,前面已做详细介绍
ensureGroupStateLoadedLocked(userId);
// 作为host 需要有绑定权限
// A special permission or allowlisting is required to bind widgets.
if (!mSecurityPolicy.hasCallerBindPermissionOrBindWhiteListedLocked(
callingPackage)) {
return false;
}
// NOTE: The lookup is enforcing security across users by making
// sure the caller can only access widgets it hosts or provides.
// 找到对应的widget
Widget widget = lookupWidgetLocked(appWidgetId,
Binder.getCallingUid(), callingPackage);
if (widget == null) {
Slog.e(TAG, "Bad widget id " + appWidgetId);
return false;
}
if (widget.provider != null) {
Slog.e(TAG, "Widget id " + appWidgetId
+ " already bound to: " + widget.provider.id);
return false;
}
final int providerUid = getUidForPackage(providerComponent.getPackageName(),
providerProfileId);
if (providerUid < 0) {
Slog.e(TAG, "Package " + providerComponent.getPackageName() + " not installed "
+ " for profile " + providerProfileId);
return false;
}
// 上面是做一些基础的检查
// 根据uid 和组件信息找到对应的provider
// NOTE: The lookup is enforcing security across users by making
// sure the provider is in the already vetted user profile.
ProviderId providerId = new ProviderId(providerUid, providerComponent);
Provider provider = lookupProviderLocked(providerId);
if (provider == null) {
Slog.e(TAG, "No widget provider " + providerComponent + " for profile "
+ providerProfileId);
return false;
}
if (provider.zombie) {
Slog.e(TAG, "Can't bind to a 3rd party provider in"
+ " safe mode " + provider);
return false;
}
widget.provider = provider;
widget.options = (options != null) ? cloneIfLocalBinder(options) : new Bundle();
// We need to provide a default value for the widget category if it is not specified
if (!widget.options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) {
widget.options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN);
}
// provider 和widget 关联
provider.widgets.add(widget);
onWidgetProviderAddedOrChangedLocked(widget);
final int widgetCount = provider.widgets.size();
// 该小部件第一次添加的时候发生enabled 广播
if (widgetCount == 1) {
// Tell the provider that it's ready.
sendEnableIntentLocked(provider);
}
// 发送更新广播
// Send an update now -- We need this update now, and just for this appWidgetId.
// It's less critical when the next one happens, so when we schedule the next one,
// we add updatePeriodMillis to its start time. That time will have some slop,
// but that's okay.
// 更新时host 不存在也没有关系,host 建立后,调用startListening 就可以恢复
sendUpdateIntentLocked(provider, new int[] {appWidgetId});
// Schedule the future updates.
// 注册更新广播
registerForBroadcastsLocked(provider, getWidgetIds(provider.widgets));
// 存储状态
saveGroupStateAsync(userId);
if (DEBUG) {
Slog.i(TAG, "Bound widget " + appWidgetId + " to provider " + provider.id);
}
}
return true;
}
}
3、AppWidgetServiceImpl. enforceCallFromPackage()
描述:安全性校验,确定请求的包名和uid 是一致的。 详细代码:
public void enforceCallFromPackage(String packageName) {
mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName);
}
@Deprecated
public void checkPackage(int uid, @NonNull String packageName) {
try {
// 检查请求的 uid 和 packageName 是否一致
if (mService.checkPackage(uid, packageName) != MODE_ALLOWED) {
throw new SecurityException(
"Package " + packageName + " does not belong to " + uid);
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
4、AppWidgetServiceImpl. ensureGroupStateLoadedLocked() 确定小部件已经加载,申请widgetId已做详细介绍
5、AppWidgetServiceImpl.lookupWidgetLocked()
- 描述:找到对应的widget
- 详细代码:
class AppWidgetServiceImpl{
private Widget lookupWidgetLocked(int appWidgetId, int uid, String packageName) {
final int N = mWidgets.size();
for (int i = 0; i < N; i++) {
Widget widget = mWidgets.get(i);
if (widget.appWidgetId == appWidgetId
&& mSecurityPolicy.canAccessAppWidget(widget, uid, packageName)) {
return widget;
}
}
return null;
}
}
6、AppWidgetServiceImpl.lookupProviderLocked()
- 描述:找到对应provider
- 详细代码:
class AppWidgetServiceImpl {
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;
}
}
7、AppWidgetServiceImpl.onWidgetProviderAddedOrChangedLocked()
- 描述:保存widget 相应的package 信息,设置屏蔽状态
- 详细代码:
class AppWidgetServiceImpl{
void onWidgetProviderAddedOrChangedLocked(Widget widget) {
if (widget.provider == null) return;
int userId = widget.provider.getUserId();
synchronized (mWidgetPackagesLock) {
ArraySet<String> packages = mWidgetPackages.get(userId);
if (packages == null) {
mWidgetPackages.put(userId, packages = new ArraySet<String>());
}
packages.add(widget.provider.id.componentName.getPackageName());
}
// If we are adding a widget it might be for a provider that
// is currently masked, if so mask the widget.
// 设置屏蔽状态
if (widget.provider.isMaskedLocked()) {
maskWidgetsViewsLocked(widget.provider, widget);
} else {
widget.clearMaskedViewsLocked();
}
}
}
8、AppWidgetServiceImpl.sendEnableIntentLocked()
- 描述:发送enable 广播
- 详细代码
class AppWidgetServiceImpl{
private void sendEnableIntentLocked(Provider p) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED);
intent.setComponent(p.id.componentName);
sendBroadcastAsUser(intent, p.id.getProfile());
}
}
9、AppWidgetServiceImpl.sendUpdateIntentLocked()
描述:发送更新广播 详细代码
private void sendUpdateIntentLocked(Provider provider, int[] appWidgetIds) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
intent.setComponent(provider.id.componentName);
sendBroadcastAsUser(intent, provider.id.getProfile());
}
10、AppWidgetServiceImpl .registerForBroadcastsLocked()
描述:注册定时刷新的广播 详细代码
private void registerForBroadcastsLocked(Provider provider, int[] appWidgetIds) {
AppWidgetProviderInfo info = provider.getInfoLocked(mContext);
if (info.updatePeriodMillis > 0) {
// if this is the first instance, set the alarm. otherwise,
// rely on the fact that we've already set it and that
// PendingIntent.getBroadcast will update the extras.
boolean alreadyRegistered = provider.broadcast != null;
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
intent.setComponent(info.provider);
final long token = Binder.clearCallingIdentity();
try {
// Broadcast alarms sent by system are immutable
provider.broadcast = PendingIntent.getBroadcastAsUser(mContext, 1, intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE,
info.getProfile());
} finally {
Binder.restoreCallingIdentity(token);
}
if (!alreadyRegistered) {
// Set the alarm outside of our locks; we've latched the first-time
// invariant and established the PendingIntent safely.
final long period = Math.max(info.updatePeriodMillis, MIN_UPDATE_PERIOD);
final PendingIntent broadcast = provider.broadcast;
mSaveStateHandler.post(() ->
mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + period, period, broadcast)
);
}
}
}
11、AppWidgetServiceImpl .saveGroupStateAsync()
描述:异步存储存储状态 详细代码
class AppWidgetServiceImpl{
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);
}
}
}
}
12、AppWidgetServiceImpl . writeProfileStateToFileLocked() 将状态写入文件里面
class AppWidgetServiceImpl{
private boolean writeProfileStateToFileLocked(FileOutputStream stream, int userId) {
int N;
try {
TypedXmlSerializer out = Xml.resolveSerializer(stream);
out.startDocument(null, true);
out.startTag(null, "gs");
out.attributeInt(null, "version", CURRENT_VERSION);
N = mProviders.size();
for (int i = 0; i < N; i++) {
Provider provider = mProviders.get(i);
// Save only providers for the user.
if (provider.getUserId() != userId) {
continue;
}
if (provider.shouldBePersisted()) {
serializeProvider(out, provider);
}
}
N = mHosts.size();
for (int i = 0; i < N; i++) {
Host host = mHosts.get(i);
// Save only hosts for the user.
if (host.getUserId() != userId) {
continue;
}
serializeHost(out, host);
}
N = mWidgets.size();
for (int i = 0; i < N; i++) {
Widget widget = mWidgets.get(i);
// Save only widgets hosted by the user.
if (widget.host.getUserId() != userId) {
continue;
}
serializeAppWidget(out, widget, true);
}
Iterator<Pair<Integer, String>> it = mPackagesWithBindWidgetPermission.iterator();
while (it.hasNext()) {
Pair<Integer, String> binding = it.next();
// Save only white listings for the user.
if (binding.first != userId) {
continue;
}
out.startTag(null, "b");
out.attribute(null, "packageName", binding.second);
out.endTag(null, "b");
}
out.endTag(null, "gs");
out.endDocument();
return true;
} catch (IOException e) {
Slog.w(TAG, "Failed to write state: " + e);
return false;
}
}
}
}
到这里,添加 widget 绑定widgetId 流程就分析完了,过程其实也挺简单,安全校验、小部件加载确定也多次介绍过,下一篇讲添加 widget 视图创建。