Android Widget(小部件)添加源码分析一申请widgetId

528 阅读4分钟

本文是 Android Widget(小部件) 系列的第五篇,主要从源码角度是对 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.png

1、AppWidgetHost 进程通过 AIDL 回调到system_server中的 AppWidgetServiceImpl
2、进行安全性校验,确认小部件已经加载
3、自增创建widgetid
4、找到对应host、创建widget,关联widget 和host,将widget 放入对应集合中

三、详细流程

1.1、AppWidgetHost .allocateAppWidgetId()

  • 描述:通过AIDL 调用系统服务AppWidgetServiceImpl
  • 详细代码
public class AppWidgetHost {
    public int allocateAppWidgetId() {
        if (*sService* == null) {
            return -1;
        }
        try {
            return *sService*.allocateAppWidgetId(mContextOpPackageName, mHostId);
        }catch (RemoteException e) {
            throw new RuntimeException("system server dead?", e);
        }
    }
}

1.1.1、AppWidgetServiceImpl.allocateAppWidgetId()

  • 描述:进行安全性校验、自增创建widgetId、添加小部件、存储小部件信息
  • 详细代码
class AppWidgetServiceImpl {   
@Override
public int allocateAppWidgetId(String callingPackage, int hostId) {
    final int userId = UserHandle.getCallingUserId();

    if (DEBUG) {
        Slog.i(TAG, "allocateAppWidgetId() " + userId);
    }

    // Make sure the package runs under the caller uid.
    // 安全性校验,确定请求的包命和uid 是一致的。后面详讲
    mSecurityPolicy.enforceCallFromPackage(callingPackage);

    synchronized (mLock) {
        // Instant apps cannot host app widgets.
        // 排除快应用,快应用不能作为widget 的host
        if (mSecurityPolicy.isInstantAppLocked(callingPackage, userId)) {
            Slog.w(TAG, "Instant package " + callingPackage + " cannot host app widgets");
            return AppWidgetManager.INVALID_APPWIDGET_ID;
        }
        // 确定小部件的信息已经加载,后面有详细解析
        ensureGroupStateLoadedLocked(userId);
        // 如果该用户没有创建过widget 则从0开始。widget 初始化时,会从文件中读取之前的值,详看readProfileStateFromFileLocked
        if (mNextAppWidgetIds.indexOfKey(userId) < 0) {

            mNextAppWidgetIds.put(userId, AppWidgetManager.INVALID_APPWIDGET_ID + 1);
        }
        // 以userId 作为key ,每次递增后更新
        final int appWidgetId = incrementAndGetAppWidgetIdLocked(userId);

        // NOTE: The lookup is enforcing security across users by making
        // sure the caller can only access hosts it owns.
        HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
        Host host = lookupOrAddHostLocked(id);

        Widget widget = new Widget();
        widget.appWidgetId = appWidgetId;
        widget.host = host;

        host.widgets.add(widget);
        // 添加小部件,后面详讲
        addWidgetLocked(widget);
        // 存储状态,后面详讲
        saveGroupStateAsync(userId);

        if (DEBUG) {
            Slog.i(TAG, "Allocated widget id " + appWidgetId
                    + " for host " + host.id);
        }

        return appWidgetId;
    }
}

// 获取新添加小部件的widgetId
private int incrementAndGetAppWidgetIdLocked(int userId) {
    final int appWidgetId = peekNextAppWidgetIdLocked(userId) + 1;
    mNextAppWidgetIds.put(userId, appWidgetId);
    return appWidgetId;
}
}


private int peekNextAppWidgetIdLocked(int userId) {
    if (mNextAppWidgetIds.indexOfKey(userId) < 0) {
        return AppWidgetManager.INVALID_APPWIDGET_ID + 1;
    } else {
        return mNextAppWidgetIds.get(userId);
    }
}
1.1.1.1、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(); 
    } 
} 
1.1.1.2、AppWidgetServiceImpl. ensureGroupStateLoadedLocked()

描述:确定小部件的信息已经加载。 详细代码:

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); 
   } 
} 
1.1.1.3、AppWidgetServiceImpl. incrementAndGetAppWidgetIdLocked()
  • 描述:生成新的widgetId
  • 详细代码:
class AppWidgetServiceImpl {
    // 获取新添加小部件的widgetId
    private int incrementAndGetAppWidgetIdLocked(int userId) {
        final int appWidgetId = peekNextAppWidgetIdLocked(userId) + 1;
        mNextAppWidgetIds.put(userId, appWidgetId);
        return appWidgetId;
    }
    private int peekNextAppWidgetIdLocked(int userId) {
        if (mNextAppWidgetIds.indexOfKey(userId) < 0) {
            return AppWidgetManager.INVALID_APPWIDGET_ID + 1;
        } else {
            return mNextAppWidgetIds.get(userId);
        }
    }
}
1.1.1.4、AppWidgetServiceImpl.addWidgetLocked()

描述:添加widget,将widget,以及widget 对应的包名存储到相应的集合中 详细代码:

class AppWidgetServiceImpl {
    
    void addWidgetLocked(Widget widget) {
        mWidgets.add(widget);
    
        onWidgetProviderAddedOrChangedLocked(widget);
    }
    
    // 将widget 的packageName 存入mWidgetPackages
    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();
        }
    }   
}

到这里,添加 widget 申请 widgetId 流程就分析完了,过程其实也挺简单,安全校验、小部件加载确定也多次介绍过,下一篇讲添加 widget 之 绑定widgetId。