Launcher3之IconCache实现分析

IconCache类简介

分析IconCache源码之前,先了解下这个类在launcher中扮演了什么样的角色?从字面可以看出,它是一个缓存类,缓存了跟Icon相关的信息,实际它主要缓存了应用的桌面图标bitmap和label,除了memory缓存,还持久化到了数据库。

下面我们就具体看下源码实现。

Icon资源获取

每个应用Icon的图标资源是怎么获取到的?声明下,这里我们聊的是原生aosp launcher的实现,不是定制ROM中的主题功能,它们实现是不一样的。

原生launcher的图标资源是从对应应用中获取来的,就是原apk中的资源,看一个有代表性的实现:

    public Drawable getFullResIcon(String packageName, int iconId) {
        Resources resources;
        try {
			// 根据包名获取该应用的Resources对象
            resources = mPackageManager.getResourcesForApplication(packageName);
        } catch (PackageManager.NameNotFoundException e) {
            resources = null;
        }
        if (resources != null) {
            if (iconId != 0) {
				// 根据icon资源id获取图标Drawable
                return getFullResIcon(resources, iconId);
            }
        }
        return getFullResDefaultActivityIcon();
    }
	
    private Drawable getFullResIcon(Resources resources, int iconId) {
        Drawable d;
        try {
			// 根据icon资源id获取图标Drawable,这里还带了像素密度参数
            d = resources.getDrawableForDensity(iconId, mIconDpi);
        } catch (Resources.NotFoundException e) {
            d = null;
        }

        return (d != null) ? d : getFullResDefaultActivityIcon();
    }

从上面代码可以看出,如果存在Resources和iconId,通过上面的方法就可以获取到图标。如果没有iconId或者通过以上方法没有获取到图标呢?我们会退而求其次,用应用的图标而非入口组件本身的图标代替。

看下面这个方法是怎么只通过包名,加载图标的:

    private CacheEntry getEntryForPackageLocked(String packageName, UserHandle user,
            boolean useLowResIcon) {
        Preconditions.assertWorkerThread();
        ComponentKey cacheKey = getPackageKey(packageName, user);
        CacheEntry entry = mCache.get(cacheKey);

        if (entry == null || (entry.isLowResIcon && !useLowResIcon)) {
            entry = new CacheEntry();
            boolean entryUpdated = true;

            // Check the DB first.
            if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
                try {
                    int flags = Process.myUserHandle().equals(user) ? 0 :
                        PackageManager.GET_UNINSTALLED_PACKAGES;
					// 根据包名获取到PackageInfo对象
                    PackageInfo info = mPackageManager.getPackageInfo(packageName, flags);
					// 得到ApplicationInfo
                    ApplicationInfo appInfo = info.applicationInfo;
					......

                    LauncherIcons li = LauncherIcons.obtain(mContext);
                    BitmapInfo iconInfo = li.createBadgedIconBitmap(
							//这里通过ApplicationInfo.loadIcon加载应用图标
                            appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion,
                            mInstantAppResolver.isInstantApp(appInfo));
                    li.recycle();

					......

                } catch (NameNotFoundException e) {
                    if (DEBUG) Log.d(TAG, "Application not installed " + packageName);
                    entryUpdated = false;
                }
            }

			......
        }
        return entry;
    }

这里是通过PackageManager拿到对应包名的ApplicationInfo,进而加载Application定义的图标。

memory缓存

内存缓存到底是个什么样的存在,来看下它的庐山真面目。

    private final HashMap<ComponentKey, CacheEntry> mCache =
            new HashMap<>(INITIAL_ICON_CACHE_CAPACITY);

mCache是IconCache中的一个HashMap类型的私有变量,通过键值对的形式存储每个应用图标的信息。

了解下CacheEntry这个内部类:

    public static class CacheEntry extends BitmapInfo {
        public CharSequence title = "";
        public CharSequence contentDescription = "";
        public boolean isLowResIcon;
    }
public class BitmapInfo {

    public Bitmap icon;
    public int color;
}

它继承了BitmapInfo,BitmapInfo定义了icon变量,用于存储Bitmap引用。

持久化

出于性能和用户体验的考虑,除了做了内存缓存,Icon也被持久化到了数据库,主要实现类是IconDB。

    @Thunk final IconDB mIconDb;

mIconDb也作为实例变量被IconCache持有。

看下内部类IconDB的类继承关系:

    private static final class IconDB extends SQLiteCacheHelper {
	
public abstract class SQLiteCacheHelper {

    private final MySQLiteOpenHelper mOpenHelper;
	
    private class MySQLiteOpenHelper extends NoLocaleSQLiteHelper {
		......
	}
}
public abstract class NoLocaleSQLiteHelper extends SQLiteOpenHelper {

IconDB应该算一个DAO帮组类,实际完成数据库创建是MySQLiteOpenHelper类完成的。

看一个IconCache中定义的插入数据的方法:

    private void addIconToDB(ContentValues values, ComponentName key,
            PackageInfo info, long userSerial) {
        values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
        values.put(IconDB.COLUMN_USER, userSerial);
        values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime);
        values.put(IconDB.COLUMN_VERSION, info.versionCode);
        mIconDb.insertOrReplace(values);
    }

Icon标准化处理

为了统一样式,launcher中定义了一个用于处理图标样式的帮助类LauncherIcons,在缓存图标之前,会通过该类进行标准化处理。
看下LauncherIcons中的一个public方法:

    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk,
            boolean isInstantApp) {
        float[] scale = new float[1];
		// 关键方法
        icon = normalizeAndWrapToAdaptiveIcon(icon, iconAppTargetSdk, null, scale);
		// 根据scale参数创建缩放处理的Bitmap
        Bitmap bitmap = createIconBitmap(icon, scale[0]);
        if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
            mCanvas.setBitmap(bitmap);
            getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
            mCanvas.setBitmap(null);
        }

        final Bitmap result;
		......
		......
		result = bitmap;

        return BitmapInfo.fromBitmap(result);
    }

normalizeAndWrapToAdaptiveIcon是一个关键方法,接着看下方法实现:

    private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, int iconAppTargetSdk,
            RectF outIconBounds, float[] outScale) {
        float scale = 1f;
		// O以上系统,apk的TargetSdk升级到O以上
        if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
            boolean[] outShape = new boolean[1];
            if (mWrapperIcon == null) {
                mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper)
                        .mutate();
            }
            AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
            dr.setBounds(0, 0, 1, 1);
            scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
			// icon不是AdaptiveIconDrawable的,给原图标统一加上底板
            if (Utilities.ATLEAST_OREO && !outShape[0] && !(icon instanceof AdaptiveIconDrawable)) {
                FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
                fsd.setDrawable(icon);
                fsd.setScale(scale);
                icon = dr;
                scale = getNormalizer().getScale(icon, outIconBounds, null, null);

                ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
            }
        } else {
			// O以下系统,或者apk的TargetSdk未升级到O以上,计算原图片缩放参数,直接用于展示,不进行AdaptiveIconDrawable转换
            scale = getNormalizer().getScale(icon, outIconBounds, null, null);
        }

        outScale[0] = scale;
        return icon;
    }

上面有一个getNormalizer().getScale方法,用于计算图标缩放的值,保证图标统一的尺寸大小。

    public IconNormalizer getNormalizer() {
        if (mNormalizer == null) {
            mNormalizer = new IconNormalizer(mContext);
        }
        return mNormalizer;
    }

感兴趣的同学可以进一步研究下IconNormalizer类代码。

扩展

launcher如果需要支持主题图标,这里缓存的图标就不是来源于应用apk了,需要从主题包中加载,当然这里的缓存实现还是可以借鉴和重用的。

小结

以上就是个人目前所了解的IconCache的一些知识,有写的不当之处,欢饮指正。

猜你喜欢

转载自blog.csdn.net/qinhai1989/article/details/86706877