上一章墨香帶你學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地址:github.com/yuchuangu85…
同步釋出:www.codemx.cn/2017/05/19/…
Android開發群:192508518
微信公眾賬號:Code-MX
注:本文原創,轉載請註明出處,多謝。