首先根據(jù)Launcher3的源碼查找卸載后的圖標(biāo)刪除流程,看看它在卸載后做了那些事。根據(jù)源碼查找到LauncherAppState類的構(gòu)造方法中有個(gè)叫LauncherAppsCompat的類,它監(jiān)聽著APP的變化,并且向它注冊了一個(gè)callback:
LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);
這里的mModel就是LauncherModel對象,它實(shí)現(xiàn)了OnAppsChangedCallbackCompat接口。
public interface OnAppsChangedCallbackCompat {
void onPackageRemoved(String packageName, UserHandleCompat user);
void onPackageAdded(String packageName, UserHandleCompat user);
void onPackageChanged(String packageName, UserHandleCompat user);
void onPackagesAvailable(String[] packageNames, UserHandleCompat user, boolean replacing);
void onPackagesUnavailable(String[] packageNames, UserHandleCompat user, boolean replacing);
}
OnAppsChangedCallbackCompat接口有各種回調(diào),其中onPackageRemoved方法就是卸載某一個(gè)APK時(shí)會(huì)回調(diào)的方法。緊接著我們看看它在LauncherModel里的實(shí)現(xiàn)。
@Override
public void onPackageRemoved(String packageName, UserHandleCompat user) {
int op = PackageUpdatedTask.OP_REMOVE;
enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName }, user));
}
void enqueuePackageUpdated(PackageUpdatedTask task) {
sWorker.post(task);
}
它啟動(dòng)了一個(gè)叫PackageUpdatedTask的Runnable,我們看看run()方法里面干了些什么。run()方法里面做了很多事情,這里我們只關(guān)心卸載相關(guān)的邏輯。
switch (mOp) {
case OP_ADD: {
...........................
break;
}
case OP_UPDATE:
............................
break;
case OP_REMOVE: {
ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
if (heuristic != null) {
heuristic.processPackageRemoved(mPackages);
}
for (int i=0; i<N; i++) {
if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
mIconCache.removeIconsForPkg(packages[i], mUser);
}
// Fall through
}
//注意:這里并沒有break,它是直接往下走的
case OP_UNAVAILABLE:
for (int i=0; i<N; i++) {
if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
mBgAllAppsList.removePackage(packages[i], mUser);
mApp.getWidgetCache().removePackage(packages[i], mUser);
}
break;
}
............................
final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
............................
if (mBgAllAppsList.removed.size() > 0) {
removedApps.addAll(mBgAllAppsList.removed);
mBgAllAppsList.removed.clear();
}
這段邏輯特別要注意的地方是switch里面的OP_REMOVE處理,它是沒有break的,它是直接走進(jìn)了OP_UNAVAILABLE邏輯中,在這里它把這個(gè)卸載的應(yīng)用從所有應(yīng)用列表中刪除mBgAllAppsList.removePackage(packages[i], mUser);,緊接著下面創(chuàng)建了一個(gè)removedApps的list存放著卸載數(shù)據(jù)。這個(gè)數(shù)據(jù)是在 mBgAllAppsList.removePackage(packages[i], mUser);中被添加到mBgAllAppsList.removed列表中的。
/**
* Remove the apps for the given apk identified by packageName.
*/
public void removePackage(String packageName, UserHandleCompat user) {
final List<AppInfo> data = this.data;
for (int i = data.size() - 1; i >= 0; i--) {
AppInfo info = data.get(i);
final ComponentName component = info.intent.getComponent();
if (info.user.equals(user) && packageName.equals(component.getPackageName())) {
removed.add(info);
data.remove(i);
}
}
}
把卸載的數(shù)據(jù)放入一個(gè)列表存起來干嘛呢?我們繼續(xù)往下看,中間有一大段是新增和修改APP的處理邏輯,我們直接略過,我們依然只看卸載相關(guān)。
final ArrayList<String> removedPackageNames = new ArrayList<String>();
if (mOp == OP_REMOVE || mOp == OP_UNAVAILABLE) {
// Mark all packages in the broadcast to be removed
removedPackageNames.addAll(Arrays.asList(packages));
} else if (mOp == OP_UPDATE) {
// Mark disabled packages in the broadcast to be removed
................
}
if (!removedPackageNames.isEmpty() || !removedApps.isEmpty()) {
final int removeReason;
if (mOp == OP_UNAVAILABLE) {
removeReason = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
} else {
// Remove all the components associated with this package
for (String pn : removedPackageNames) {
deletePackageFromDatabase(context, pn, mUser);//關(guān)鍵代碼1
}
// Remove all the specific components
for (AppInfo a : removedApps) {//關(guān)鍵代碼2
ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName, mUser);
deleteItemsFromDatabase(context, infos);
}
removeReason = 0;
}
// Remove any queued items from the install queue
InstallShortcutReceiver.removeFromInstallQueue(context, removedPackageNames, mUser);
// Call the components-removed callback
mHandler.post(new Runnable() {
public void run() {
Callbacks cb = getCallback();
if (callbacks == cb && cb != null) {//關(guān)鍵代碼3
callbacks.bindComponentsRemoved(removedPackageNames, removedApps, mUser, removeReason);
}
}
});
}
這里注意三個(gè)關(guān)鍵代碼的注釋。我們首先看注釋關(guān)鍵代碼1處的邏輯,它調(diào)用了deletePackageFromDatabase(context, pn, mUser);方法,根據(jù)包名來刪除數(shù)據(jù)庫中的數(shù)據(jù),我們再看這個(gè)方法具體做了什么。
/**
* Removes all the items from the database corresponding to the specified package.
*/
static void deletePackageFromDatabase(Context context, final String pn,
final UserHandleCompat user) {
deleteItemsFromDatabase(context, getItemsByPackageName(pn, user));
}
private static ArrayList<ItemInfo> getItemsByPackageName(final String pn, final UserHandleCompat user) {
ItemInfoFilter filter = new ItemInfoFilter() {
@Override
public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
return cn.getPackageName().equals(pn) && info.user.equals(user);
}
};
return filterItemInfos(sBgItemsIdMap, filter);
}
我們看到它里面是調(diào)用deleteItemsFromDatabase方法,deleteItemsFromDatabase是根據(jù)ItemInfo去刪除相關(guān)數(shù)據(jù),getItemsByPackageName方法是用來通過包名過濾ItemInfo列表信息。它是怎么過濾的呢?我們來看看。
private static ArrayList<ItemInfo> getItemsByPackageName(final String pn, final UserHandleCompat user) {
ItemInfoFilter filter = new ItemInfoFilter() {
@Override
public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
return cn.getPackageName().equals(pn) && info.user.equals(user);
}
};
return filterItemInfos(sBgItemsIdMap, filter);
}
我們看到它是遍歷了sBgItemsIdMap,通過ComponentName獲取包名對比過濾出ItemInfo,sBgItemsIdMap中存儲(chǔ)的是APP圖標(biāo)的信息。至此關(guān)鍵代碼1弄清楚了我們接著玩下看。
關(guān)鍵代碼2處它用到了前面卸載列表removedApps,并調(diào)用deleteItemsFromDatabase方法執(zhí)行刪除,從這里我們知道最終刪除操作都是deleteItemsFromDatabase方法來完成。此處還有一個(gè)方法getItemInfoForComponentName,它也是用來過濾ItemInfo列表的,那它又是怎么實(shí)現(xiàn)的?我們來看看。
@Thunk
ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname, final UserHandleCompat user) {
ItemInfoFilter filter = new ItemInfoFilter() {
@Override
public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
if (info.user == null) {
return cn.equals(cname);
} else {
return cn.equals(cname) && info.user.equals(user);
}
}
};
return filterItemInfos(sBgItemsIdMap, filter);
}
它是直接對比ComponentName對象來過濾的。這兩個(gè)過濾規(guī)則先記一下,后面有大用處。接下來我們看看deleteItemsFromDatabase方法中具體做了什么。
/**
* Removes the specified items from the database
* @param context
* @param item
*/
static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
final ContentResolver cr = context.getContentResolver();
Runnable r = new Runnable() {
public void run() {
for (ItemInfo item : items) {
final Uri uri = LauncherSettings.Favorites.getContentUri(item.id);
cr.delete(uri, null, null);//刪除數(shù)據(jù)庫中數(shù)據(jù)
// Lock on mBgLock *after* the db operation
synchronized (sBgLock) {
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
sBgFolders.remove(item.id);
for (ItemInfo info: sBgItemsIdMap) {
if (info.container == item.id) {
// We are deleting a folder which still contains items that
// think they are contained by that folder.
String msg = "deleting a folder (" + item + ") which still " +
"contains items (" + info + ")";
Log.e(TAG, msg);
}
}
sBgWorkspaceItems.remove(item);//刪除緩存中數(shù)據(jù)
break;
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
sBgWorkspaceItems.remove(item);//刪除緩存中數(shù)據(jù)
break;
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
sBgAppWidgets.remove((LauncherAppWidgetInfo) item);//刪除緩存中數(shù)據(jù)
break;
}
sBgItemsIdMap.remove(item.id);//刪除緩存中數(shù)據(jù)
}
}
}
};
runOnWorkerThread(r);
}
至此我們知道一個(gè)應(yīng)用卸載后它的數(shù)據(jù)刪除情況,數(shù)據(jù)已經(jīng)被刪了,那Launcher中的圖標(biāo)呢?什么時(shí)候被移除?接著看關(guān)鍵代碼3。
關(guān)鍵代碼3處它獲取了一個(gè)Callbacks回調(diào),調(diào)用了bindComponentsRemoved方法,那么是誰注冊的這個(gè)回調(diào)呢?又做了什么?根據(jù)追蹤是在LauncherAppState類中setLauncher方法中通過mModel.initialize(launcher);設(shè)置的Callbacks,實(shí)現(xiàn)接口的是Launcher類,那我們來看看里面是怎么實(shí)現(xiàn)的。
@Override
public void bindComponentsRemoved(final ArrayList<String> packageNames,
final ArrayList<AppInfo> appInfos, final UserHandleCompat user, final int reason) {
Runnable r = new Runnable() {
public void run() {
bindComponentsRemoved(packageNames, appInfos, user, reason);
}
};
if (waitUntilResume(r)) {
return;
}
if (reason == 0) {
HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
for (AppInfo info : appInfos) {
removedComponents.add(info.componentName);
}
if (!packageNames.isEmpty()) {
mWorkspace.removeItemsByPackageName(packageNames, user);
}
if (!removedComponents.isEmpty()) {
mWorkspace.removeItemsByComponentName(removedComponents, user);
}
// Notify the drag controller
mDragController.onAppsRemoved(packageNames, removedComponents);
} else {
mWorkspace.disableShortcutsByPackageName(packageNames, user, reason);
}
// Update AllApps
if (mAppsView != null) {
mAppsView.removeApps(appInfos);
}
}
通過查看可知它調(diào)用了mWorkspace.removeItemsByPackageName(packageNames, user);和mWorkspace.removeItemsByComponentName(removedComponents, user);方法去刪除桌面圖標(biāo)的,具體怎么實(shí)現(xiàn)的繼續(xù)往下看。
void removeItemsByPackageName(final ArrayList<String> packages, final UserHandleCompat user) {
final HashSet<String> packageNames = new HashSet<String>();
packageNames.addAll(packages);
// Filter out all the ItemInfos that this is going to affect
final HashSet<ItemInfo> infos = new HashSet<ItemInfo>();
final HashSet<ComponentName> cns = new HashSet<ComponentName>();
ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
for (CellLayout layoutParent : cellLayouts) {
ViewGroup layout = layoutParent.getShortcutsAndWidgets();
int childCount = layout.getChildCount();
for (int i = 0; i < childCount; ++i) {
View view = layout.getChildAt(i);
infos.add((ItemInfo) view.getTag());
}
}
LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
@Override
public boolean filterItem(ItemInfo parent, ItemInfo info,
ComponentName cn) {
if (packageNames.contains(cn.getPackageName())
&& info.user.equals(user)) {
cns.add(cn);//過濾同一包名的ComponentName對象
return true;
}
return false;
}
};
LauncherModel.filterItemInfos(infos, filter);
// Remove the affected components
removeItemsByComponentName(cns, user);
}
我們看到removeItemsByPackageName方法中是通過ComponentName對象獲取包名對比過濾出一個(gè)HashSet<ComponentName>的集合為cns的對象,然后調(diào)用removeItemsByComponentName(cns, user);方法執(zhí)行刪除???code>removeItemsByComponentName方法中具體做了什么。
void removeItemsByComponentName(final HashSet<ComponentName> componentNames, final UserHandleCompat user) {
ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
for (final CellLayout layoutParent: cellLayouts) {
final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>();
for (int j = 0; j < layout.getChildCount(); j++) {
final View view = layout.getChildAt(j);
children.put((ItemInfo) view.getTag(), view);
}
final ArrayList<View> childrenToRemove = new ArrayList<View>();
final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove =
new HashMap<FolderInfo, ArrayList<ShortcutInfo>>();
LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
@Override
public boolean filterItem(ItemInfo parent, ItemInfo info,
ComponentName cn) {
if (parent instanceof FolderInfo) {
if (componentNames.contains(cn) && info.user.equals(user)) {
FolderInfo folder = (FolderInfo) parent;
ArrayList<ShortcutInfo> appsToRemove;
if (folderAppsToRemove.containsKey(folder)) {
appsToRemove = folderAppsToRemove.get(folder);
} else {
appsToRemove = new ArrayList<ShortcutInfo>();
folderAppsToRemove.put(folder, appsToRemove);
}
appsToRemove.add((ShortcutInfo) info);
return true;
}
} else {
if (componentNames.contains(cn) && info.user.equals(user)) {
childrenToRemove.add(children.get(info));
return true;
}
}
return false;
}
};//過濾出要被刪除的信息
LauncherModel.filterItemInfos(children.keySet(), filter);
// Remove all the apps from their folders
for (FolderInfo folder : folderAppsToRemove.keySet()) {//刪除文件夾里面的數(shù)據(jù)
ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
for (ShortcutInfo info : appsToRemove) {
folder.remove(info);
}
}
// Remove all the other children
for (View child : childrenToRemove) {//刪除桌面圖標(biāo)view
// Note: We can not remove the view directly from CellLayoutChildren as this
// does not re-mark the spaces as unoccupied.
layoutParent.removeViewInLayout(child);
if (child instanceof DropTarget) {
mDragController.removeDropTarget((DropTarget) child);
}
}
if (childrenToRemove.size() > 0) {//刷新界面
layout.requestLayout();
layout.invalidate();
}
}
// Strip all the empty screens
stripEmptyScreens();
}
它首先是過濾出和APP相關(guān)桌面圖標(biāo)view信息(一個(gè)app可能有多入口),存儲(chǔ)在childrenToRemovelist中,然后看是否有圖標(biāo)是在文件夾中,在文件夾中的信息存儲(chǔ)到folderAppsToRemovemap中,然后遍歷childrenToRemove和folderAppsToRemove執(zhí)行刪除操作,最后刷新界面。至此應(yīng)用卸載所引起的桌面圖標(biāo)和快捷方式的刪除流程我們已經(jīng)清楚了。
轉(zhuǎn)載請注明來處:http://www.itdecent.cn/p/67d82d56ca1d