概述
我们先看下IconCache的初始化过程,接着看下IconCache核心数据结构、算法,最后介绍与之关联的几个类。
Launcher.java
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
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
3.
加载和绑定所有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
java
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
java
/**
* 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()
java
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多种实现图标差异性。
java
// 加载 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.首先看下构造方法
java
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
- 注意这里有一段代码是
虚内存
,使用技巧值得学习
java
// 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()
java
/**
* @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
从数据库中查询目标Entry
-
getEntryForPackageLocked
与上面这个方法类似,唯一多的逻辑是当从packagemanger查询到应用图标会存入到数据库
3.方法addIconToDBAndMemCache
java
/**
* 在数据库和内存缓存中添加一个条目。
* @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
是可以定制图标样式的
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
java
// True is the widget support is disabled.
public static final boolean GO_DISABLE_WIDGETS = true;
- 当打开_GO_DISABLE_WIDGETS = false ,会开启widget,同时在optionsview上会显示菜单, 如下图_
java
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 */);
}
}
- 异常情况默认图标兜底
makeDefaultIcon
com.android.launcher3.icons.BaseIconFactory#getFullResDefaultActivityIcon
👀关注公众号:Android老皮!!!欢迎大家来找我探讨交流👀