概述
我們先看下IconCache的初始化過程锣披,接著看下IconCache核心數據結構丰刊、算法,最后介紹與之關聯(lián)的幾個類岸梨。
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
即數據管理器,用于維護啟動器的內存狀態(tài)鸠姨。預計靜態(tài)中應該只有一個LauncherModel
對象铜秆。還提供用于更新 Launcher 的數據庫狀態(tài)的 API。mIconCache
應用程序icon和title的緩存享怀,圖標可以由任何線程創(chuàng)建羽峰。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()
搀愧,這是個工具類,處理更新圖標緩存, 處理業(yè)務與
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.繼續(xù)看另一個重要方法 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繼續(xù)賦值
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
/**
* 在數據庫和內存緩存中添加一個條目碟案。
* @param replaceExisting 如果為真,它會重新創(chuàng)建位圖颇蜡,即使它已經存在于內存中价说。
* 這在以前的位圖是使用舊數據創(chuàng)建時很有用。
*/
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 */);
}
}
- 異常情況默認圖標兜底
makeDefaultIcon
com.android.launcher3.icons.BaseIconFactory#getFullResDefaultActivityIcon