Android Widget (小部件) option 更新流程

本文是 Android Widget(小部件) 系列的第四篇,主要从源码角度是对 Android widget option更新流程的分析 。

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

系列文章

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

Android Widget (小部件)刷新源码解析一非列表

Android Widget (小部件)刷新源码解析一列表

Android Widget (小部件) option 刷新源码解析一列表

小部件 option 更新

一、描述

option 通常用于host 与 应用widget 通信 。Host通过该通道将小部件最小宽高、最大宽高(OPTION_APPWIDGET_MIN_WIDTH、OPTION_APPWIDGET_MIN_HEIGHT、OPTION_APPWIDGET_MAX_WIDTH、OPTION_APPWIDGET_MAX_HEIGHT)等属性值传给小部件所在应用。当然host 也可以通过该通道定义其他的通信规则。理论上 widget 也是可以通过 option 和 host 通信,因为option 会存于widget中。

二、更新流程

scss 复制代码
1、Host 进程调用AppWidgetManager.updateAppWidgetOptions(),通过AIDL 回调到system_server
2、system_server对回调进行一系列的校验,找到对应的widget
2.1、SecurityPolicy.enforceCallFromPackage(),安全性校验,确定请求的包命和uid 是一致的
2.2、AppWidgetServiceImpl.ensureGroupStateLoadedLocked()
若已经加载过了则return,若没有加载过则根据uid 获取widgetProvider(过滤带刷新action 的广播),根据uid 获取相应的配置文件,根据配置文件设置widget,并绑定相应的host。放入mWidgets中。
2.3、AppWidgetServiceImpl.lookupWidgetLocked()
根据widgetId在mWidgets 找到对应的widget,通过uid验证权限
2.4、AppWidgetServiceImpl.sendOptionsChangedIntentLocked(),发送option change 的广播
3、system_server 通过广播通知应用widget

三、详细流程

1、Host 进程调用AppWidgetManager.updateAppWidgetOptions()

csharp 复制代码
class AppWidgetManager {
    public void updateAppWidgetOptions(int appWidgetId, Bundle options) { 
        if (mService == null) { 
            return; 
        } 
        try { 
        // 通过AIDL 回调到 AppWidgetServiceImpl 
            mService.updateAppWidgetOptions(mPackageName, appWidgetId, options); 
        } catch (RemoteException e) { 
            throw e.rethrowFromSystemServer(); 
        } 
    } 
}

2、通过AIDL 回调到system_server,对回调进行一系列的校验,找到对应的widget

scss 复制代码
class AppWidgetServiceImpl {
    @Override 
    public void updateAppWidgetOptions(String callingPackage, int appWidgetId, Bundle options) { 
        final int userId = UserHandle.getCallingUserId(); 
        if (DEBUG) { 
            Slog.i(TAG, "updateAppWidgetOptions() " + userId); 
        } 
        // Make sure the package runs under the caller uid. 
      // AppWidgetServiceImpl 运行在system_process ,包名为字符串传入, 
      // 安全性校验,确定请求的包命和uid 是一致的,后面详讲 
        mSecurityPolicy.enforceCallFromPackage(callingPackage); 
        synchronized (mLock) { 
         // 是否解锁状态,处于解锁状态,若第一次加载则构建widget,后面会详细解析 
            ensureGroupStateLoadedLocked(userId); 
            // NOTE: The lookup is enforcing security across users by making 
            // sure the caller can only access widgets it hosts or provides. 
         //根据widgetId在mWidgets 找到对应的widget,通过uid验证权限,后面详讲 
            Widget widget = lookupWidgetLocked(appWidgetId, 
                    Binder.getCallingUid(), callingPackage); 
            if (widget == null) { 
                return; 
            } 
            // Merge the options. 
            // 存储option , 因此option 同样可以用于widget 和host 的通信
            widget.options.putAll(options); 
            // Send the broacast to notify the provider that options changed. 
        //发送option change 的广播,通知应用widget,后面详讲 
            sendOptionsChangedIntentLocked(widget);   
        // 将当前的状态进行存储 
            saveGroupStateAsync(userId); 
        } 
    } 
}

2.1、SecurityPolicy.enforceCallFromPackage()安全性校验,确定请求的包命和uid 是一致的

typescript 复制代码
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(); 
    } 
} 

2.2、 AppWidgetServiceImpl.ensureGroupStateLoadedLocked ()

若已经加载过了则return,若没有加载过则根据uid 获取widgetProvider(过滤带刷新action 的广播),根据uid 获取相应的配置文件,根据配置文件设置widget,并绑定相应的host。放入mWidgets中。

java 复制代码
class AppWidgetServiceImpl{ 
   private void ensureGroupStateLoadedLocked(int userId, boolean enforceUserUnlockingOrUnlocked) { 
       // 判断该应用是否处在解锁状态,设备锁 
       if (enforceUserUnlockingOrUnlocked && !isUserRunningAndUnlocked(userId)) { 
           throw new IllegalStateException( 
                "User " + userId + " must be unlocked for widgets to be available"); 
       } 
   // 判断该应用文件配置是否处在解锁状态 
       if (enforceUserUnlockingOrUnlocked && isProfileWithLockedParent(userId)) { 
           throw new IllegalStateException( 
                "Profile " + userId + " must have unlocked parent"); 
       }   
       // 获取能用的配置配置id 
       final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId); 
       // 查看是否有未加载的user 
       // Careful lad, we may have already loaded the state for some 
       // group members, so check before loading and read only the 
       // state for the new member(s). 
       int newMemberCount = 0; 
       final int profileIdCount = profileIds.length; 
       for (int i = 0; i < profileIdCount; i++) { 
           final int profileId = profileIds[i]; 
           // >=0代表已经加载过,标记数组 
           if (mLoadedUserIds.indexOfKey(profileId) >= 0) { 
               profileIds[i] = LOADED_PROFILE_ID; 
           } else { 
               newMemberCount++; 
           } 
       } 
       // 没有新加的 便会return 
       if (newMemberCount <= 0) { 
           return; 
       }   
        // 构建新增加的ProfileId 数组,后续通常在第一次加载的时候执行 
       int newMemberIndex = 0; 
       final int[] newProfileIds = new int[newMemberCount]; 
       for (int i = 0; i < profileIdCount; i++) { 
           final int profileId = profileIds[i]; 
           if (profileId != LOADED_PROFILE_ID) { 
               mLoadedUserIds.put(profileId, profileId); 
               newProfileIds[newMemberIndex] = profileId; 
               newMemberIndex++; 
           } 
       } 
       // 清除provider 和 host 的tag 设置为 TAG_UNDEFINED 
       clearProvidersAndHostsTagsLocked(); 
        // 根据加载ProfileId 获取系统 ResolveInfo 列表, 根据ResolveInfo 构建 provider; 
       loadGroupWidgetProvidersLocked(newProfileIds);   
       // 从系统配置文件/data/system/users/0/appwidgets.xml 加载状态、 
       loadGroupStateLocked(newProfileIds); 
   } 
} 

2.3、lookupWidgetLocked

根据widgetId在mWidgets 找到对应的widget,通过uid验证权限

ini 复制代码
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; 
   } 
} 

2.4、sendOptionsChangedIntentLocked

scss 复制代码
class AppWidgetServiceImpl{  
   public void sendOptionsChangedIntentLocked(Widget widget) { 
     Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED); 
       intent.setComponent(widget.provider.id.componentName); 
       intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widget.appWidgetId); 
       intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, widget.options); 
       sendBroadcastAsUser(intent, widget.provider.id.getProfile()); 
   } 
} 

到这里,option 的更新流程就分析完了,option 刷新流程相对比较简单,主要作用是host 给widget 所在应用传递信息。当然也可用于widget 所在应用给host传递信息,不过host 需要找一个时机去读取信息。另外需要注意一点updateAppWidgetOptions调用属于跨进程调用,因此最好在子线程中调用

相关推荐
菜鸟国国33 分钟前
一步到位学 Compose + Paging3:从 0 到 1 实现分页加载(超详细新手教程)
android
TO_ZRG38 分钟前
Android Service基础
android
ECT-OS-JiuHuaShan2 小时前
功夫不负匠心人,渡劫代谢舞沧桑
android·开发语言·人工智能·算法·机器学习·kotlin·拓扑学
ZC跨境爬虫3 小时前
移动端爬虫工具Fiddler完整配置流程:PC+安卓模拟器全覆盖,零基础一次配置成功
android·前端·爬虫·测试工具·fiddler
巴德鸟3 小时前
DaVinci 常用技巧 关键帧 自动字幕 追踪 音频 冻结帧 快捷键 多轨道字幕 扩充边缘
android·编辑器·音视频·视频·davinci·davin
学习使我健康4 小时前
Android 广播介绍详情
android·开发语言·kotlin
dalancon5 小时前
AudioTrack Start 执行流程分析
android
众少成多积小致巨5 小时前
Android 初始化语言入门
android·linux·c++
Carson带你学Android5 小时前
谁才是地表最强 Android Agent 大模型?Google官方测评来了!
android·openai