Android Launcher3應(yīng)用卸載后桌面圖標(biāo)及快捷方式的刪除流程

首先根據(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中,然后遍歷childrenToRemovefolderAppsToRemove執(zhí)行刪除操作,最后刷新界面。至此應(yīng)用卸載所引起的桌面圖標(biāo)和快捷方式的刪除流程我們已經(jīng)清楚了。

轉(zhuǎn)載請注明來處:http://www.itdecent.cn/p/67d82d56ca1d

下一篇:Android Launcher3中微信聯(lián)系人快捷方式無法卸載的解決方案

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容