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调用属于跨进程调用,因此最好在子线程中调用

相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
哈基米南北1 天前
ViewRootImpl 事件分发责任链分析
源码
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo1 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077001 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼1 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone1 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen1 天前
属性动画原理与高级动画实现
android·kotlin·canvas