美文网首页移动开发狂热者(299402133)UI程序员
墨香带你学Launcher之(八)- 加载Icon、设置壁纸

墨香带你学Launcher之(八)- 加载Icon、设置壁纸

作者: 翰墨飘香 | 来源:发表于2017-05-25 16:21 被阅读803次

    上一章墨香带你学Launcher之(七)- 小部件的加载、添加以及大小调节介绍了小部件的加载以及添加过程,基于我的计划对于Launcher的讲解基本要完成了,因此本篇是我对Launcher讲解的最后一部分,计划了很久,因为时间的问题一直没有写,今天趁着有空写完。写了八篇,不多,Launcher里面还有很多东西,有兴趣的可以自己继续研究,看完这些主要的其他都是问题了,有什么需要了解的可以留言。最新版的Launcher代码我已经放到github上,想看的自己可以去下载。

    加载Icon

    对于Icon的操作其实主要是加载、更新以及删除,加载主要是启动Launcher、安装应用,更新是在更新应用时更新Icon、删除是卸载应用时会删除Icon,因此我们可以从这几方面分析Icon的处理。

    Launcher启动时Icon加载

    Launcher的数据加载流程我在第二篇墨香带你学Launcher之(二)- 数据加载流程讲过,不熟悉的可以去看看。首先是将xml文件中配置的Apk信息解析保存到数据库,然后读取数据库,查看手机中是否存在该apk,如果有加载相关信息,加载流程在“loadWorkspace”方法中,在加载过程中会去生成对应的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);
        ...
    }
    

    在段代码中主要有三个方法涉及到加载Icon,getAppShortcutInfo、getRestoredItemInfo以及getShortcutInfo方法,我们看看这个三个方法的代码:

    第一个:

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

    在这段代码中主要是调用IconCache中的getTitleAndIcon方法,这个方法详细过程我们一会再看,然后判断是否是默认图标,如果是生成Icon图标,如果能生成则设置图标,如果不能生成则采用默认图标。Utilities.createIconBitmap代码不在详细讲,看看就会了。

    我们接着看第二个方法:

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

    这个方法中主要是调用CursorIconInfo中的loadIcon方法,代码我们一会再看,如果能获取到Icon则设置这个Icon,如果不能则通过IconCache.getTitleAndIcon方法获取,和上面一样了。

    第三个方法:

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

    这个方法中还是调用CursorIconInfo中的loadIcon方法,如果能获取,则设置图标,如果不能获取默认图标设置。从上面三个方法代码看其实最终调用了两个方法,一个是IconCache.getTitleAndIcon方法,一个是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;
        }
    

    在这个方法中首先是从资源获取,如果获取不到,则从数据库获取,及Utilities.createIconBitmap(packageName, resourceName, context)和Utilities.createIconBitmap(c, iconIndex, context),我们看看这两个方法:

    第一个方法:

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

    这个方法是根据包名获取id,然后根据id获取drawable,由drawable生产Bitmap。

    第二个方法:

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

    从数据库读取Icon的byte数据,然后生成图片。这样看就很清楚这个方法加载Icon的过程了。那么数据库中的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这个方法创建了Icon,这个方法有个我们不熟悉的对象entry,向上看这个entry是子啊上面通过cacheLocked方法创建的,我们跟踪一下这个方法:

    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对象,则不需要再创建,如果没有则要创建改对象,然后加载到mCache中,然后通过调用getEntryFromDB方法从数据库查询是否有改对象信息,如果没有则要创建对应Icon,我们先看看getEntryFromDB这个方法:

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

    该方法通过查询数据库来生成Icon,调用方法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方法中,如果数据库中没有,要继续创建Icon,首先判断LauncherActivityInfoCompat是否为空,调用Utilities.createIconBitmap方法获取Icon,代码就不贴了,也不难,如果为空的话会判断usePackageIcon(根据包名获取Icon),如果用的话则会调用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方法很像,也是先判断数据库中是否存在,不存在就要加载,这里有个方法addIconToDB,看上面ContentValues的注释,就是把Icon存到数据库中,原来是在这里存入数据库的,其实Icon的信息首先放入ContentValues中,然后存入数据库,我们看看代码:

        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转换成byte数组然后存入数据库。再回到cacheLocked方法中,如果还是没有获取到Icon,那么只能获取系统默认Icon了,也就是我们自己写app的默认Icon图标(机器人图标)。这个是我们加载配置文件中的Apk信息时加载Icon的过程,我们再看看加载所有app时是不是也是这样,我们先看加载方法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对象的生成,我们看看代码:

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

    从上面代码我们看到其实还是调用getTitleAndIcon方法,又回到我们上面讲的过程了。

    APK安装、更新、卸载时Icon处理

    APK的安装、卸载、更新、可用以及不可用在墨香带你学Launcher之(四)-应用安装、更新、卸载时的数据加载中讲到过,不清楚的可以去看看,这几个实现方法是在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));
            }
        }
    

    我们看代码发现其实都是PackageUpdatedTask这个执行方法,代码比较多,我们只贴重点部分,详细的可以去看源码:

    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(更新)时都是调用的mIconCache.removeIconsForPkg,而和OP_REMOVE(卸载)时调用mIconCache.removeIconsForPkg方法,而在下面又调用了si.setIcon(icon)、si.updateIcon来更新Icon,我们分别来看看这四个方法,首先看第一个方法(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;
            }
        }
    

    首先调用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)});
        }
    

    首先调用removeFromMemCacheLocked方法,其实这个方法就是从mCache中把缓存的CacheEntry对象删除,然后再从数据库删除Icon。然后回到updateIconsForPkg方法,接着调用addIconToDBAndMemCache方法,也就是添加Icon到数据库:

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

    首先调用updateCacherAndGetContentValues这个方法:

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

    这个方法是生成新的CacheEntry,以及Icon,放将其放置到mCache中缓存,就是我们上面删除的那个,然后通过调用newContentValues方法将Icon转换成byte数组放到ContentValues中,最后存入数据库中。这就是我们安装,更新,卸载时对于Icon的数据库操作。我们在Icon生成后其实要放到相应的应用对象中,以方便我们显示到桌面上,其实就是(setIcon(icon)、si.updateIcon(mIconCache))这两个方法,第一个是直接将生成好的Icon放入到ShortcutInfo中,另一个是从缓存获取,我们来看从缓存获取这个方法:

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

    调用updateIcon方法:

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

    我们看到此时调用了iconCache.getTitleAndIcon方法,也就是又回到我们之前将的获取Icon的方法了。

    整个Icon加载的流程基本就是这些,有些我没有详细讲解,自己看看就好了,Icon会放到ShortcutInfo中,在绑定图标的时候会读取出来显示到桌面上,流程就是这样的,如果要做切换主题其实就是从这里入手。

    设置壁纸

    原生桌面长按桌面空白处,会出现壁纸、widget和设置三个菜单,我们点击壁纸会进入壁纸选择设置界面,也就是WallpaperPickerActivity,WallpaperPickerActivity继承WallpaperCropActivity,所以有些操作可能分别在这两个类中进行。

    设置壁纸是从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是一个异步任务,也就是执行异步任务设置壁纸然后调用onEndCrop中的run方法结束改界面,返回桌面。异步任务执行顺序是: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) {
            ...
        }
    }
    

    首先初始化,然后执行doInBackground方法,其实这个方法中执行的是cropBitmap方法,代码:

        public boolean cropBitmap() {
            ...
            if (mSetWallpaper) {
                //获取WallpaperManager对象
                wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());
            }
    
            if (mSetWallpaper && mNoCrop) {
                try {
                    //不需要裁切的情况下,直接通过URI获取图片流
                    InputStream is = regenerateInputStream();
                    if (is != null) {
                        //如果图片存在,设置壁纸
                        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();
                //判断是否需要旋转
                if (mRotation > 0) {
                    rotateMatrix.setRotate(mRotation);
                    inverseRotateMatrix.setRotate(-mRotation);
                    ...
                }
    
                mCropBounds.roundOut(roundedTrueCrop);
                //如果宽高小于0则视为失败
                if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
                    ...
                    return false;
                }
    
                // 根据宽高比来设置缩放倍数
                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) {
                        // 计算切割图片的范围
                        ...
                        //生成切割图片
                        crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
                                roundedTrueCrop.top, roundedTrueCrop.width(),
                                roundedTrueCrop.height());
                    }
                }
                
                ...
                
                if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) {
                    ...
    
                    Matrix m = new Matrix();
                    // 不需要旋转
                    if (mRotation == 0) {
                        m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
                    } else {//旋转
                        ...
                    }
                    
                    //生成新的旋转后的图片
                    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);
                //压缩图片成数组
                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();
                            //设置壁纸
                            wallpaperManager.setStream(new ByteArrayInputStream(outByteArray));
                            if (mOnBitmapCroppedHandler != null) {
                                mOnBitmapCroppedHandler.onBitmapCropped(outByteArray);
                            }
                        } catch (IOException e) {
                            ...
                        }
                    }
                } else {
                    ...
                }
            }
            return !failure; // True if any of the operations failed
    

    整个过程看上面代码,解释都卸载注释里面了,一些裁切计算问题看看代码就知道了,最终就是转换成流的形式进行设置壁纸。

    最后

    原文地址:墨香博客

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

    Android开发群:192508518

    微信公众账号:Code-MX

    注:本文原创,转载请注明出处,多谢。

    相关文章

      网友评论

      • 鸩羽千夜92:博主,你上传到GitHub 上的Launcher3代码是做过定制需求修改过的,还是android 原生没有修改过的
        翰墨飘香:@鸩羽千夜92 第一个是原生的,单层那个是修改的,都可以直接编译
      • yhmatg:up主,Launcher 如何去掉主菜单,所有应用摆在桌面,类似小米桌面:smile: 求解答
        yhmatg:好的,我试试看,先谢谢了:smile:
        翰墨飘香:@yhmatg 你看看绑定应用的时候直接绑定到桌面,而不是绑定到二级菜单

      本文标题:墨香带你学Launcher之(八)- 加载Icon、设置壁纸

      本文链接:https://www.haomeiwen.com/subject/pudzxxtx.html