基于 Launcher3 2c34cba

概述

找到LauncherModel中监听应用安装的回调,在PackageUpdateTask中通过ItemInstallQueue发送一条添加应用图标的任务,桌面在onResume后会执行图标添加

代码修改位置

找到com.android.launcher3.model.PackageUpdatedTask这个类,在execute方法中最后面部分进行修改
修改前

if (mOp == OP_ADD) {
    // Load widgets for the new package. Changes due to app updates are handled through
    // AppWidgetHost events, this is just to initialize the long-press options.
    for (int i = 0; i < N; i++) {
        dataModel.widgetsModel.update(app, new PackageUserKey(packages[i], mUser));
    }
    bindUpdatedWidgets(dataModel);
}

修改后

if (mOp == OP_ADD) {
    for (String pkg : mPackages) {
        //将新安装的应用包名和当前user发送给ItemInstallQueue待添加
        ItemInstallQueue.INSTANCE.get(context).queueItem(pkgmUser);
    }
    
    // Load widgets for the new package. Changes due to app updates are handled through
    // AppWidgetHost events, this is just to initialize the long-press options.
    for (int i = 0; i < N; i++) {
        dataModel.widgetsModel.update(app, new PackageUserKey(packages[i], mUser));
    }
    bindUpdatedWidgets(dataModel);
}

简易流程

graph TB
LauncherModel.onPackageAdded-->PackageUpdateTask.execute-->ItemInstallQueue.queueItem-->LauncherModel.addAndBindAddedWorkspaceItems-->AddWorkspaceItemTask.execute-->Launcher.bindAppsAdded

逻辑分析

在LauncherAppState的构造方法中,LauncherModel被注册到了LauncherApps中,LauncherApps是一个系统服务,可以用来查询和监听系统内应用的安装卸载等变化。
LauncherModel类继承了LauncherApps.Callback并实现了其回调方法。
所以当有新应用安装成功后,会回调此方法,在此方法内创建了一个op为OP_ADD的PackageUpdatedTask给线程池执行

@Override
public void onPackageAdded(@NonNull final String packageName, @NonNullfinal UserHandle user) {
    int op = PackageUpdatedTask.OP_ADD;
    enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
}

给PackageUpdatedTask一些参数赋值

public void enqueueModelUpdateTask(@NonNull final ModelUpdateTask task){
    if (mModelDestroyed) {
        return;
    }
    task.init(mApp, this, mBgDataModel, mBgAllAppsList, MAIN_EXECUTOR);
    MODEL_EXECUTOR.execute(task);
}

在MODEL_EXECUTOR.execute(task)后这个PackageUpdatedTask的execute方法会被执行,在这个方法的末尾刚好有一个mOp == OP_ADD的判断,在这个判断里面我们添加一段代码,将新安装应用的包名和user添加到ItemInstallQueue的集合中

if (mOp == OP_ADD) {
    for (String pkg : mPackages) {
        //新安装的应用添加到队列中,在桌面回到前台后,自动将图标显示到桌面
        ItemInstallQueue.INSTANCE.get(context).queueItem(pkg,mUser);
    }
    
    // Load widgets for the new package. Changes due to app updates are handled through
    // AppWidgetHost events, this is just to initialize the long-press options.
    for (int i = 0; i < N; i++) {
        dataModel.widgetsModel.update(app, new PackageUserKey(packages[i], mUser));
    }
    bindUpdatedWidgets(dataModel);
}

ItemInstallQueue这个类的作用是处理将要向workspace插入的item,item包括shortcut或者widget
将包名和userHandle封装成一个PendingInstallShortcutInfo,然后存入到集合中待执行

public void queueItem(String packageName, UserHandle userHandle) {
    queuePendingShortcutInfo(new PendingInstallShortcutInfo(packageName, userHandle));
}

info.getItemInfo会根据创建PendingInstallShortcutInfo时的包名和类型来创建一个WorkspaceItemInfo。

这里执行时会打印stackTrace,应该是原开发者想看一些堆栈信息留下的

当PendingInstallShortcutInfo被添加到集合后并不一定会立即执行,通常情况是桌面处于可见情况时才会执行队列里的任务,Launcher通过调用ItemInstallQueue的pauseModelPush和resumeModelPush方法来修改mInstallQueueDisabledFlags,以此来控制集合内的任务是否执行

private void queuePendingShortcutInfo(PendingInstallShortcutInfo info) {
    final Exception stackTrace = new Exception();
    // Queue the item up for adding if launcher has not loaded properly yet
    MODEL_EXECUTOR.post(() -> {
        Pair<ItemInfo, Object> itemInfo = info.getItemInfo(mContext);
        if (TestProtocol.sDebugTracing) {
            Log.d(TestProtocol.MISSING_PROMISE_ICON, LOG + " queuePendingShortcutInfo"
                    + ", itemInfo=" + itemInfo);
        }
        if (itemInfo == null) {
            FileLog.d(LOG,
                    "Adding PendingInstallShortcutInfo with no attached info to queue.",
                    stackTrace);
        } else {
            FileLog.d(LOG,
                    "Adding PendingInstallShortcutInfo to queue. Attached info: "
                            + itemInfo.first,
                    stackTrace);
        }
        addToQueue(info);
    });
    flushInstallQueue();
}
public Pair<ItemInfo, Object> getItemInfo(Context context) {
    switch (itemType) {
        case ITEM_TYPE_APPLICATION: {
            String packageName = intent.getPackage();
            List<LauncherActivityInfo> laiList =
                    context.getSystemService(LauncherApps.class)
                            .getActivityList(packageName, user);
            final WorkspaceItemInfo si = new WorkspaceItemInfo();
            si.user = user;
            si.itemType = ITEM_TYPE_APPLICATION;
            LauncherActivityInfo lai;
            boolean usePackageIcon = laiList.isEmpty();
            if (usePackageIcon) {
                lai = null;
                si.intent = makeLaunchIntent(new ComponentName(packageName, ""))
                        .setPackage(packageName);
                si.status |= WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
            } else {
                lai = laiList.get(0);
                si.intent = makeLaunchIntent(lai);
            }
            LauncherAppState.getInstance(context).getIconCache()
                    .getTitleAndIcon(si, () -> lai, usePackageIcon, false);
            return Pair.create(si, null);
        }
        case ITEM_TYPE_DEEP_SHORTCUT: {
            //此处代码省略....
        }
        case ITEM_TYPE_APPWIDGET: {
            //此处代码省略....
        }
    }
    return null;
}

而执行任务的方法就是flushInstallQueue和flushQueueInBackground,在flushQueueInBackground中将任务转换成一个集合交给LauncherModel,然后清空队列和本地存储

private void flushInstallQueue() {
    if (mInstallQueueDisabledFlags != 0) {
        return;
    }
    MODEL_EXECUTOR.post(this::flushQueueInBackground);
}

@WorkerThread
private void flushQueueInBackground() {
    Launcher launcher = Launcher.ACTIVITY_TRACKER.getCreatedActivity();
    if (launcher == null) {
        // Launcher not loaded
        if (TestProtocol.sDebugTracing) {
            Log.d(TestProtocol.MISSING_PROMISE_ICON,
                    LOG + " flushQueueInBackground launcher not loaded");
        }
        return;
    }
    ensureQueueLoaded();
    if (mItems.isEmpty()) {
        if (TestProtocol.sDebugTracing) {
            Log.d(TestProtocol.MISSING_PROMISE_ICON,
                    LOG + " flushQueueInBackground no items to load");
        }
        return;
    }
    List<Pair<ItemInfo, Object>> installQueue = mItems.stream()
            .map(info -> info.getItemInfo(mContext))
            .collect(Collectors.toList());
    // Add the items and clear queue
    if (!installQueue.isEmpty()) {
        if (TestProtocol.sDebugTracing) {
            Log.d(TestProtocol.MISSING_PROMISE_ICON,
                    LOG + " flushQueueInBackground launcher addAndBindAddedWorkspaceItems");
        }
        // add log
        launcher.getModel().addAndBindAddedWorkspaceItems(installQueue);
    }
    mItems.clear();
    mStorage.getFile(mContext).delete();
}

回到LauncherModel中,创建了一个AddWorkspaceItemsTask任务交给队列执行,直接看它的execute方法

这里主要是4件事,先对要添加的item进行最终筛查,之后给通过筛查的item在桌面找一个合适的添加位置,然后保存到数据库,最后通知桌面进行更新

@Override
public void execute(@NonNull final LauncherAppState app, @NonNull finaBgDataModel dataModel,
        @NonNull final AllAppsList apps) {
    if (mItemList.isEmpty()) {
        return;
    }

    final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>();
    final IntArray addedWorkspaceScreensFinal = new IntArray();

    synchronized (dataModel) {
        IntArray workspaceScreens = dataModel.collectWorkspaceScreens();

        List<ItemInfo> filteredItems = new ArrayList<>();
        for (Pair<ItemInfo, Object> entry : mItemList) {
            ItemInfo item = entry.first;
            //这个判断只是为了加快轮询速度,对于图标已存在或者是系统应用的item直接跳过
            if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
                    item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
                //此处代码省略...
            }

            if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
                if (item instanceof WorkspaceItemFactory) {
                    item = ((WorkspaceItemFactory) item).makeWorkspaceItem(app.getContext());
                }
            }
            if (item != null) {
                filteredItems.add(item);
            }
        }

        InstallSessionHelper packageInstaller =
                InstallSessionHelper.INSTANCE.get(app.getContext());
        LauncherApps launcherApps = app.getContext().getSystemService(LauncherApps.class);

        for (ItemInfo item : filteredItems) {
            //mItemSpaceFinder给item找一个合适位置
            // Find appropriate space for the item.
            int[] coords = mItemSpaceFinder.findSpaceForItem(app, dataModel, workspaceScreens,
                    addedWorkspaceScreensFinal, item.spanX, item.spanY);
            int screenId = coords[0];

            ItemInfo itemInfo;
            if (item instanceof WorkspaceItemInfo || item instanceof FolderInfo ||
                    item instanceof LauncherAppWidgetInfo) {
                itemInfo = item;
            } else if (item instanceof WorkspaceItemFactory) {
                itemInfo = ((WorkspaceItemFactory) item).makeWorkspaceItem(app.getContext());
            } else {
                throw new RuntimeException("Unexpected info type");
            }

            if (item instanceof WorkspaceItemInfo && ((WorkspaceItemInfo) item).isPromise()) {
                //这里一大段都是处理PromiseApp的,省略代码...
            }

            // Add the shortcut to the db
            getModelWriter().addItemToDatabase(itemInfo,
                    LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId,
                    coords[1], coords[2]);

            // Save the WorkspaceItemInfo for binding in the workspace
            addedItemsFinal.add(itemInfo);

            // log bitmap and label
            FileLog.d(LOG, "Adding item info to workspace: " + itemInfo);
        }
    }

    if (!addedItemsFinal.isEmpty()) {
        scheduleCallbackTask(new CallbackTask() {
            @Override
            public void execute(@NonNull Callbacks callbacks) {
                final ArrayList<ItemInfo> addAnimated = new ArrayList<>();
                final ArrayList<ItemInfo> addNotAnimated = new ArrayList<>();
                if (!addedItemsFinal.isEmpty()) {
                    ItemInfo info = addedItemsFinal.get(addedItemsFinal.size() - 1);
                    int lastScreenId = info.screenId;
                    for (ItemInfo i : addedItemsFinal) {
                        if (i.screenId == lastScreenId) {
                            addAnimated.add(i);
                        } else {
                            addNotAnimated.add(i);
                        }
                    }
                }
                callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
                        addNotAnimated, addAnimated);
            }
        });
    }
}