概述

我们先看下IconCache的初始化过程,接着看下IconCache核心数据结构、算法,最后介绍与之关联的几个类。
Launcher.java
public class Launcher extends StatefulActivity<LauncherState> implements ... {
    ...
    public static final String TAG = "Launcher";
    private LauncherModel mModel;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        LauncherAppState app = LauncherAppState.getInstance(this);
        mOldConfig = new Configuration(getResources().getConfiguration());
        mModel = app.getModel();
        ...
       }
   }- 这个类是Launcher的主入口,即 MainActivity。
onCreate()做了许多界面和管理器的初始化。 - 这里我们关注是初始化了 
LauncherAppState和LauncherModel 
LauncherAppState.java
public class LauncherAppState {
    // 注释1
    // We do not need any synchronization for this variable as its only written on UI thread.
    public static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
            new MainThreadInitializedObject<>(LauncherAppState::new);
    private final Context mContext;
    private final LauncherModel mModel;
    private final IconProvider mIconProvider;
    private final IconCache mIconCache;
    private final DatabaseWidgetPreviewLoader mWidgetCache;
    private final InvariantDeviceProfile mInvariantDeviceProfile;
    private final RunnableList mOnTerminateCallback = new RunnableList();
    public static LauncherAppState getInstance(final Context context) {
        return INSTANCE.get(context);
    }
    public LauncherAppState(Context context) {
        // 注释2
        this(context, LauncherFiles.APP_ICONS_DB);
        ...
    }
   public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
       ...
       // 注释3
       mIconCache = new IconCache(mContext, mInvariantDeviceProfile,
       iconCacheFileName, mIconProvider);
       ...
   }
}- 注释1:
LauncherAppState是单例,且限定在主线程上初始化 - 注释2 注释3 传入数据库名字 
app_icon.db,进而初始化IconCache mModel即数据管理器,用于维护启动器的内存状态。预计静态中应该只有一个LauncherModel对象。还提供用于更新 Launcher 的数据库状态的 API。mIconCache应用程序icon和title的缓存,图标可以由任何线程创建。mWidgetCache存储widget预览信息的数据库
LoaderTask.java
是有数据管理类LauncherModel来调用的,其核心是Run方法。

主要分为四大步骤,并开启事务机制来管理
加载与绑定桌面内容
- loadWorkspace
 - sanitizeData
 - bindWorkspace
 - sendFirstScreenActiveInstallsBroadcast
 
加载和绑定所有的应用图标和信息
- loadAllApps
 - bindAllApps
 - update icon cache 对应图标缓存逻辑类 
LauncherActivityCachingLogic - save shortcuts in icon cache
 
这一步实际是在第一步的,对应的图标缓存逻辑类 ShortcutCachingLogic
加载和绑定所有DeepShortcuts
- loadDeepShortcuts
 - bindDeepShortcuts
 - save deep shortcuts in icon cache 对应的图标缓存逻辑类 
ShortcutCachingLogic 
加载和绑定所有的Widgets
- load widgets
 - bindWidgets
 - save widgets in icon cache 对应的图标缓存逻辑类 
ComponentWithIconCachingLogic 
IconCacheUpdateHandler.java
在IconCacheUpdateHandler扫描到所有应用后,会开启一个线程 SerializedIconUpdateTask进行更新图标操作,把图标缓存到内存和数据库里。
调用流程

- 在上面LoaderTask过程中更新图标用的是
IconCacheUpdateHandler.updateIcons(), - 这是个工具类,处理更新图标缓存, 处理业务与
IconCache的连接 - 内部类 
SerializedIconUpdateTask序列化图标更新任务,即将这些图标信息存储或者更新到数据库中 
举例说明过程

updateIcons
public <T> void updateIcons(List<T> apps, CachingLogic<T> cachingLogic,
        OnUpdateCallback onUpdateCallback) {
    // Filter the list per user
    HashMap<UserHandle, HashMap<ComponentName, T>> userComponentMap = new HashMap<>();
    int count = apps.size();
    for (int i = 0; i < count; i++) {
        T app = apps.get(i);
        UserHandle userHandle = cachingLogic.getUser(app);
        HashMap<ComponentName, T> componentMap = userComponentMap.get(userHandle);
        if (componentMap == null) {
            componentMap = new HashMap<>();
            userComponentMap.put(userHandle, componentMap);
        }
        componentMap.put(cachingLogic.getComponent(app), app);
    }
    for (Entry<UserHandle, HashMap<ComponentName, T>> entry : userComponentMap.entrySet()) {
        updateIconsPerUser(entry.getKey(), entry.getValue(), cachingLogic, onUpdateCallback);
    }
    // From now on, clear every valid item from the global valid map.
    mFilterMode = MODE_CLEAR_VALID_ITEMS;
}- 这里有两个Map,按照用户维度来分组组件
 
- 按照用户维度来分组组件 
HashMap<UserHandle, HashMap<ComponentName, T>> userComponentMap; - 按照组件不同分组 
HashMap<ComponentName, T> componentMap = userComponentMap.get(userHandle); 
updateIconsPerUser
/**
 * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in
 * the DB and are updated.
 * @return The set of packages for which icons have updated.
 */
@SuppressWarnings("unchecked")
private <T> void updateIconsPerUser(UserHandle user, HashMap<ComponentName, T> componentMap,
        CachingLogic<T> cachingLogic, OnUpdateCallback onUpdateCallback) {
    Set<String> ignorePackages = mPackagesToIgnore.get(user);
    if (ignorePackages == null) {
        ignorePackages = Collections.emptySet();
    }
    long userSerial = mIconCache.getSerialNumberForUser(user);
    Log.d(TAG, "updateIconsPerUser: userSerial = " + userSerial + " ,componentMap =" + componentMap.size());
    Stack<T> appsToUpdate = new Stack<>();
    try (Cursor c = mIconCache.mIconDb.query(
            new String[]{IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT,
                    IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION,
                    IconDB.COLUMN_SYSTEM_STATE},
            IconDB.COLUMN_USER + " = ? ",
            new String[]{Long.toString(userSerial)})) {
        final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT);
        final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED);
        final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION);
        final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID);
        final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE);
        Log.d(TAG, "updateIconsPerUser: 111");
        while (c.moveToNext()) {
            Log.d(TAG, "updateIconsPerUser: 222");
            ...
        }
    } catch (SQLiteException e) {
        Log.d(TAG, "Error reading icon cache", e);
        // Continue updating whatever we have read so far
    }
    // Insert remaining apps.
    if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) {
        Stack<T> appsToAdd = new Stack<>();
        appsToAdd.addAll(componentMap.values());
        Log.d(TAG, "SerializedIconUpdateTask appsToAdd = " + appsToAdd.size() + ", appsToUpdate = "+ appsToUpdate.size());
        new SerializedIconUpdateTask(userSerial, user, appsToAdd, appsToUpdate, cachingLogic,
                onUpdateCallback).scheduleNext();
    }
}
- 为什么要删除操作?setIgnorePackages
 
SerializedIconUpdateTask.run()
private class SerializedIconUpdateTask<T> implements Runnable {
    ....
    @Override
    public void run() {
       ...
       if (!mAppsToAdd.isEmpty()) {
            T app = mAppsToAdd.pop();
            PackageInfo info = mPkgInfoMap.get(mCachingLogic.getComponent(app).getPackageName());
            // We do not check the mPkgInfoMap when generating the mAppsToAdd. Although every
            // app should have package info, this is not guaranteed by the api
            if (info != null) {
                mIconCache.addIconToDBAndMemCache(app, mCachingLogic, info,
                        mUserSerial, false /*replace existing*/);
            }
            if (!mAppsToAdd.isEmpty()) {
                scheduleNext();
            }
        }
    }
    public void scheduleNext() {
        mIconCache.mWorkerHandler.postAtTime(this, ICON_UPDATE_TOKEN,
                SystemClock.uptimeMillis() + 1);
    }
}IconCache.java
核心思想:针对每类图标提供通用的HashMap内存缓存 + 数据库缓存,同时通过CachingLogic多种实现图标差异性。
// 加载 shortcut 图标
private synchronized <T extends ItemInfoWithIcon> void getShortcutIcon(T info, ShortcutInfo si,
        boolean useBadged, @NonNull Predicate<T> fallbackIconCheck) {
    BitmapInfo bitmapInfo;
    if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
        bitmapInfo = cacheLocked(ShortcutKey.fromInfo(si).componentName, si.getUserHandle(),
                () -> si, mShortcutCachingLogic, false, false).bitmap;
    } else {
        // If caching is disabled, load the full icon
        bitmapInfo = mShortcutCachingLogic.loadIcon(mContext, si);
    }
    if (bitmapInfo.isNullOrLowRes()) {
        bitmapInfo = getDefaultIcon(si.getUserHandle());
    }
    if (isDefaultIcon(bitmapInfo, si.getUserHandle()) && fallbackIconCheck.test(info)) {
        return;
    }
    info.bitmap = bitmapInfo;
    if (useBadged) {
        BitmapInfo badgeInfo = getShortcutInfoBadge(si);
        try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
            info.bitmap = li.badgeBitmap(info.bitmap.icon, badgeInfo);
        }
    }
}
/**
 * 加载 Widget 图标
 */
public synchronized String getTitleNoCache(ComponentWithLabel info) {
    CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), () -> info,
            mComponentWithLabelCachingLogic, false /* usePackageIcon */,
            true /* useLowResIcon */);
    return Utilities.trim(entry.title);
}BaseIconCache.java
1.首先看下构造方法
public abstract class BaseIconCache {
    ....
    private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
    private final Map<ComponentKey, CacheEntry> mCache;
    ...
    public BaseIconCache(Context context, String dbFileName, Looper bgLooper,
        int iconDpi, int iconPixelSize, boolean inMemoryCache) {
        ...
        if (inMemoryCache) {
            mCache = new HashMap<>(INITIAL_ICON_CACHE_CAPACITY);
        } else {
            // Use a dummy cache
            mCache = new AbstractMap<ComponentKey, CacheEntry>() {
                @Override
                public Set<Entry<ComponentKey, CacheEntry>> entrySet() {
                    return Collections.emptySet();
                }
                @Override
                public CacheEntry put(ComponentKey key, CacheEntry value) {
                    return value;
                }
            };
        }
        ...
    }
}
// 缓存key, 组成: 组件名 和 用户UserHandle
public class ComponentKey {
    public final ComponentName componentName;
    public final UserHandle user;
    private final int mHashCode;
    public ComponentKey(ComponentName componentName, UserHandle user) {
        if (componentName == null || user == null) {
            throw new NullPointerException();
        }
        this.componentName = componentName;
        this.user = user;
        mHashCode = Arrays.hashCode(new Object[] {componentName, user});
    }
    ...
}
// 缓存Value,组成:图标 + title + contentDesc
public static class CacheEntry {
    @NonNull
    public BitmapInfo bitmap = BitmapInfo.LOW_RES_INFO;
    public CharSequence title = "";
    public CharSequence contentDescription = "";
}
- 缓存数据结构:Map<ComponentKey, CacheEntry>
 - 缓存集合初始大小为50
 - ComponentKey:缓存key, 组成: 组件名(pkg+cls) 和 用户UserHandle
 - CacheEntry:缓存Value,组成:图标 + title + contentDesc
 - 注意这里有一段代码是
虚内存,使用技巧值得学习 
// Use a dummy cache
            mCache = new AbstractMap<ComponentKey, CacheEntry>() {
                @Override
                public Set<Entry<ComponentKey, CacheEntry>> entrySet() {
                    return Collections.emptySet();
                }
                @Override
                public CacheEntry put(ComponentKey key, CacheEntry value) {
                    return value;
                }
            };2.继续看另一个重要方法 cacheLocked()
/**
 * @param  componentName  组件名
 * @param  user 用户
 * @param  infoProvider 组件信息提供者
 * @param  cachingLogic  对应的缓存逻辑处理类
 * @param  usePackageIcon 是否使用pkg的icon
 * @param  useLowResIcon  是否使用默认的空图标
 */
protected <T> CacheEntry cacheLocked(
        @NonNull ComponentName componentName, @NonNull UserHandle user,
        @NonNull Supplier<T> infoProvider, @NonNull CachingLogic<T> cachingLogic,
        boolean usePackageIcon, boolean useLowResIcon) {
    assertWorkerThread();
    // 1.生成缓存key
    ComponentKey cacheKey = new ComponentKey(componentName, user);
    // 2.尝试根据key,从缓存中取
    CacheEntry entry = mCache.get(cacheKey);
    // 3.尚未缓存 或者 缓存了但是缓存的是空的默认图标,此时去缓存
    if (entry == null || (entry.bitmap.isLowRes() && !useLowResIcon)) {
        entry = new CacheEntry();
        //4.如果对应的缓存逻辑控制类 允许添加到内存缓存中,即存入mCache,但此时value未赋值
        if (cachingLogic.addToMemCache()) { 
            mCache.put(cacheKey, entry);
        }
        // Check the DB first.
        T object = null;
        boolean providerFetchedOnce = false;
        // 4.首先查看数据库是否存在
        // 如果数据存在,取出来赋值给entry
        // 如果数据库不存在,加载默认空图标、pkg图标、或者 cachingLogic.loadIcon
        if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
            object = infoProvider.get();
            providerFetchedOnce = true;
            if (object != null) { // 4.1如果信息提供者不为空,直接去对应的缓存控制逻辑取图标
                entry.bitmap = cachingLogic.loadIcon(mContext, object);
            } else { // 4.2如果提供者是空的,返回默认的或者使用pkg的图标
                if (usePackageIcon) {
                    CacheEntry packageEntry = getEntryForPackageLocked(
                            componentName.getPackageName(), user, false);
                    if (packageEntry != null) {
                        if (DEBUG) Log.d(TAG, "using package default icon for " +
                                componentName.toShortString());
                        entry.bitmap = packageEntry.bitmap;
                        entry.title = packageEntry.title;
                        entry.contentDescription = packageEntry.contentDescription;
                    }
                }
                // 如果pkg依然为空,使用默认的空白图标
                if (entry.bitmap == null) {
                    if (DEBUG) Log.d(TAG, "using default icon for " +
                            componentName.toShortString());
                    entry.bitmap = getDefaultIcon(user);
                }
            }
        }
        // 5.检查并对entry的title和desc继续赋值
        if (TextUtils.isEmpty(entry.title)) {
            if (object == null && !providerFetchedOnce) {
                object = infoProvider.get();
                providerFetchedOnce = true;
            }
            if (object != null) {
                entry.title = cachingLogic.getLabel(object);
                entry.contentDescription = mPackageManager.getUserBadgedLabel(
                        cachingLogic.getDescription(object, entry.title), user);
            }
        }
    }
    return entry; // 返回缓存的Value,及CacheEntry
}补充说明两点
getEntryFromDB从数据库中查询目标EntrygetEntryForPackageLocked与上面这个方法类似,唯一多的逻辑是当从packagemanger查询到应用图标会存入到数据库
3.方法addIconToDBAndMemCache
/**
* 在数据库和内存缓存中添加一个条目。 
* @param replaceExisting 如果为真,它会重新创建位图,即使它已经存在于内存中。
* 这在以前的位图是使用旧数据创建时很有用。
*/
public synchronized <T> void addIconToDBAndMemCache(T object, CachingLogic<T> cachingLogic,
        PackageInfo info, long userSerial, boolean replaceExisting) {
    UserHandle user = cachingLogic.getUser(object);
    ComponentName componentName = cachingLogic.getComponent(object);
    final ComponentKey key = new ComponentKey(componentName, user);
    CacheEntry entry = null;
    if (!replaceExisting) {
        entry = mCache.get(key);
        // We can't reuse the entry if the high-res icon is not present.
        if (entry == null || entry.bitmap.isNullOrLowRes()) {
            entry = null;
        }
    }
    // 新加载图标
    if (entry == null) {
        entry = new CacheEntry();
        entry.bitmap = cachingLogic.loadIcon(mContext, object);
    }
    // 无法从 cachingLogic 加载图标,这意味着已加载替代图标(例如后备图标、默认图标)。
    // 所以我们放在这里,因为缓存空条目没有意义。
    if (entry.bitmap.isNullOrLowRes()) return;
    entry.title = cachingLogic.getLabel(object);
    entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user);
    // 是否需要添加到内存中
    if (cachingLogic.addToMemCache()) mCache.put(key, entry);
    ContentValues values = newContentValues(entry.bitmap, entry.title.toString(),
            componentName.getPackageName(), cachingLogic.getKeywords(object, mLocaleList));
    // 添加到数据库
    addIconToDB(values, componentName, info, userSerial,
            cachingLogic.getLastUpdatedTime(object, info));
}IconDB
- 类路径:com.android.launcher3.icons.cache.BaseIconCache.IconDB
 - db数据库名:app_icons.db
 - table表名:icons
 

某个手机数据库表示例
CachingLogic.java 系列

- LauncherActivityCachingLogic 用于allApp的图标缓存
 - ShortcutCachingLogic 用于shortcut的图标缓存
 - ComponentWithIconCachingLogic 用于widget的图标缓存
 - 其中 
loadIcon是可以定制图标样式的 
public class ShortcutCachingLogic implements CachingLogic<ShortcutInfo> {
    private static final String TAG = "ShortcutCachingLogic";
    // 根据shortcutInfo获取组件
    @Override
    public ComponentName getComponent(ShortcutInfo info) {
        return ShortcutKey.fromInfo(info).componentName;
    }
   ...
    @NonNull
    @Override
    public BitmapInfo loadIcon(Context context, ShortcutInfo info) {
        try (LauncherIcons li = LauncherIcons.obtain(context)) {
            Drawable unbadgedDrawable = ShortcutCachingLogic.getIcon(
                    context, info, LauncherAppState.getIDP(context).fillResIconDpi);
            if (unbadgedDrawable == null) return BitmapInfo.LOW_RES_INFO;
            return new BitmapInfo(li.createScaledBitmapWithoutShadow(
                    unbadgedDrawable, 0), Themes.getColorAccent(context));
        }
    }
    @Override
    public boolean addToMemCache() {
        return false;// 表示不缓存到内存中
    }
    /**
     * Similar to {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)} with additional
     * Launcher specific checks
     */
    public static Drawable getIcon(Context context, ShortcutInfo shortcutInfo, int density) {
        if (GO_DISABLE_WIDGETS) { // 开关控制是否允许有shortcut
            return null;
        }
        try {// 从LauncherApps中查询图标
            return context.getSystemService(LauncherApps.class)
                    .getShortcutIconDrawable(shortcutInfo, density);
        } catch (SecurityException | IllegalStateException e) {
            Log.e(TAG, "Failed to get shortcut icon", e);
            return null;
        }
    }
}WidgetsModel.java
// True is the widget support is disabled.
public static final boolean GO_DISABLE_WIDGETS = true;- 当打开GO_DISABLE_WIDGETS = false ,会开启widget,同时在optionsview上会显示菜单, 如下图
 
OptionsPopupView.java
public static WidgetsFullSheet openWidgets(Launcher launcher) {
    if (launcher.getPackageManager().isSafeMode()) {
        Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
        return null;
    } else {
        return WidgetsFullSheet.show(launcher, true /* animated */);
    }
}
- 异常情况默认图标兜底 
makeDefaultIconcom.android.launcher3.icons.BaseIconFactory#getFullResDefaultActivityIcon 









