Launcher3 中 IconCache 缓存逻辑

2023-09-13  本文已影响0人  smart_dev

概述

我们先看下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();
        ...
       }
   }

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

LoaderTask.java

是有数据管理类LauncherModel来调用的,其核心是Run方法。

主要分为四大步骤,并开启事务机制来管理

  1. 加载与绑定桌面内容

    1. loadWorkspace

    2. sanitizeData

    3. bindWorkspace

    4. sendFirstScreenActiveInstallsBroadcast

  2. 加载和绑定所有的应用图标和信息

    1. loadAllApps

    2. bindAllApps

    3. update icon cache 对应图标缓存逻辑类 LauncherActivityCachingLogic

    4. save shortcuts in icon cache

    这一步实际是在第一步的,对应的图标缓存逻辑类 ShortcutCachingLogic

  3. 加载和绑定所有DeepShortcuts

    1. loadDeepShortcuts

    2. bindDeepShortcuts

    3. save deep shortcuts in icon cache 对应的图标缓存逻辑类 ShortcutCachingLogic

  4. 加载和绑定所有的Widgets

    1. load widgets

    2. bindWidgets

    3. save widgets in icon cache 对应的图标缓存逻辑类 ComponentWithIconCachingLogic

IconCacheUpdateHandler.java

IconCacheUpdateHandler扫描到所有应用后,会开启一个线程 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;
}

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

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

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

补充说明两点

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

某个手机数据库表示例

CachingLogic.java 系列

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;
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 */);
    }
}
上一篇 下一篇

猜你喜欢

热点阅读