Android Widget(小部件)删除源码分析

本文是 Android Widget(小部件) 系列的第八篇,主要从源码角度是对 Android widget 删除过程进行分析。

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

系列文章

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

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

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

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

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

Android Widget(小部件)添加源码分析二--绑定widgetId

Android Widget(小部件)添加源码分析三--创建视图

一、描述

删除小部件通常是 host 的逻辑,但了解该过程能够帮助我们熟悉小部件体系,分析小部件问题。由于每个 host 对小部件的逻辑处理都不一样,因此本文不梳理 host 中删除逻辑,仅从 deleteAppWidgetId 开始。

二、删除流程

java 复制代码
1、host进程调用 AppWidgetHost.deleteAppWidgetId(),通过AIDL回调到system_server
2、system_server进行一些列的校验,并确定系统中 widget 已经被加载。
3、找到对应的widget,发送相应的删除广播,如果该widget 是该小部件的最后一个卡片,则还会发出 disable 广播 
4、去掉定时刷新广播(小部件可以设置定时刷新)
5、存储小部件信息

三、详细流程

1、AppWidgetHost.deleteAppWidgetId()

  • 描述:在AppWidgetHostView 数组中移除对应的AppWidgetHostView。通过AIDL调用AppWidgetServiceImpl 的deleteAppWidgetId()

  • 详细代码

java 复制代码
public void deleteAppWidgetId(int appWidgetId) {
    if (sService == null) {
        return;
    }
    synchronized (mViews) {
        mViews.remove(appWidgetId);
        try {
            sService.deleteAppWidgetId(mContextOpPackageName, appWidgetId);
        }
        catch (RemoteException e) {
            throw new RuntimeException("system server dead?", e);
        }
    }
}

2、AppWidgetServiceImpl.deleteAppWidgetId()

  • 描述:进行安全校验,确认添加的小部件已经被加载过,找到对应的widget,删除对应小部件。更新系统中的小部件信息
  • 详细代码
java 复制代码
@Override
public void deleteAppWidgetId(String callingPackage, int appWidgetId) {
    final int userId = UserHandle.getCallingUserId();

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

    // Make sure the package runs under the caller uid.
      // AppWidgetServiceImpl 运行在system_process ,包名为字符串传入,
    // 检查uid 和package 是否对应
    mSecurityPolicy.enforceCallFromPackage(callingPackage);

    synchronized (mLock) {
        ensureGroupStateLoadedLocked(userId);

        // NOTE: The lookup is enforcing security across users by making
        // sure the caller can only access widgets it hosts or provides.
        Widget widget = lookupWidgetLocked(appWidgetId,
                Binder.getCallingUid(), callingPackage);

        if (widget == null) {
            return;
        }

        deleteAppWidgetLocked(widget);

        saveGroupStateAsync(userId);

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

3、AppWidgetServiceImpl.ensureGroupStateLoadedLocked()

  • 描述:未解锁抛出状态异常,解锁判断是否第一次添加、如第一次添加则构建provider 信息
  • 详细代码
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);
    }
}

4、AppWidgetServiceImpl.lookupWidgetLocked()

  • 描述:寻找对应的widget
  • 详细代码
java 复制代码
class AppWidgetServiceImpl {
    private Widget lookupWidgetLocked(int appWidgetId, int uid, String packageName) {
        final int N = mWidgets.size();
        // 遍历寻找对应的widget,并进行安全校验
        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;
    }
}

5、AppWidgetServiceImpl.deleteAppWidgetLocked()

  • 描述:

    • 解绑对应的RemoteViewService
    • 移除host 中widget 数组对应的widget
    • 处理host
    • 处理widget 的 packageName 数组,如果该包名下没有widget,从数组中移除对应的packageName
    • 移除对应 provider 的widgets 数组中的widget
    • 发送删除广播
    • 处理定时广播
    • 处理disable 广播
  • 详细

java 复制代码
class AppWidgetServiceImpl {
    private void deleteAppWidgetLocked(Widget widget) {
        // We first unbind all services that are bound to this id
        // Check if we need to destroy any services (if no other app widgets are
        // referencing the same service)
        // 解绑对应的RemoteViewService
        decrementAppWidgetServiceRefCount(widget);
        // 移除host 中widget 数组对应的widget
        Host host = widget.host;
        host.widgets.remove(widget);
        // 当host 没有数组且没有callback 时从host 数组移除
        pruneHostLocked(host);
        // 处理widget 的 packageName 数组,如果该包名下没有widget,从数组中移除对应的packageName
        removeWidgetLocked(widget);
    
        Provider provider = widget.provider;
        if (provider != null) {
            // 移除对应 provider 的widgets 数组中的widget
            provider.widgets.remove(widget);
            // 安全模式下,没有引用也不会删除,会保留,成为僵尸态
            if (!provider.zombie) {
                // send the broacast saying that this appWidgetId has been deleted
                // 发送删除广播
                sendDeletedIntentLocked(widget);
    
                if (provider.widgets.isEmpty()) {
                    // cancel the future updates
                    // 如果没有该类的 widget 会取消定时广播
                    cancelBroadcastsLocked(provider);
    
                    // send the broacast saying that the provider is not in use any more
                    // 如果没有该类的 widget 会发 disable 广播 
                    sendDisabledIntentLocked(provider);
                }
            }
        }
    }
}

6、AppWidgetServiceImpl.sendDeletedIntentLocked()

  • 描述:发送删除广播
  • 详细代码
java 复制代码
class AppWidgetServiceImpl {
    private void sendDeletedIntentLocked(Widget widget) {
        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED);
        intent.setComponent(widget.provider.id.componentName);
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widget.appWidgetId);
        sendBroadcastAsUser(intent, widget.provider.id.getProfile());
    }
}

7、AppWidgetServiceImpl.cancelBroadcastsLocked()

  • 描述:取消定时广播
  • 详细代码
java 复制代码
class AppWidgetServiceImpl {
    private void cancelBroadcastsLocked(Provider provider) {
        if (DEBUG) {
            Slog.i(TAG, "cancelBroadcastsLocked() for " + provider);
        }
        if (provider.broadcast != null) {
            final PendingIntent broadcast = provider.broadcast;
            mSaveStateHandler.post(() -> {
                    mAlarmManager.cancel(broadcast);
                    broadcast.cancel();
            });
            provider.broadcast = null;
        }
    }
}

8、AppWidgetServiceImpl.sendDisabledIntentLocked()

  • 描述:发送 disable 广播
  • 详细代码
java 复制代码
class AppWidgetServiceImpl {
    private void sendDisabledIntentLocked(Provider provider) {
        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED);
        intent.setComponent(provider.id.componentName);
        sendBroadcastAsUser(intent, provider.id.getProfile());
    }
}

到这里小部件的删除流程就分析完了,整体流程比较简单,核心思想就是校验、找widget、内存中移除、发送广播通知widget 进程、存储信息。了解这个流程能够帮助管理删除小部件关联信息。

相关推荐
xlsw_1 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
神仙别闹2 小时前
基于java的改良版超级玛丽小游戏
java
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭2 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫3 小时前
泛型(2)
java
超爱吃士力架3 小时前
邀请逻辑
java·linux·后端
南宫生3 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石3 小时前
12/21java基础
java
拭心3 小时前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
李小白663 小时前
Spring MVC(上)
java·spring·mvc
GoodStudyAndDayDayUp3 小时前
IDEA能够从mapper跳转到xml的插件
xml·java·intellij-idea