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

681 阅读4分钟

本文是 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中。

二、更新流程

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()

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

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 是一致的

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中。

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验证权限

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

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