墨香帶你學(xué)Launcher之(八)- 加載Icon、設(shè)置壁紙

上一章墨香帶你學(xué)Launcher之(七)- 小部件的加載、添加以及大小調(diào)節(jié)介紹了小部件的加載以及添加過程熟尉,基于我的計(jì)劃對(duì)于Launcher的講解基本要完成了,因此本篇是我對(duì)Launcher講解的最后一部分洲脂,計(jì)劃了很久斤儿,因?yàn)闀r(shí)間的問題一直沒有寫剧包,今天趁著有空寫完。寫了八篇往果,不多疆液,Launcher里面還有很多東西,有興趣的可以自己繼續(xù)研究陕贮,看完這些主要的其他都是問題了堕油,有什么需要了解的可以留言。最新版的Launcher代碼我已經(jīng)放到github上肮之,想看的自己可以去下載掉缺。

加載Icon

對(duì)于Icon的操作其實(shí)主要是加載、更新以及刪除戈擒,加載主要是啟動(dòng)Launcher眶明、安裝應(yīng)用,更新是在更新應(yīng)用時(shí)更新Icon峦甩、刪除是卸載應(yīng)用時(shí)會(huì)刪除Icon赘来,因此我們可以從這幾方面分析Icon的處理。

Launcher啟動(dòng)時(shí)Icon加載

Launcher的數(shù)據(jù)加載流程我在第二篇墨香帶你學(xué)Launcher之(二)- 數(shù)據(jù)加載流程講過,不熟悉的可以去看看凯傲。首先是將xml文件中配置的Apk信息解析保存到數(shù)據(jù)庫犬辰,然后讀取數(shù)據(jù)庫,查看手機(jī)中是否存在該apk冰单,如果有加載相關(guān)信息幌缝,加載流程在“l(fā)oadWorkspace”方法中,在加載過程中會(huì)去生成對(duì)應(yīng)的Icon诫欠,我們看一下代碼:

if (itemReplaced) {
    ...
        info = getAppShortcutInfo(manager, intent, user, context, null,
                cursorIconInfo.iconIndex, titleIndex,
                false, useLowResIcon);
    ...
} else if (restored) {
    ...
        info = getRestoredItemInfo(c, titleIndex, intent,
                promiseType, itemType, cursorIconInfo, context);
    ...                                   
} else if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
    info = getAppShortcutInfo(manager, intent, user, context, c,
            cursorIconInfo.iconIndex, titleIndex,
            allowMissingTarget, useLowResIcon);
} else {
    info = getShortcutInfo(c, context, titleIndex, cursorIconInfo);
    ...
}

在段代碼中主要有三個(gè)方法涉及到加載Icon涵卵,getAppShortcutInfo、getRestoredItemInfo以及getShortcutInfo方法荒叼,我們看看這個(gè)三個(gè)方法的代碼:

第一個(gè):

    public ShortcutInfo getAppShortcutInfo(PackageManager manager, Intent intent,
                                           UserHandleCompat user, Context context, Cursor c, int iconIndex, int titleIndex,
                                           boolean allowMissingTarget, boolean useLowResIcon) {
                                           
        ...

        final ShortcutInfo info = new ShortcutInfo();
        mIconCache.getTitleAndIcon(info, componentName, lai, user, false, useLowResIcon);
        if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && c != null) {
            Bitmap icon = Utilities.createIconBitmap(c, iconIndex, context);
            info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon);
        }

        ...
    }

在這段代碼中主要是調(diào)用IconCache中的getTitleAndIcon方法轿偎,這個(gè)方法詳細(xì)過程我們一會(huì)再看,然后判斷是否是默認(rèn)圖標(biāo)被廓,如果是生成Icon圖標(biāo)坏晦,如果能生成則設(shè)置圖標(biāo),如果不能生成則采用默認(rèn)圖標(biāo)嫁乘。Utilities.createIconBitmap代碼不在詳細(xì)講昆婿,看看就會(huì)了。

我們接著看第二個(gè)方法:

public ShortcutInfo getRestoredItemInfo(Cursor c, int titleIndex, Intent intent,
                                            int promiseType, int itemType, CursorIconInfo iconInfo, Context context) {
        ...

        Bitmap icon = iconInfo.loadIcon(c, info, context);
        // the fallback icon
        if (icon == null) {
            mIconCache.getTitleAndIcon(info, intent, info.user, false /* useLowResIcon */);
        } else {
            info.setIcon(icon);
        }

        ...
    }

這個(gè)方法中主要是調(diào)用CursorIconInfo中的loadIcon方法蜓斧,代碼我們一會(huì)再看仓蛆,如果能獲取到Icon則設(shè)置這個(gè)Icon,如果不能則通過IconCache.getTitleAndIcon方法獲取挎春,和上面一樣了看疙。

第三個(gè)方法:

    ShortcutInfo getShortcutInfo(Cursor c, Context context,
                                 int titleIndex, CursorIconInfo iconInfo) {
        ...

        Bitmap icon = iconInfo.loadIcon(c, info, context);
        // the fallback icon
        if (icon == null) {
            icon = mIconCache.getDefaultIcon(info.user);
            info.usingFallbackIcon = true;
        }
        info.setIcon(icon);
        return info;
    }

這個(gè)方法中還是調(diào)用CursorIconInfo中的loadIcon方法豆拨,如果能獲取,則設(shè)置圖標(biāo)能庆,如果不能獲取默認(rèn)圖標(biāo)設(shè)置辽装。從上面三個(gè)方法代碼看其實(shí)最終調(diào)用了兩個(gè)方法,一個(gè)是IconCache.getTitleAndIcon方法相味,一個(gè)是CursorIconInfo.loadIcon方法。

我們先看一下CursorIconInfo.loadIcon代碼:

public Bitmap loadIcon(Cursor c, ShortcutInfo info, Context context) {
        Bitmap icon = null;
        int iconType = c.getInt(iconTypeIndex);
        switch (iconType) {
        case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
            String packageName = c.getString(iconPackageIndex);
            String resourceName = c.getString(iconResourceIndex);
            if (!TextUtils.isEmpty(packageName) || !TextUtils.isEmpty(resourceName)) {
                info.iconResource = new ShortcutIconResource();
                info.iconResource.packageName = packageName;
                info.iconResource.resourceName = resourceName;
                icon = Utilities.createIconBitmap(packageName, resourceName, context);
            }
            if (icon == null) {
                // Failed to load from resource, try loading from DB.
                icon = Utilities.createIconBitmap(c, iconIndex, context);
            }
            break;
        case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
            icon = Utilities.createIconBitmap(c, iconIndex, context);
            info.customIcon = icon != null;
            break;
        }
        return icon;
    }

在這個(gè)方法中首先是從資源獲取殉挽,如果獲取不到丰涉,則從數(shù)據(jù)庫獲取,及Utilities.createIconBitmap(packageName, resourceName, context)和Utilities.createIconBitmap(c, iconIndex, context)斯碌,我們看看這兩個(gè)方法:

第一個(gè)方法:

 public static Bitmap createIconBitmap(String packageName, String resourceName,
            Context context) {
        PackageManager packageManager = context.getPackageManager();
        // the resource
        try {
            Resources resources = packageManager.getResourcesForApplication(packageName);
            if (resources != null) {
                final int id = resources.getIdentifier(resourceName, null, null);
                return createIconBitmap(
                        resources.getDrawableForDensity(id, LauncherAppState.getInstance()
                                .getInvariantDeviceProfile().fillResIconDpi), context);
            }
        } catch (Exception e) {
            // Icon not found.
        }
        return null;
    }

這個(gè)方法是根據(jù)包名獲取id一死,然后根據(jù)id獲取drawable,由drawable生產(chǎn)Bitmap傻唾。

第二個(gè)方法:

public static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) {
        byte[] data = c.getBlob(iconIndex);
        try {
            return createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length), context);
        } catch (Exception e) {
            return null;
        }
    }

從數(shù)據(jù)庫讀取Icon的byte數(shù)據(jù)投慈,然后生成圖片。這樣看就很清楚這個(gè)方法加載Icon的過程了冠骄。那么數(shù)據(jù)庫中的Icon怎么來的我們回到前面再看IconCache.getTitleAndIcon方法:

    public synchronized void getTitleAndIcon(
            ShortcutInfo shortcutInfo, ComponentName component, LauncherActivityInfoCompat info,
            UserHandleCompat user, boolean usePkgIcon, boolean useLowResIcon) {
        CacheEntry entry = cacheLocked(component, info, user, usePkgIcon, useLowResIcon);
        shortcutInfo.setIcon(getNonNullIcon(entry, user));
        shortcutInfo.title = Utilities.trim(entry.title);
        shortcutInfo.usingFallbackIcon = isDefaultIcon(entry.icon, user);
        shortcutInfo.usingLowResIcon = entry.isLowResIcon;
    }

我們看到了setIcon方法伪煤,那么是getNonNullIcon這個(gè)方法創(chuàng)建了Icon,這個(gè)方法有個(gè)我們不熟悉的對(duì)象entry凛辣,向上看這個(gè)entry是子啊上面通過cacheLocked方法創(chuàng)建的抱既,我們跟蹤一下這個(gè)方法:

private CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info,
            UserHandleCompat user, boolean usePackageIcon, boolean useLowResIcon) {
        ComponentKey cacheKey = new ComponentKey(componentName, user);
        CacheEntry entry = mCache.get(cacheKey);
        if (entry == null || (entry.isLowResIcon && !useLowResIcon)) {
            entry = new CacheEntry();
            mCache.put(cacheKey, entry);

            // Check the DB first.
            if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
                if (info != null) {
                    entry.icon = Utilities.createIconBitmap(info.getBadgedIcon(mIconDpi), mContext);
                } else {
                    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.icon = packageEntry.icon;
                            entry.title = packageEntry.title;
                            entry.contentDescription = packageEntry.contentDescription;
                        }
                    }
                    if (entry.icon == null) {
                        entry.icon = getDefaultIcon(user);
                    }
                }
            }
            ...
            
        }
        return entry;
    }

首先是從mCache中獲取,如果存在CacheEntry對(duì)象扁誓,則不需要再創(chuàng)建防泵,如果沒有則要?jiǎng)?chuàng)建改對(duì)象,然后加載到mCache中蝗敢,然后通過調(diào)用getEntryFromDB方法從數(shù)據(jù)庫查詢是否有改對(duì)象信息捷泞,如果沒有則要?jiǎng)?chuàng)建對(duì)應(yīng)Icon,我們先看看getEntryFromDB這個(gè)方法:

private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
        ...
        try {
            if (c.moveToNext()) {
                entry.icon = loadIconNoResize(c, 0, lowRes ? mLowResOptions : null);
                entry.isLowResIcon = lowRes;
                ...
            }
        } finally {
            c.close();
        }
        return false;
    }

該方法通過查詢數(shù)據(jù)庫來生成Icon寿谴,調(diào)用方法loadIconNoResize锁右,看代碼:

private static Bitmap loadIconNoResize(Cursor c, int iconIndex, BitmapFactory.Options options) {
        byte[] data = c.getBlob(iconIndex);
        try {
            return BitmapFactory.decodeByteArray(data, 0, data.length, options);
        } catch (Exception e) {
            return null;
        }
    }

和上面的一樣,就不用講了拭卿。

回到cacheLocked方法中骡湖,如果數(shù)據(jù)庫中沒有,要繼續(xù)創(chuàng)建Icon峻厚,首先判斷LauncherActivityInfoCompat是否為空响蕴,調(diào)用Utilities.createIconBitmap方法獲取Icon,代碼就不貼了惠桃,也不難浦夷,如果為空的話會(huì)判斷usePackageIcon(根據(jù)包名獲取Icon)辖试,如果用的話則會(huì)調(diào)用getEntryForPackageLocked方法獲取CacheEntry,看代碼:

private CacheEntry getEntryForPackageLocked(String packageName, UserHandleCompat user,
            boolean useLowResIcon) {
        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 {
                    ...
                    Drawable drawable = mUserManager.getBadgedDrawableForUser(
                            appInfo.loadIcon(mPackageManager), user);
                    entry.icon = Utilities.createIconBitmap(drawable, mContext);
                    entry.title = appInfo.loadLabel(mPackageManager);
                    entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
                    entry.isLowResIcon = false;

                    // Add the icon in the DB here, since these do not get written during
                    // package updates.
                    ContentValues values =
                            newContentValues(entry.icon, entry.title.toString(), mPackageBgColor);
                    addIconToDB(values, cacheKey.componentName, info,
                            mUserManager.getSerialNumberForUser(user));

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

            // Only add a filled-out entry to the cache
            if (entryUpdated) {
                mCache.put(cacheKey, entry);
            }
        }
        return entry;
    }

代碼和cacheLocked方法很像劈狐,也是先判斷數(shù)據(jù)庫中是否存在罐孝,不存在就要加載,這里有個(gè)方法addIconToDB肥缔,看上面ContentValues的注釋莲兢,就是把Icon存到數(shù)據(jù)庫中,原來是在這里存入數(shù)據(jù)庫的续膳,其實(shí)Icon的信息首先放入ContentValues中改艇,然后存入數(shù)據(jù)庫,我們看看代碼:

    private ContentValues newContentValues(Bitmap icon, String label, int lowResBackgroundColor) {
        ContentValues values = new ContentValues();
        values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(icon));

        values.put(IconDB.COLUMN_LABEL, label);
        values.put(IconDB.COLUMN_SYSTEM_STATE, mSystemState);

        if (lowResBackgroundColor == Color.TRANSPARENT) {
          values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap(
          Bitmap.createScaledBitmap(icon,
                  icon.getWidth() / LOW_RES_SCALE_FACTOR,
                  icon.getHeight() / LOW_RES_SCALE_FACTOR, true)));
        } else {
            synchronized (this) {
                if (mLowResBitmap == null) {
                    mLowResBitmap = Bitmap.createBitmap(icon.getWidth() / LOW_RES_SCALE_FACTOR,
                            icon.getHeight() / LOW_RES_SCALE_FACTOR, Bitmap.Config.RGB_565);
                    mLowResCanvas = new Canvas(mLowResBitmap);
                    mLowResPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
                }
                mLowResCanvas.drawColor(lowResBackgroundColor);
                mLowResCanvas.drawBitmap(icon, new Rect(0, 0, icon.getWidth(), icon.getHeight()),
                        new Rect(0, 0, mLowResBitmap.getWidth(), mLowResBitmap.getHeight()),
                        mLowResPaint);
                values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap(mLowResBitmap));
            }
        }
        return values;
    }

通過Utilities.flattenBitmap(icon)方法將Icon轉(zhuǎn)換成byte數(shù)組然后存入數(shù)據(jù)庫坟岔。再回到cacheLocked方法中谒兄,如果還是沒有獲取到Icon,那么只能獲取系統(tǒng)默認(rèn)Icon了社付,也就是我們自己寫app的默認(rèn)Icon圖標(biāo)(機(jī)器人圖標(biāo))承疲。這個(gè)是我們加載配置文件中的Apk信息時(shí)加載Icon的過程荠卷,我們?cè)倏纯醇虞d所有app時(shí)是不是也是這樣肴盏,我們先看加載方法loadAllApps代碼:

        private void loadAllApps() {
            ...

                // Create the ApplicationInfos
                for (int i = 0; i < apps.size(); i++) {
                    LauncherActivityInfoCompat app = apps.get(i);
                    // This builds the icon bitmaps.
                    mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
                }

            ...        
    }

我們看到主要是AppInfo對(duì)象的生成,我們看看代碼:

    public AppInfo(Context context, LauncherActivityInfoCompat info, UserHandleCompat user,
                   IconCache iconCache) {
        this.componentName = info.getComponentName();
        this.container = ItemInfo.NO_ID;

        flags = initFlags(info);
        firstInstallTime = info.getFirstInstallTime();
        iconCache.getTitleAndIcon(this, info, true /* useLowResIcon */);
        intent = makeLaunchIntent(context, info, user);
        this.user = user;
    }

從上面代碼我們看到其實(shí)還是調(diào)用getTitleAndIcon方法敬拓,又回到我們上面講的過程了扛或。

APK安裝绵咱、更新、卸載時(shí)Icon處理

APK的安裝熙兔、卸載悲伶、更新、可用以及不可用在墨香帶你學(xué)Launcher之(四)-應(yīng)用安裝住涉、更新麸锉、卸載時(shí)的數(shù)據(jù)加載中講到過,不清楚的可以去看看舆声,這幾個(gè)實(shí)現(xiàn)方法是在LauncherModel中來處理的:

@Override
    public void onPackageChanged(String packageName, UserHandleCompat user) {
        int op = PackageUpdatedTask.OP_UPDATE;
        enqueuePackageUpdated(new PackageUpdatedTask(op, new String[]{packageName},
                user));
    }

    @Override
    public void onPackageRemoved(String packageName, UserHandleCompat user) {
        int op = PackageUpdatedTask.OP_REMOVE;
        enqueuePackageUpdated(new PackageUpdatedTask(op, new String[]{packageName},
                user));
    }

    @Override
    public void onPackageAdded(String packageName, UserHandleCompat user) {
        int op = PackageUpdatedTask.OP_ADD;
        enqueuePackageUpdated(new PackageUpdatedTask(op, new String[]{packageName},
                user));
    }

    @Override
    public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
                                    boolean replacing) {
        if (!replacing) {
            enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packageNames,
                    user));
            if (mAppsCanBeOnRemoveableStorage) {
                startLoaderFromBackground();
            }
        } else {
            // If we are replacing then just update the packages in the list
            enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
                    packageNames, user));
        }
    }

    @Override
    public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
                                      boolean replacing) {
        if (!replacing) {
            enqueuePackageUpdated(new PackageUpdatedTask(
                    PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
                    user));
        }
    }

我們看代碼發(fā)現(xiàn)其實(shí)都是PackageUpdatedTask這個(gè)執(zhí)行方法花沉,代碼比較多,我們只貼重點(diǎn)部分媳握,詳細(xì)的可以去看源碼:

private class PackageUpdatedTask implements Runnable {
        
        ...

        public void run() {
            ...
            switch (mOp) {
                case OP_ADD: {
                    for (int i = 0; i < N; i++) {
                        ...
                        mIconCache.updateIconsForPkg(packages[i], mUser);
                        ...
                    }
                    ...
                    break;
                }
                case OP_UPDATE:
                    for (int i = 0; i < N; i++) {
                        ...
                        mIconCache.updateIconsForPkg(packages[i], mUser);
                        ...
                    }
                    break;
                case OP_REMOVE: {
                    ...
                    for (int i = 0; i < N; i++) {
                        ...
                        mIconCache.removeIconsForPkg(packages[i], mUser);
                    }
                }
                case OP_UNAVAILABLE:
                    for (int i = 0; i < N; i++) {
                        ...
                    }
                    break;
            }
            ...
            // Update shortcut infos
            if (mOp == OP_ADD || mOp == OP_UPDATE) {
                ...
                synchronized (sBgLock) {
                    for (ItemInfo info : sBgItemsIdMap) {
                        if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
                            ...
                            // Update shortcuts which use iconResource.
                            if ((si.iconResource != null)
                                    && packageSet.contains(si.iconResource.packageName)) {
                                Bitmap icon = Utilities.createIconBitmap(
                                        si.iconResource.packageName,
                                        si.iconResource.resourceName, context);
                                if (icon != null) {
                                    si.setIcon(icon);
                                    ...
                                }
                            }

                            ComponentName cn = si.getTargetComponent();
                            if (cn != null && packageSet.contains(cn.getPackageName())) {
                                ...
                                if (si.isPromise()) {
                                    ...
                                    si.updateIcon(mIconCache);
                                }

                                if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
                                        && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
                                    si.updateIcon(mIconCache);
                                    ...
                                }
                                ...
                            }
                            ...
                        } 
                    }
                }
            }
        }
    }

在上面代碼中我們看到OP_ADD(安裝)碱屁、OP_UPDATE(更新)時(shí)都是調(diào)用的mIconCache.removeIconsForPkg,而和OP_REMOVE(卸載)時(shí)調(diào)用mIconCache.removeIconsForPkg方法蛾找,而在下面又調(diào)用了si.setIcon(icon)娩脾、si.updateIcon來更新Icon,我們分別來看看這四個(gè)方法打毛,首先看第一個(gè)方法(removeIconsForPkg):

public synchronized void updateIconsForPkg(String packageName, UserHandleCompat user) {
        removeIconsForPkg(packageName, user);
        try {
            PackageInfo info = mPackageManager.getPackageInfo(packageName,
                    PackageManager.GET_UNINSTALLED_PACKAGES);
            long userSerial = mUserManager.getSerialNumberForUser(user);
            for (LauncherActivityInfoCompat app : mLauncherApps.getActivityList(packageName, user)) {
                addIconToDBAndMemCache(app, info, userSerial);
            }
        } catch (NameNotFoundException e) {
            Log.d(TAG, "Package not found", e);
            return;
        }
    }

首先調(diào)用removeIconsForPkg方法柿赊,也就是刪除Icon俩功,看代碼:

    public synchronized void removeIconsForPkg(String packageName, UserHandleCompat user) {
        removeFromMemCacheLocked(packageName, user);
        long userSerial = mUserManager.getSerialNumberForUser(user);
        mIconDb.getWritableDatabase().delete(IconDB.TABLE_NAME,
                IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?",
                new String[] {packageName + "/%", Long.toString(userSerial)});
    }

首先調(diào)用removeFromMemCacheLocked方法,其實(shí)這個(gè)方法就是從mCache中把緩存的CacheEntry對(duì)象刪除碰声,然后再從數(shù)據(jù)庫刪除Icon诡蜓。然后回到updateIconsForPkg方法,接著調(diào)用addIconToDBAndMemCache方法胰挑,也就是添加Icon到數(shù)據(jù)庫:

    @Thunk void addIconToDBAndMemCache(LauncherActivityInfoCompat app, PackageInfo info,
            long userSerial) {
        // Reuse the existing entry if it already exists in the DB. This ensures that we do not
        // create bitmap if it was already created during loader.
        ContentValues values = updateCacheAndGetContentValues(app, false);
        addIconToDB(values, app.getComponentName(), info, userSerial);
    }

首先調(diào)用updateCacherAndGetContentValues這個(gè)方法:

@Thunk ContentValues updateCacheAndGetContentValues(LauncherActivityInfoCompat app,
            boolean replaceExisting) {
        final ComponentKey key = new ComponentKey(app.getComponentName(), app.getUser());
        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.isLowResIcon || entry.icon == null) {
                entry = null;
            }
        }
        if (entry == null) {
            entry = new CacheEntry();
            entry.icon = Utilities.createIconBitmap(app.getBadgedIcon(mIconDpi), mContext);
        }
        entry.title = app.getLabel();
        entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
        mCache.put(new ComponentKey(app.getComponentName(), app.getUser()), entry);

        return newContentValues(entry.icon, entry.title.toString(), mActivityBgColor);
    }

這個(gè)方法是生成新的CacheEntry蔓罚,以及Icon,放將其放置到mCache中緩存瞻颂,就是我們上面刪除的那個(gè)脚粟,然后通過調(diào)用newContentValues方法將Icon轉(zhuǎn)換成byte數(shù)組放到ContentValues中,最后存入數(shù)據(jù)庫中蘸朋。這就是我們安裝,更新扣唱,卸載時(shí)對(duì)于Icon的數(shù)據(jù)庫操作藕坯。我們?cè)贗con生成后其實(shí)要放到相應(yīng)的應(yīng)用對(duì)象中,以方便我們顯示到桌面上噪沙,其實(shí)就是(setIcon(icon)炼彪、si.updateIcon(mIconCache))這兩個(gè)方法,第一個(gè)是直接將生成好的Icon放入到ShortcutInfo中正歼,另一個(gè)是從緩存獲取辐马,我們來看從緩存獲取這個(gè)方法:

    public void updateIcon(IconCache iconCache) {
        updateIcon(iconCache, shouldUseLowResIcon());
    }

調(diào)用updateIcon方法:

    public void updateIcon(IconCache iconCache, boolean useLowRes) {
        if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
            iconCache.getTitleAndIcon(this, promisedIntent != null ? promisedIntent : intent, user,
                    useLowRes);
        }
    }

我們看到此時(shí)調(diào)用了iconCache.getTitleAndIcon方法,也就是又回到我們之前將的獲取Icon的方法了局义。

整個(gè)Icon加載的流程基本就是這些喜爷,有些我沒有詳細(xì)講解,自己看看就好了萄唇,Icon會(huì)放到ShortcutInfo中檩帐,在綁定圖標(biāo)的時(shí)候會(huì)讀取出來顯示到桌面上,流程就是這樣的另萤,如果要做切換主題其實(shí)就是從這里入手湃密。

設(shè)置壁紙

原生桌面長(zhǎng)按桌面空白處,會(huì)出現(xiàn)壁紙四敞、widget和設(shè)置三個(gè)菜單泛源,我們點(diǎn)擊壁紙會(huì)進(jìn)入壁紙選擇設(shè)置界面,也就是WallpaperPickerActivity忿危,WallpaperPickerActivity繼承WallpaperCropActivity达箍,所以有些操作可能分別在這兩個(gè)類中進(jìn)行。

設(shè)置壁紙是從WallpaperCropActivity中的setWallpaper方法開始的:

protected void setWallpaper(Uri uri, final boolean finishActivityWhenDone) {
        int rotation = BitmapUtils.getRotationFromExif(getContext(), uri);
        BitmapCropTask cropTask = new BitmapCropTask(
                getContext(), uri, null, rotation, 0, 0, true, false, null);
        final Point bounds = cropTask.getImageBounds();
        Runnable onEndCrop = new Runnable() {
            public void run() {
                updateWallpaperDimensions(bounds.x, bounds.y);
                if (finishActivityWhenDone) {
                    setResult(Activity.RESULT_OK);
                    finish();
                }
            }
        };
        cropTask.setOnEndRunnable(onEndCrop);
        cropTask.setNoCrop(true);
        cropTask.execute();
    }

其中BitmapCropTask是一個(gè)異步任務(wù)癌蚁,也就是執(zhí)行異步任務(wù)設(shè)置壁紙然后調(diào)用onEndCrop中的run方法結(jié)束改界面幻梯,返回桌面兜畸。異步任務(wù)執(zhí)行順序是:onPreExecute-->doInBackground-->onPostExecute。我們看代碼:

public class BitmapCropTask extends AsyncTask<Void, Void, Boolean> {

    // Helper to setup input stream
    private InputStream regenerateInputStream() {
        ...
    }
    
    public boolean cropBitmap() {
        ...
    }

    @Override
    protected Boolean doInBackground(Void... params) {
        return cropBitmap();
    }

    @Override
    protected void onPostExecute(Boolean result) {
        ...
    }
}

首先初始化碘梢,然后執(zhí)行doInBackground方法咬摇,其實(shí)這個(gè)方法中執(zhí)行的是cropBitmap方法,代碼:

    public boolean cropBitmap() {
        ...
        if (mSetWallpaper) {
            //獲取WallpaperManager對(duì)象
            wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());
        }

        if (mSetWallpaper && mNoCrop) {
            try {
                //不需要裁切的情況下煞躬,直接通過URI獲取圖片流
                InputStream is = regenerateInputStream();
                if (is != null) {
                    //如果圖片存在肛鹏,設(shè)置壁紙
                    wallpaperManager.setStream(is);
                    Utils.closeSilently(is);
                }
            } catch (IOException e) {
                Log.w(LOGTAG, "cannot write stream to wallpaper", e);
                failure = true;
            }
            return !failure;
        } else {// 如果需要裁切
            // Find crop bounds (scaled to original image size)
            ...
            
            //獲取圖片的大小范圍
            Point bounds = getImageBounds();
            //判斷是否需要旋轉(zhuǎn)
            if (mRotation > 0) {
                rotateMatrix.setRotate(mRotation);
                inverseRotateMatrix.setRotate(-mRotation);
                ...
            }

            mCropBounds.roundOut(roundedTrueCrop);
            //如果寬高小于0則視為失敗
            if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
                ...
                return false;
            }

            // 根據(jù)寬高比來設(shè)置縮放倍數(shù)
            int scaleDownSampleSize = Math.max(1, Math.min(roundedTrueCrop.width() / mOutWidth,
                    roundedTrueCrop.height() / mOutHeight));
            ...
            try {
                //通過流讀取圖片
                is = regenerateInputStream();
                ...
                decoder = BitmapRegionDecoder.newInstance(is, false);
                Utils.closeSilently(is);
            } catch (IOException e) {
                ...
            } finally {
               ...
            }

            Bitmap crop = null;
            if (decoder != null) {
                // Do region decoding to get crop bitmap
                BitmapFactory.Options options = new BitmapFactory.Options();
                if (scaleDownSampleSize > 1) {
                    options.inSampleSize = scaleDownSampleSize;
                }
                // 獲取切割圖片
                crop = decoder.decodeRegion(roundedTrueCrop, options);
                decoder.recycle();
            }

            if (crop == null) {//獲取切割圖片失敗
                // BitmapRegionDecoder has failed, try to crop in-memory
                is = regenerateInputStream();
                Bitmap fullSize = null;
                if (is != null) {
                    BitmapFactory.Options options = new BitmapFactory.Options();
                    if (scaleDownSampleSize > 1) {
                        options.inSampleSize = scaleDownSampleSize;
                    }
                    //獲取原始圖片
                    fullSize = BitmapFactory.decodeStream(is, null, options);
                    Utils.closeSilently(is);
                }
                if (fullSize != null) {
                    // 計(jì)算切割圖片的范圍
                    ...
                    //生成切割圖片
                    crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
                            roundedTrueCrop.top, roundedTrueCrop.width(),
                            roundedTrueCrop.height());
                }
            }
            
            ...
            
            if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) {
                ...

                Matrix m = new Matrix();
                // 不需要旋轉(zhuǎn)
                if (mRotation == 0) {
                    m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
                } else {//旋轉(zhuǎn)
                    ...
                }
                
                //生成新的旋轉(zhuǎn)后的圖片
                Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
                        (int) returnRect.height(), Bitmap.Config.ARGB_8888);
                if (tmp != null) {
                    Canvas c = new Canvas(tmp);
                    Paint p = new Paint();
                    p.setFilterBitmap(true);
                    c.drawBitmap(crop, m, p);
                    crop = tmp;
                }
            }

            if (mSaveCroppedBitmap) {
                mCroppedBitmap = crop;
            }

            // Compress to byte array
            ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
            //壓縮圖片成數(shù)組
            if (crop.compress(CompressFormat.JPEG, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
                // If we need to set to the wallpaper, set it
                if (mSetWallpaper && wallpaperManager != null) {
                    try {
                        byte[] outByteArray = tmpOut.toByteArray();
                        //設(shè)置壁紙
                        wallpaperManager.setStream(new ByteArrayInputStream(outByteArray));
                        if (mOnBitmapCroppedHandler != null) {
                            mOnBitmapCroppedHandler.onBitmapCropped(outByteArray);
                        }
                    } catch (IOException e) {
                        ...
                    }
                }
            } else {
                ...
            }
        }
        return !failure; // True if any of the operations failed

整個(gè)過程看上面代碼,解釋都卸載注釋里面了恩沛,一些裁切計(jì)算問題看看代碼就知道了在扰,最終就是轉(zhuǎn)換成流的形式進(jìn)行設(shè)置壁紙。

最后

原文地址:墨香博客

Github地址:https://github.com/yuchuangu85/Launcher3_mx/tree/launcher3_6.0

Android開發(fā)群:192508518

微信公眾賬號(hào):Code-MX

注:本文原創(chuàng)雷客,轉(zhuǎn)載請(qǐng)注明出處芒珠,多謝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末搅裙,一起剝皮案震驚了整個(gè)濱河市皱卓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌部逮,老刑警劉巖娜汁,帶你破解...
    沈念sama閱讀 212,294評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異兄朋,居然都是意外死亡掐禁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,493評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門颅和,熙熙樓的掌柜王于貴愁眉苦臉地迎上來傅事,“玉大人,你說我怎么就攤上這事峡扩∠硗辏” “怎么了?”我有些...
    開封第一講書人閱讀 157,790評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵有额,是天一觀的道長(zhǎng)般又。 經(jīng)常有香客問我,道長(zhǎng)巍佑,這世上最難降的妖魔是什么茴迁? 我笑而不...
    開封第一講書人閱讀 56,595評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮萤衰,結(jié)果婚禮上堕义,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好倦卖,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,718評(píng)論 6 386
  • 文/花漫 我一把揭開白布洒擦。 她就那樣靜靜地躺著,像睡著了一般怕膛。 火紅的嫁衣襯著肌膚如雪熟嫩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,906評(píng)論 1 290
  • 那天褐捻,我揣著相機(jī)與錄音掸茅,去河邊找鬼。 笑死柠逞,一個(gè)胖子當(dāng)著我的面吹牛昧狮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播板壮,決...
    沈念sama閱讀 39,053評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼逗鸣,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了绰精?” 一聲冷哼從身側(cè)響起慕购,我...
    開封第一講書人閱讀 37,797評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎茬底,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體获洲,經(jīng)...
    沈念sama閱讀 44,250評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡阱表,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,570評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贡珊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片最爬。...
    茶點(diǎn)故事閱讀 38,711評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖门岔,靈堂內(nèi)的尸體忽然破棺而出爱致,到底是詐尸還是另有隱情,我是刑警寧澤寒随,帶...
    沈念sama閱讀 34,388評(píng)論 4 332
  • 正文 年R本政府宣布糠悯,位于F島的核電站,受9級(jí)特大地震影響妻往,放射性物質(zhì)發(fā)生泄漏互艾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,018評(píng)論 3 316
  • 文/蒙蒙 一讯泣、第九天 我趴在偏房一處隱蔽的房頂上張望纫普。 院中可真熱鬧,春花似錦好渠、人聲如沸昨稼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,796評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽假栓。三九已至寻行,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間但指,已是汗流浹背寡痰。 一陣腳步聲響...
    開封第一講書人閱讀 32,023評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留棋凳,地道東北人拦坠。 一個(gè)月前我還...
    沈念sama閱讀 46,461評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像剩岳,于是被迫代替她去往敵國(guó)和親贞滨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,595評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容