likes
comments
collection
share

Android Widget(小部件) 小部件版本升级更新分析

作者站长头像
站长
· 阅读数 6

本文是 Android Widget(小部件) 系列的第九篇,主要从源码角度是梳理 widget 升级时,系统是如何感知,以及视图是如何更新的。

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

系列文章

一、描述

二、小部件版本升级流程

Android Widget(小部件) 小部件版本升级更新分析

1、AppWidgetServiceImpl 监听 ACTION_PACKAGE_CHANGED 广播 
2、AppWidgetServiceImpl 收到 ACTION_PACKAGE_CHANGED 广播
3、ensureGroupStateLoadedLocked确保小部件相关信息已经加载到内存中
4、根据包名找到找到对应的provider
5、取消已经注册的定是刷新广播,重新注册定时刷新广播
6、找到相应的小部件回调AppHostView 重置视图
7、发送刷新广播,后续得更新流程同《Android Widget (小部件)刷新源码解析一非列表》《Android Widget (小部件)刷新源码解析一列表》
8、saveGroupStateAsync 存储相关状态

三、详细流程

1、 注册ACTION_PACKAGE_CHANGED 广播监听

  • 描述:AppWidgetService 初始化时在onStart() 方法中注册 package 相关的监听
  • 详细代码

public void onStart() {
 ...
 registerBroadcastReceiver();
 ...
}

private void registerBroadcastReceiver() {
    // Register for broadcasts about package install, etc., so we can
    // update the provider list.
    IntentFilter packageFilter = new IntentFilter();
    packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
    packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
    packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
    packageFilter.addDataScheme("package");
    mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
            packageFilter, null, null);
            ...
}

2、AppWidgetServiceImpl 收到ACTION_PACKAGE_CHANGED 广播。

  • 描述:AppWidgetServiceImpl 收到ACTION_PACKAGE_CHANGED广播,进行相应的安全性校验,找到对应的 widget ,发送刷新广播。
  • 详细代码
class AppWidgetServiceImpl {   
    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {       
        @Override 
        public void onReceive(Context context, Intent intent) { 
        ... 
            switch (action) { 
            ... 
            default: 
                // 走默认逻辑 
                onPackageBroadcastReceived(intent, getSendingUserId()); 
                break; 
            } 
        } 
    } 
} 

3、onPackageBroadcastReceived()广播处理

  • 描述:检查小部件加载状态,然后更新provider,保存widget信息
  • 详细代码
private void onPackageBroadcastReceived(Intent intent, int userId) { 
    final String action = intent.getAction(); 
    boolean added = false; 
    boolean changed = false; 
    boolean componentsModified = false; 


    final String pkgList[]; 
    switch (action) { 
        ... 
        default: { 
            Uri uri = intent.getData(); 
            if (uri == null) { 
                return; 
            } 
            String pkgName = uri.getSchemeSpecificPart(); 
            if (pkgName == null) { 
                return; 
            } 
            pkgList = new String[] { pkgName }; 
            added = Intent.ACTION_PACKAGE_ADDED.equals(action); 
            //设置组件可用和不可用时,也会触发ACTION_PACKAGE_CHANGED 
            changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); 
        } 
    } 
    if (pkgList == null || pkgList.length == 0) { 
        return; 
    } 
    
    synchronized (mLock) { 
        if (!mUserManager.isUserUnlockingOrUnlocked(userId) || 
                isProfileWithLockedParent(userId)) { 
            return; 
        } 
        // !!!确定小部件加载状态
        ensureGroupStateLoadedLocked(userId,  false); 


        Bundle extras = intent.getExtras(); 


        if (added || changed) { 
            final boolean newPackageAdded = added && (extras == null 
                    || !extras.getBoolean(Intent.EXTRA_REPLACING, false)); 


            for (String pkgName : pkgList) { 
            // !!! 更新provider
                componentsModified |= updateProvidersForPackageLocked(pkgName, userId, null); 

                if (newPackageAdded && userId == UserHandle.USER_SYSTEM) { 
                    final int uid = getUidForPackage(pkgName, userId); 
                    if (uid >= 0 ) { 
                        // 为 mHosts 中 uid 为 UNKNOWN_UID 重新赋值 
                        resolveHostUidLocked(pkgName, uid); 
                    } 
                } 
            } 
        } else { 
            // 包被移除 
            // If the package is being updated, we'll receive a PACKAGE_ADDED 
            // shortly, otherwise it is removed permanently. 
            final boolean packageRemovedPermanently = (extras == null 
                    || !extras.getBoolean(Intent.EXTRA_REPLACING, false)); 


            if (packageRemovedPermanently) { 
                for (String pkgName : pkgList) { 
                    componentsModified |= removeHostsAndProvidersForPackageLocked( 
                            pkgName, userId); 
                } 
            } 
        } 
        // 组件是否被修改 
        if (componentsModified) { 
            // 存储状态 
            saveGroupStateAsync(userId); 

            // If the set of providers has been modified, notify each active AppWidgetHost 
            //回调AppWidgetHost 的onProvidersChanged,默认方法没有。逻辑 
            scheduleNotifyGroupHostsForProvidersChangedLocked(userId); 
            // Possibly notify any new components of widget id changes 
            // 通知widgetId 改变的widget 
            mBackupRestoreController.widgetComponentsChanged(userId); 
        } 
    } 
} 

updateProvidersForPackageLocked

  • 描述:以下代码包含 根据包名找到找到对应的provider
    • 取消已经注册的定时刷新广播,重新注册定时刷新广播
    • 找到相应的小部件回调AppHostView 重置视图
    • 发送更新广播
    • 后续得更新流程同《Android Widget (小部件)刷新源码解析一非列表》《Android Widget (小部件)刷新源码解析一列表》
    • saveGroupStateAsync 存储相关状态 详细代码
private boolean updateProvidersForPackageLocked(String packageName, int userId, 
        Set<ProviderId> removedProviders) { 
    boolean providersUpdated = false; 

   // 查询包名对应应用中含有ACTION_APPWIDGET_UPDATE 的provider
    HashSet<ProviderId> keep = new HashSet<>(); 
    Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); 
    intent.setPackage(packageName); 
    List<ResolveInfo> broadcastReceivers = queryIntentReceivers(intent, userId); 


    // add the missing ones and collect which ones to keep 
    int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); 
    for (int i = 0; i < N; i++) { 
        ResolveInfo ri = broadcastReceivers.get(i); 
        ActivityInfo ai = ri.activityInfo; 
        // 应用被卸载的情况 
        if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { 
            continue; 
        } 


        if (packageName.equals(ai.packageName)) { 
            ProviderId providerId = new ProviderId(ai.applicationInfo.uid, 
                    new ComponentName(ai.packageName, ai.name)); 
            // mProviders 查找provider 
            Provider provider = lookupProviderLocked(providerId); 
            if (provider == null) { 
               // 不存在着新建一个 
                if (addProviderLocked(ri)) { 
                    keep.add(providerId); 
                    providersUpdated = true; 
                } 
            } else { 
                 // 重新创建info 
                AppWidgetProviderInfo info = 
                        createPartialProviderInfo(providerId, ri, provider); 
                if (info != null) { 
                    keep.add(providerId); 
                    // Use the new AppWidgetProviderInfo. 
                    provider.setPartialInfoLocked(info); 
                    // If it's enabled 
                    final int M = provider.widgets.size(); 
                    if (M > 0) { 
                        int[] appWidgetIds = getWidgetIds(provider.widgets); 
                        // Reschedule for the new updatePeriodMillis (don't worry about handling 
                        // it specially if updatePeriodMillis didn't change because we just sent 
                        // an update, and the next one will be updatePeriodMillis from now). 
                        // 取消广播 
                        cancelBroadcastsLocked(provider); 
                        // 重新注册广播 
                        registerForBroadcastsLocked(provider, appWidgetIds); 
                        // If it's currently showing, call back with the new 
                        // AppWidgetProviderInfo. 
                        for (int j = 0; j < M; j++) { 
                            Widget widget = provider.widgets.get(j); 
                            widget.views = null; 
                            // 通知providerChange,回调AppWidgetHost中方法 
                            scheduleNotifyProviderChangedLocked(widget); 
                        } 
                        // Now that we've told the host, push out an update. 
                        // 发送更新广播 
                        sendUpdateIntentLocked(provider, appWidgetIds); 
                    } 
                } 
                providersUpdated = true; 
            } 
        } 
    } 


    // prune the ones we don't want to keep 
    //  没在keep容器中,说明已经不需要,因此移除 
    N = mProviders.size(); 
    for (int i = N - 1; i >= 0; i--) { 
        Provider provider = mProviders.get(i); 
        if (packageName.equals(provider.id.componentName.getPackageName()) 
                && provider.getUserId() == userId 
                && !keep.contains(provider.id)) { 
            if (removedProviders != null) { 
                removedProviders.add(provider.id); 
            } 
            deleteProviderLocked(provider); 
            providersUpdated = true; 
        } 
    } 


    return providersUpdated; 
} 

4、根据包名找到找到对应的provider

  • 描述:查询包名对应应用中含有ACTION_APPWIDGET_UPDATE 的provider
  • 详细代码:
private boolean updateProvidersForPackageLocked(String packageName, int userId, 
        Set<ProviderId> removedProviders) { 
    boolean providersUpdated = false; 

   // 查询包名对应应用中含有ACTION_APPWIDGET_UPDATE 的provider
    HashSet<ProviderId> keep = new HashSet<>(); 
    Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); 
    intent.setPackage(packageName); 
    List<ResolveInfo> broadcastReceivers = queryIntentReceivers(intent, userId); 


    // add the missing ones and collect which ones to keep 
    int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); 
    for (int i = 0; i < N; i++) { 
        ResolveInfo ri = broadcastReceivers.get(i); 
        ActivityInfo ai = ri.activityInfo; 
        // 应用被卸载的情况 
        if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { 
            continue; 
        } 


        if (packageName.equals(ai.packageName)) { 
            ProviderId providerId = new ProviderId(ai.applicationInfo.uid, 
                    new ComponentName(ai.packageName, ai.name)); 
            // mProviders 查找provider 
            Provider provider = lookupProviderLocked(providerId); 
            ...
            } 
        } 
    } 


   


    return providersUpdated; 
4.1、queryIntentReceivers()

描述:查询对应的广播接收者信息 详细代码

//  这里的intent 为Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); 
private List<ResolveInfo> queryIntentReceivers(Intent intent, int userId) { 
    final long identity = Binder.clearCallingIdentity(); 
    try { 
        int flags = PackageManager.GET_META_DATA; 

        // We really need packages to be around and parsed to know if they 
        // provide widgets. 
        flags |= PackageManager.MATCH_DEBUG_TRIAGED_MISSING; 


        // Widget hosts that are non-crypto aware may be hosting widgets 
        // from a profile that is still locked, so let them see those 
        // widgets. 
        // 如果还是未解锁状态,过滤带直接启动的组件https://developer.android.com/training/articles/direct-boot?hl=zh-cn 
        if (isProfileWithUnlockedParent(userId)) { 
            flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE 
                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; 
        } 


        // 引用共享库的小部件需要加载它们的依赖项 
        flags |= PackageManager.GET_SHARED_LIBRARY_FILES; 


        return mPackageManager.queryIntentReceivers(intent, 
                intent.resolveTypeIfNeeded(mContext.getContentResolver()), 
                flags, userId).getList(); 
    } catch (RemoteException re) { 
        return Collections.emptyList(); 
    } finally { 
        Binder.restoreCallingIdentity(identity); 
    } 
} 
4.2、lookupProviderLocked在mProviders查找对应的provider

描述: 详细代码:

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; 
} 

5、取消已经注册的定时刷新广播,重新注册定时刷新广播

描述:根据找到的provider ,取消已经注册的定时广播,并且重新注册 详细代码:

private boolean updateProvidersForPackageLocked(String packageName, int userId, 
        Set<ProviderId> removedProviders) { 
                    ...
                    // 取消广播 
                   cancelBroadcastsLocked(provider); 
                   // 重新注册广播                                      
                   registerForBroadcastsLocked(provider, appWidgetIds); 
                   ...
  
    } 

6、 重置视图

6.1、AIDL通过回调,回调到host的的providerChanged

描述:通过widget host 的callback 回调 host 的providerChanged 方法。调用过程都是通过message 以及handler 完成,可能主要是保证一个顺序 详细 代码

private void scheduleNotifyProviderChangedLocked(Widget widget) { 
    long requestId = UPDATE_COUNTER.incrementAndGet(); 
    if (widget != null) { 
        // When the provider changes, reset everything else. 
        widget.updateSequenceNos.clear(); 
        widget.updateSequenceNos.append(ID_PROVIDER_CHANGED, requestId); 
    } 
    if (widget == null || widget.provider == null || widget.provider.zombie 
            || widget.host.callbacks == null || widget.host.zombie) { 
        return; 
    } 


    SomeArgs args = SomeArgs.obtain(); 
    args.arg1 = widget.host; 
    args.arg2 = widget.host.callbacks; 
    args.arg3 = widget.provider.getInfoLocked(mContext); 
    args.arg4 = requestId; 
    args.argi1 = widget.appWidgetId; 


    mCallbackHandler.obtainMessage( 
            CallbackHandler.MSG_NOTIFY_PROVIDER_CHANGED, 
            args).sendToTarget(); 
} 

private void handleNotifyProviderChanged(Host host, IAppWidgetHost callbacks, 
        int appWidgetId, AppWidgetProviderInfo info, long requestId) { 
    try { 
        callbacks.providerChanged(appWidgetId, info); 
        host.lastWidgetUpdateSequenceNo = requestId; 
    } catch (RemoteException re) { 
        synchronized (mLock){ 
            Slog.e(TAG, "Widget host dead: " + host.id, re); 
            host.callbacks = null; 
        } 
    } 
}


protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) { 
    AppWidgetHostView v; 


    // Convert complex to dp -- we are getting the AppWidgetProviderInfo from the 
    // AppWidgetService, which doesn't have our context, hence we need to do the 
    // conversion here. 
    appWidget.updateDimensions(mDisplayMetrics); 
    synchronized (mViews) { 
        v = mViews.get(appWidgetId); 
    } 
    if (v != null) { 
        v.resetAppWidget(appWidget); 
    } 
} 



6.2、重置视图

描述:调用updateAppWidget并传递一个空参数,这样就将host设置为默认视图 详细代码:

void resetAppWidget(AppWidgetProviderInfo info) { 
    setAppWidget(mAppWidgetId, info); 
    mViewMode = VIEW_MODE_NOINIT; 
    updateAppWidget(null); 
} 

public void updateAppWidget(RemoteViews remoteViews) { 
    mLastInflatedRemoteViews = remoteViews; 
    applyRemoteViews(remoteViews, true); 
} 

protected void applyRemoteViews(@Nullable RemoteViews remoteViews, boolean useAsyncIfPossible) { 
    boolean recycled = false; 
    View content = null; 
    Exception exception = null; 

    // Block state restore until the end of the apply. 
    mLastInflatedRemoteViewsId = -1; 


    if (mLastExecutionSignal != null) { 
        mLastExecutionSignal.cancel(); 
        mLastExecutionSignal = null; 
    } 

    if (remoteViews == null) { 
        if (mViewMode == VIEW_MODE_DEFAULT) { 
            // We've already done this -- nothing to do. 
            return; 
        } 
        content = getDefaultView(); 
        mLayoutId = -1; 
        mViewMode = VIEW_MODE_DEFAULT; 
    } else { 
        // 该部分逻辑省略 
    } 
    applyContent(content, recycled, exception); 
} 

7、发送刷新广播

private boolean updateProvidersForPackageLocked(String packageName, int userId, 
        Set<ProviderId> removedProviders) { 
    boolean providersUpdated = false; 
...
          sendUpdateIntentLocked(provider, appWidgetIds); 
...


    return providersUpdated; 
}

8、saveGroupStateAsync 存储相关状态 描述:使用存储hander ,在子线程中 widget 相关信息存入文件中 详细代码:

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);
        }
    }
}

private void saveStateLocked(int userId) {
    tagProvidersAndHosts();

    final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId);

    final int profileCount = profileIds.length;
    for (int i = 0; i < profileCount; i++) {
        final int profileId = profileIds[i];

        AtomicFile file = getSavedStateFile(profileId);
        FileOutputStream stream;
        try {
            stream = file.startWrite();
            if (writeProfileStateToFileLocked(stream, profileId)) {
                file.finishWrite(stream);
            } else {
                file.failWrite(stream);
                Slog.w(TAG, "Failed to save state, restoring backup.");
            }
        } catch (IOException e) {
            Slog.w(TAG, "Failed open state file for write: " + e);
        }
    }
}

到这里,小部件升级视图刷新的过程。应用升级时小部件会先恢复默认视图,再刷新真实视图。因此尽量不要加载视图的过程过长,否则会经常在默认视图,导致体验不好。同时 WorkManager、设置组件状态等 也会导致触发ACTION_PACKAGE_CHANGED ,导致视图重置,因此在有小部件时,使用这些功能需要慎重。