上一章墨香带你学Launcher之(七)- 小部件的加载、添加以及大小调节 介绍了小部件的加载以及添加过程,基于我的计划对于Launcher的讲解基本要完成了,因此本篇是我对Launcher讲解的最后一部分,计划了很久,因为时间的问题一直没有写,今天趁着有空写完。写了八篇,不多,Launcher里面还有很多东西,有兴趣的可以自己继续研究,看完这些主要的其他都是问题了,有什么需要了解的可以留言。最新版的Launcher 代码我已经放到github上,想看的自己可以去下载。
加载Icon 对于Icon的操作其实主要是加载、更新以及删除,加载主要是启动Launcher、安装应用,更新是在更新应用时更新Icon、删除是卸载应用时会删除Icon,因此我们可以从这几方面分析Icon的处理。
Launcher启动时Icon加载 Launcher的数据加载流程我在第二篇墨香带你学Launcher之(二)- 数据加载流程 讲过,不熟悉的可以去看看。首先是将xml文件中配置的Apk信息解析保存到数据库,然后读取数据库,查看手机中是否存在该apk,如果有加载相关信息,加载流程在“loadWorkspace”方法中,在加载过程中会去生成对应的Icon,我们看一下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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方法,我们看看这个三个方法的代码:
第一个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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代码不在详细讲,看看就会了。
我们接着看第二个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public ShortcutInfo getRestoredItemInfo (Cursor c, int titleIndex, Intent intent, int promiseType, int itemType, CursorIconInfo iconInfo, Context context) { ... Bitmap icon = iconInfo.loadIcon(c, info, context); if (icon == null ) { mIconCache.getTitleAndIcon(info, intent, info.user, false ); } else { info.setIcon(icon); } ... }
这个方法中主要是调用CursorIconInfo中的loadIcon方法,代码我们一会再看,如果能获取到Icon则设置这个Icon,如果不能则通过IconCache.getTitleAndIcon方法获取,和上面一样了。
第三个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 ShortcutInfo getShortcutInfo (Cursor c, Context context, int titleIndex, CursorIconInfo iconInfo) { ... Bitmap icon = iconInfo.loadIcon(c, info, context); if (icon == null ) { icon = mIconCache.getDefaultIcon(info.user); info.usingFallbackIcon = true ; } info.setIcon(icon); return info; }
这个方法中还是调用CursorIconInfo中的loadIcon方法,如果能获取,则设置图标,如果不能获取默认图标设置。从上面三个方法代码看其实最终调用了两个方法,一个是IconCache.getTitleAndIcon方法,一个是CursorIconInfo.loadIcon方法。
我们先看一下CursorIconInfo.loadIcon代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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 ) { 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),我们看看这两个方法:
第一个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static Bitmap createIconBitmap (String packageName, String resourceName, Context context) { PackageManager packageManager = context.getPackageManager(); 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) { } return null ; }
这个方法是根据包名获取id,然后根据id获取drawable,由drawable生产Bitmap。
第二个方法:
1 2 3 4 5 6 7 8 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方法:
1 2 3 4 5 6 7 8 9 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方法创建的,我们跟踪一下这个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 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); 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这个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 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,看代码:
1 2 3 4 5 6 7 8 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,看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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 ; 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 ; 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 ; } } if (entryUpdated) { mCache.put(cacheKey, entry); } } return entry; }
代码和cacheLocked方法很像,也是先判断数据库中是否存在,不存在就要加载,这里有个方法addIconToDB,看上面ContentValues的注释,就是把Icon存到数据库中,原来是在这里存入数据库的,其实Icon的信息首先放入ContentValues中,然后存入数据库,我们看看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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代码:
1 2 3 4 5 6 7 8 9 10 11 12 private void loadAllApps () { ... for (int i = 0 ; i < apps.size(); i++) { LauncherActivityInfoCompat app = apps.get(i); mBgAllAppsList.add(new AppInfo (mContext, app, user, mIconCache)); } ... }
我们看到主要是AppInfo对象的生成,我们看看代码:
1 2 3 4 5 6 7 8 9 10 11 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 ); intent = makeLaunchIntent(context, info, user); this .user = user; }
从上面代码我们看到其实还是调用getTitleAndIcon方法,又回到我们上面讲的过程了。
APK安装、更新、卸载时Icon处理 APK的安装、卸载、更新、可用以及不可用在墨香带你学Launcher之(四)-应用安装、更新、卸载时的数据加载 中讲到过,不清楚的可以去看看,这几个实现方法是在LauncherModel中来处理的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 @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 { 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这个执行方法,代码比较多,我们只贴重点部分,详细的可以去看源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 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 ; } ... if (mOp == OP_ADD || mOp == OP_UPDATE) { ... synchronized (sBgLock) { for (ItemInfo info : sBgItemsIdMap) { if (info instanceof ShortcutInfo && mUser.equals(info.user)) { ... 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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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,看代码:
1 2 3 4 5 6 7 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到数据库:
1 2 3 4 5 6 7 @Thunk void addIconToDBAndMemCache (LauncherActivityInfoCompat app, PackageInfo info, long userSerial) { ContentValues values = updateCacheAndGetContentValues(app, false ); addIconToDB(values, app.getComponentName(), info, userSerial); }
首先调用updateCacherAndGetContentValues这个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @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); 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中,另一个是从缓存获取,我们来看从缓存获取这个方法:
1 2 3 public void updateIcon (IconCache iconCache) { updateIcon(iconCache, shouldUseLowResIcon()); }
调用updateIcon方法:
1 2 3 4 5 6 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方法开始的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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。我们看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class BitmapCropTask extends AsyncTask <Void, Void, Boolean> { private InputStream regenerateInputStream () { ... } public boolean cropBitmap () { ... } @Override protected Boolean doInBackground (Void... params) { return cropBitmap(); } @Override protected void onPostExecute (Boolean result) { ... } }
首先初始化,然后执行doInBackground方法,其实这个方法中执行的是cropBitmap方法,代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 public boolean cropBitmap () { ... if (mSetWallpaper) { wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext()); } if (mSetWallpaper && mNoCrop) { try { 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 { ... Point bounds = getImageBounds(); if (mRotation > 0 ) { rotateMatrix.setRotate(mRotation); inverseRotateMatrix.setRotate(-mRotation); ... } mCropBounds.roundOut(roundedTrueCrop); 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 ) { BitmapFactory.Options options = new BitmapFactory .Options(); if (scaleDownSampleSize > 1 ) { options.inSampleSize = scaleDownSampleSize; } crop = decoder.decodeRegion(roundedTrueCrop, options); decoder.recycle(); } if (crop == null ) { 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; } ByteArrayOutputStream tmpOut = new ByteArrayOutputStream (2048 ); if (crop.compress(CompressFormat.JPEG, DEFAULT_COMPRESS_QUALITY, tmpOut)) { 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;
整个过程看上面代码,解释都卸载注释里面了,一些裁切计算问题看看代码就知道了,最终就是转换成流的形式进行设置壁纸。
最后 Github地址:https://github.com/yuchuangu85/Launcher3_mx/tree/launcher3_6.0
Android开发群:192508518
微信公众账号:Code-MX
注:本文原创,转载请注明出处,多谢。