Android Widget(小部件)添加源码分析二--绑定widgetId

574 阅读6分钟

本文是 Android Widget(小部件) 系列的第6⃣篇,主要从源码角度是对 Android widget 添加过程进行分析 。

本系列的目的是通过对Android 小部件的梳理,了解小部件刷新流程、恢复流程、以及系统发生变化时,小部件是如何适配的,解决在开发小部件过程中遇到的问题。系列文章大部份来自源码的解读,内容非常多,也非常容易遗忘,因此记录分享。

系列文章

Android Widget (小部件)基础介绍以及常见问题

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 流程

绑定widgetId (1).png

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 视图创建。