Launcher3源码分析(番外篇)-InstallShortcutReceiver

我们知道在Launcher中,应用程序可以创建自己的快捷方式到桌面,那么是如何实现的呢?其实就是依赖于InstallShortcutReceiver这一个广播接收器,我们先看看Android应用程序怎么添加快捷方式到桌面。
注:我在代码中添加了中文注释的地方是比较关键的内容

首先需要在AndroidMainfest.xml中添加权限:

然后在代码中添加如下代码:

//需要在Androidmanifest中添加权限
Intent shortcutIntent = new Intent(this, MainActivity.class);
//Intent 的 action
Intent intent = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
//快捷方式图标
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, R.mipmap.ic_launcher);
//快捷方式名字
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "this is a shortcut");
//快捷方式 Intent,点击图标跳转的Intent
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
sendBroadcast(intent);

通过上述代码我们就在Launcher桌面创建了一个图标是:R.mipmap.ic_launcher,名字是:"this is a shortcut",点击之后跳转到本应用程序的MainActivity的快捷方式。

创建快捷方式的代码很简单,就是发送一个action为"com.android.launcher.action.INSTALL_SHORTCUT"的广播就行了,那么Launcher中如何处理这个广播并在桌面创建快捷方式的呢?且往下看。
首先我们在Launcher的AndroidManifest文件中找到了如下广播接收器:


        
            
                
            
        

我们可以看出,InstallShortcutReceiver这个广播接收器就是负责接收上面应用程序发出的创建桌面快捷方式的广播接收器。该广播接收器执行的具体逻辑我们看代码。我们都知道广播接收器的主要方法就是onReceiver()。

public void onReceive(Context context, Intent data) {
        //如果action不为ACTION_INSTALL_SHORTCUT就说明接收到的不是我们期望的广播就不向下执行
        //ACTION_INSTALL_SHORTCUT就是"com.android.launcher.action.INSTALL_SHORTCUT"
        if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) {
            return;
        }
        //使用获取到的Intent对象来创建PendingInstallShortcutInfo 对象
        PendingInstallShortcutInfo info = createPendingInfo(context, data);
        if (info != null) {
            if (!info.isLauncherActivity()) {
                // Since its a custom shortcut, verify that it is safe to launch.
                if (!PackageManagerHelper.hasPermissionForActivity(
                        context, info.launchIntent, null)) {
                    // Target cannot be launched, or requires some special permission to launch
                    Log.e(TAG, "Ignoring malicious intent " + info.launchIntent.toUri(0));
                    return;
                }
            }
            queuePendingShortcutInfo(info, context);
        }
    }

在onReceive方法中,我们通过传递进来的Intent,创建了PendingInstallShortcutInfo 对象,并调用了方法queuePendingShortcutInfo,那么这个方法是干嘛的呢?

private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) {
        // Queue the item up for adding if launcher has not loaded properly yet
        //Launcher中的一个单例类,存储了很多和Launcher相关的实体类对象
        LauncherAppState app = LauncherAppState.getInstance();
        //callback是一个LauncherModel中的一个接口,Launcher 类会实现该接口,
        //如果Launcher已经启动了,那么getCallback()方法返回Launcher对象,否则返回null
        boolean launcherNotLoaded = app.getModel().getCallback() == null;
        //添加info对象到Install队列中
        addToInstallQueue(Utilities.getPrefs(context), info);
        if (!mUseInstallQueue && !launcherNotLoaded) {
            flushInstallQueue(context);
        }
    }

我们看到上面方法中有两个变量:mUseInstallQueue 和 launcherNotLoaded。

mUseInstallQueue 的定义是:
// Determines whether to defer installing shortcuts immediately until
// processAllPendingInstalls() is called.
private static boolean mUseInstallQueue = false;

我们可以理解为:如果mUseInstallQueue 为true,我们就只添加PendingInstallShortcutInfo 对象到InstallQueue队列中,并不做别的操作;如果mUseInstallQueue 为false,再结合launcherNotLoaded的值决定执行什么操作。如果launcherNotLoaded为false,也就是说Launcher已经启动了,那么就执行方法flushInstallQueue;否则什么都不做。
上面方法中又出现了两个方法:addToInstallQueue 和 flushInstallQueue。还是直接看代码:

    /**
     * 添加 info 到 sharedPreference
     */
    private static void addToInstallQueue(
            SharedPreferences sharedPrefs, PendingInstallShortcutInfo info) {
        synchronized(sLock) {
            String encoded = info.encodeToString();
            if (encoded != null) {
                Set strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
                if (strings == null) {
                    strings = new HashSet(1);
                } else {
                    strings = new HashSet(strings);
                }
                strings.add(encoded);
                sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
            }
        }
    }


    /**
     * 从 sp 中获取 PendingInstallShortcutInfo 列表并添加到桌面
     */
    static void flushInstallQueue(Context context) {
        SharedPreferences sp = Utilities.getPrefs(context);
        //从sp 中获取InstallQueue并清空sp中的InstallQueue
        ArrayList installQueue = getAndClearInstallQueue(sp, context);
        if (!installQueue.isEmpty()) {
            Iterator iter = installQueue.iterator();
            ArrayList addShortcuts = new ArrayList();
            while (iter.hasNext()) {
                final PendingInstallShortcutInfo pendingInfo = iter.next();

                // If the intent specifies a package, make sure the package exists
                String packageName = pendingInfo.getTargetPackage();
                if (!TextUtils.isEmpty(packageName)) {
                    UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
                    if (!LauncherModel.isValidPackage(context, packageName, myUserHandle)) {
                        if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: "
                                + pendingInfo.launchIntent);
                        continue;
                    }
                }

                // Generate a shortcut info to add into the model
                addShortcuts.add(pendingInfo.getShortcutInfo());
            }

            // Add the new apps to the model and bind them
            //创建快捷方式到桌面
            if (!addShortcuts.isEmpty()) {
                LauncherAppState app = LauncherAppState.getInstance();
                app.getModel().addAndBindAddedWorkspaceItems(context, addShortcuts);
            }
        }
    }

    /**
     * 获取 sp 中存储的PendingInstallShortcutInfo,并清空 sp
     */
    private static ArrayList getAndClearInstallQueue(
            SharedPreferences sharedPrefs, Context context) {
        synchronized(sLock) {
            Set strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
            if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
            if (strings == null) {
                return new ArrayList();
            }
            ArrayList infos =
                new ArrayList();
            for (String encoded : strings) {
                //解析String为PendingInstallShortcutInfo 对象
                PendingInstallShortcutInfo info = decode(encoded, context);
                if (info != null) {
                    infos.add(info);
                }
            }
            sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, new HashSet()).apply();
            return infos;
        }
    }

通过上面的代码,我们看出来了其实InstallQueue就是一个存储在SharedPreference的String的集合。在flushInstallQueue方法中,获取了该集合,并使用getAndClearInstallQueue方法把集合中的String解析为PendingInstallShortcutInfo对象。再使用创建的PendingInstallShortcutInfo集合,依次获取其中的对象并创建ShortcutInfo对象,ShortcutInfo对象就是我们桌面上显示的快捷方式对象。

LauncherAppState app = LauncherAppState.getInstance();
app.getModel().addAndBindAddedWorkspaceItems(context, addShortcuts);

最后再使用LauncherModel的addAndBindAddedWorkspaceItems方法就把图标添加到了桌面上。
接下来的的核心内容就是LauncherModel了,我们分析一下addAndBindAddedWorkspaceItems方法。

/**
     * Adds the provided items to the workspace.
     * 添加 items 到屏幕
     */
    public void addAndBindAddedWorkspaceItems(final Context context,
            final ArrayList workspaceApps) {
        final Callbacks callbacks = getCallback();
        if (workspaceApps.isEmpty()) {
            return;
        }
        // Process the newly added applications and add them to the database first
        Runnable r = new Runnable() {
            public void run() {
                final ArrayList addedShortcutsFinal = new ArrayList();
                final ArrayList addedWorkspaceScreensFinal = new ArrayList();

                // Get the list of workspace screens.  We need to append to this list and
                // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
                // called.
                //首先从数据库加载出 workspace screen,也即是加载出当前Launcher的屏幕集合
                ArrayList workspaceScreens = loadWorkspaceScreensDb(context);
                synchronized(sBgLock) {
                    for (ItemInfo item : workspaceApps) {
                        if (item instanceof ShortcutInfo) {
                            // Short-circuit this logic if the icon exists somewhere on the workspace
                            // 该 shortcutInfo 已经存在在桌面就不再创建快捷方式
                            if (shortcutExists(context, item.getIntent(), item.user)) {
                                continue;
                            }
                        }

                        // Find appropriate space for the item.
                        // 为 item 查找一个合适的地方, 即在桌面上寻找一个有空闲位置的屏幕,并返回空闲位置的坐标或者新建一屏返回最左上角的坐标
                        // 返回的 Long 为屏幕序号,int[]为长度是2的数组,int[0]是在屏幕上的x位置,int[1]是在屏幕上的Y位置
                        Pair coords = findSpaceForItem(context,
                                workspaceScreens, addedWorkspaceScreensFinal, 1, 1);
                        long screenId = coords.first;
                        int[] cordinates = coords.second;

                        //添加到桌面的 Item 类型只能是 Shortcut 或者 Folder
                        ItemInfo itemInfo;
                        if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
                            itemInfo = item;
                        } else if (item instanceof AppInfo) {
                            itemInfo = ((AppInfo) item).makeShortcut();
                        } else {
                            throw new RuntimeException("Unexpected info type");
                        }

                        // Add the shortcut to the db
                        // 添加 shortcut 到数据库
                        addItemToDatabase(context, itemInfo,
                                LauncherSettings.Favorites.CONTAINER_DESKTOP,
                                screenId, cordinates[0], cordinates[1]);
                        // Save the ShortcutInfo for binding in the workspace
                        addedShortcutsFinal.add(itemInfo);
                    }
                }

                // Update the workspace screens
                //上面寻找Item位置的时候有可能新建了一屏,所以需要对workspaceScreens重新排序
                updateWorkspaceScreenOrder(context, workspaceScreens);

                if (!addedShortcutsFinal.isEmpty()) {
                    runOnMainThread(new Runnable() {
                        public void run() {
                            Callbacks cb = getCallback();
                            if (callbacks == cb && cb != null) {
                                final ArrayList addAnimated = new ArrayList();
                                final ArrayList addNotAnimated = new ArrayList();
                                if (!addedShortcutsFinal.isEmpty()) {
                                    //获取到列表中最后一个Item的screenId
                                    ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1);
                                    long lastScreenId = info.screenId;
                                    for (ItemInfo i : addedShortcutsFinal) {
                                        if (i.screenId == lastScreenId) {
                                            addAnimated.add(i);
                                        } else {
                                            addNotAnimated.add(i);
                                        }
                                    }
                                }
                                //回调 bindAppsAdded 方法去添加Item
                                callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
                                        addNotAnimated, addAnimated, null);
                            }
                        }
                    });
                }
            }
        };
        runOnWorkerThread(r);
    }

在上面的方法中,我们主要是在桌面上寻找能容纳下该Item 的位置,然后添加Item到该位置。那么寻找空闲位置的方法如下:

/**
     * Find a position on the screen for the given size or adds a new screen.
     * 根据给定的大小在屏幕上找一个位置或者新建一屏
     * @return screenId and the coordinates for the item.
     */
    @Thunk Pair findSpaceForItem(
            Context context,
            ArrayList workspaceScreens,
            ArrayList addedWorkspaceScreensFinal,
            int spanX, int spanY) {
        LongSparseArray> screenItems = new LongSparseArray<>();

        // Use sBgItemsIdMap as all the items are already loaded.
        assertWorkspaceLoaded();
        synchronized (sBgLock) {
            for (ItemInfo info : sBgItemsIdMap) {
                 //获取已经添加到桌面的Item
                if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
                    ArrayList items = screenItems.get(info.screenId);
                    if (items == null) {
                        items = new ArrayList<>();
                        screenItems.put(info.screenId, items);
                    }
                    items.add(info);
                }
            }
        }

        // Find appropriate space for the item.
        long screenId = 0;
        int[] cordinates = new int[2];
        boolean found = false;

        int screenCount = workspaceScreens.size();
        // First check the preferred screen.
        // 在首选屏幕上寻找空间,这里的首选屏幕默认是主屏的右边一屏
        int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
        if (preferredScreenIndex < screenCount) { //如果首选屏幕存在,那么就在该屏寻找空位
            screenId = workspaceScreens.get(preferredScreenIndex);
            //findNextAvailableIconSpaceInScreen 方法是在某一屏寻找空位,cordinates为存储空位坐标的数组
            found = findNextAvailableIconSpaceInScreen(
                    screenItems.get(screenId), cordinates, spanX, spanY);
        }

        //如果没有在首选屏幕找到空位
        if (!found) {
            // Search on any of the screens starting from the first screen.
            // 在首选屏之外的屏幕寻找空间
            for (int screen = 1; screen < screenCount; screen++) {
                screenId = workspaceScreens.get(screen);
                if (findNextAvailableIconSpaceInScreen(
                        screenItems.get(screenId), cordinates, spanX, spanY)) {
                    // We found a space for it
                    found = true;
                    break;
                }
            }
        }

        //如果在所有屏幕都没找到空余空间就创建一屏
        if (!found) {
            // Still no position found. Add a new screen to the end.
            // 还是没有找到空间,就新创建一屏
            screenId = LauncherSettings.Settings.call(context.getContentResolver(),
                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
                    .getLong(LauncherSettings.Settings.EXTRA_VALUE);

            // Save the screen id for binding in the workspace
            workspaceScreens.add(screenId);
            addedWorkspaceScreensFinal.add(screenId);

            //如果在新建的一屏上都找不到空余空间,那么上帝来帮助我们吧,google程序猿也是很Funny啊
            // If we still can't find an empty space, then God help us all!!!
            if (!findNextAvailableIconSpaceInScreen(
                    screenItems.get(screenId), cordinates, spanX, spanY)) {
                throw new RuntimeException("Can't find space to add the item");
            }
        }
        return Pair.create(screenId, cordinates);
    }


/**
     * 在屏幕上找到可用的空白区域
     * @return
     */
    private static boolean findNextAvailableIconSpaceInScreen(ArrayList occupiedPos,
            int[] xy, int spanX, int spanY) {
        LauncherAppState app = LauncherAppState.getInstance();
        InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
        //根据传递的行数、列数创建一个GridOccupancy 对象,该对象就是用于标记屏幕上占位的
        GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows);
        if (occupiedPos != null) {
            for (ItemInfo r : occupiedPos) {
                occupied.markCells(r, true);
            }
        }
        return occupied.findVacantCell(xy, spanX, spanY);
    }

上面已经找到了Item的放置的位置,而且也添加Item到数据库中了,那么接下来就是要显示到桌面了,我们看到方法addAndBindAddedWorkspaceItems最后回调了callbacks.bindAppsAdded,文章最开始就说过了callback是一个接口,在Launcher3中Launcher类实现了该接口,所以我们找到Launcher中bindAppsAdded方法的实现:

/**
     * 绑定新增的 app
     */
    public void bindAppsAdded(final ArrayList newScreens,
                              final ArrayList addNotAnimated,
                              final ArrayList addAnimated,
                              final ArrayList addedApps) {
        Runnable r = new Runnable() {
            public void run() {
                bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps);
            }
        };
        if (waitUntilResume(r)) {
            return;
        }
        
        // Add the new screens
        // 绑定新增的屏
        if (newScreens != null) {
            bindAddScreens(newScreens);
        }

        // We add the items without animation on non-visible pages, and with
        // animations on the new page (which we will try and snap to).
        // 在当前不可见的屏添items无动画,在当前可见的屏添加Items带动画的
        if (addNotAnimated != null && !addNotAnimated.isEmpty()) {
            bindItems(addNotAnimated, 0,
                    addNotAnimated.size(), false);
        }
        if (addAnimated != null && !addAnimated.isEmpty()) {
            bindItems(addAnimated, 0,
                    addAnimated.size(), true);
        }

        // Remove the extra empty screen
        // 删除额外的空屏
        mWorkspace.removeExtraEmptyScreen(false, false);

        //最后添加新增的 items 到AllAppsView中去
        if (addedApps != null && mAppsView != null) {
            mAppsView.addApps(addedApps);
        }
    }

在上面的方法中主要做了三件事:

1、在桌面添加新增的屏workspaceScreen;
2、在桌面添加新增的Items, 在当前不可见的屏添items无动画,在当前可见的屏添加Items带动画的
3、在AllAppsView添加新增的Items

在桌面添加新增的屏,主要是在workspace中添加一个新的CellLayout。而在AllAppsView中添加新增的Items主要就是更新原有的AllApps列表,并在更新Allapps的查询控制类。所以我们主要关注第二点,在桌面添加新增的Items。

/**
     * Bind the items start-end from the list.
     * 从 list 中取出 start 位置到 end 位置的 items 绑定到桌面
     *
     * Implementation of the method from LauncherModel.Callbacks.
     */
    @Override
    public void bindItems(final ArrayList shortcuts, final int start, final int end,
                          final boolean forceAnimateIcons) {
        Runnable r = new Runnable() {
            public void run() {
                bindItems(shortcuts, start, end, forceAnimateIcons);
            }
        };
        if (waitUntilResume(r)) {
            return;
        }

        // Get the list of added shortcuts and intersect them with the set of shortcuts here
        final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
        final Collection bounceAnims = new ArrayList();
        final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
        Workspace workspace = mWorkspace;
        long newShortcutsScreenId = -1;
        for (int i = start; i < end; i++) {
            final ItemInfo item = shortcuts.get(i);

            // Short circuit if we are loading dock items for a configuration which has no dock
            // 如果绑定的是 hotseat 的 item, 但是 hotseat 又不存在就跳出该次循环
            if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
                    mHotseat == null) {
                continue;
            }

            final View view;
            switch (item.itemType) {
                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
                    //如果Item是 shortcut类型的,就创建Shortcut的View
                    ShortcutInfo info = (ShortcutInfo) item;
                    view = createShortcut(info);
                    break;
                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                    //如果是Folder类型的就创建Folder的View
                    view = FolderIcon.fromXml(R.layout.folder_icon, this,
                            (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
                            (FolderInfo) item, mIconCache);
                    break;
                default:
                    throw new RuntimeException("Invalid Item Type");
            }

             /*
             * Remove colliding items.
             * 如果指定位置被占用了,那么就抛出异常或者移除之前的占用的item
             */
            if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
                CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
                if (cl != null && cl.isOccupied(item.cellX, item.cellY)) { //指定位置被占用了
                    View v = cl.getChildAt(item.cellX, item.cellY);
                    Object tag = v.getTag();
                    String desc = "Collision while binding workspace item: " + item
                            + ". Collides with " + tag;
                    if (ProviderConfig.IS_DOGFOOD_BUILD) {
                        throw (new RuntimeException(desc));
                    } else {
                        Log.d(TAG, desc);
                        LauncherModel.deleteItemFromDatabase(this, item);
                        continue;
                    }
                }
            }
            //在workspace指定屏幕指定位置添加view
            workspace.addInScreenFromBind(view, item.container, item.screenId, item.cellX,
                    item.cellY, 1, 1);
            //下面就是动画相关的内容
            if (animateIcons) {
                // Animate all the applications up now
                view.setAlpha(0f);
                view.setScaleX(0f);
                view.setScaleY(0f);
                bounceAnims.add(createNewAppBounceAnimation(view, i));
                newShortcutsScreenId = item.screenId;
            }
        }

        if (animateIcons) {
            // Animate to the correct page
            if (newShortcutsScreenId > -1) {
                long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
                final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
                final Runnable startBounceAnimRunnable = new Runnable() {
                    public void run() {
                        anim.playTogether(bounceAnims);
                        anim.start();
                    }
                };
                if (newShortcutsScreenId != currentScreenId) {
                    // We post the animation slightly delayed to prevent slowdowns
                    // when we are loading right after we return to launcher.
                    mWorkspace.postDelayed(new Runnable() {
                        public void run() {
                            if (mWorkspace != null) {
                                mWorkspace.snapToPage(newScreenIndex);
                                mWorkspace.postDelayed(startBounceAnimRunnable,
                                        NEW_APPS_ANIMATION_DELAY);
                            }
                        }
                    }, NEW_APPS_PAGE_MOVE_DELAY);
                } else {
                    mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
                }
            }
        }
        workspace.requestLayout();
    }

上面代码中比较关键的是workspace.addInScreenFromBind方法,即在workspace的指定屏幕,指定位置添加shortcut或者folder的view。而addInScreenFromBind方法又是调用的addInScreen方法。

/**
     * Adds the specified child in the specified screen. The position and dimension of
     * the child are defined by x, y, spanX and spanY.
     *
     * @param child The child to add in one of the workspace's screens.
     * @param screenId The screen in which to add the child.
     * @param x The X position of the child in the screen's grid.
     * @param y The Y position of the child in the screen's grid.
     * @param spanX The number of cells spanned horizontally by the child.
     * @param spanY The number of cells spanned vertically by the child.
     * @param insert When true, the child is inserted at the beginning of the children list.
     * @param computeXYFromRank When true, we use the rank (stored in screenId) to compute
     *                          the x and y position in which to place hotseat items. Otherwise
     *                          we use the x and y position to compute the rank.
     *
     * 在指定屏幕上添加 item
     */
    void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
            boolean insert, boolean computeXYFromRank) {
        //是在桌面添加view
        if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
            if (getScreenWithId(screenId) == null) {
                Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
                // DEBUGGING - Print out the stack trace to see where we are adding from
                new Throwable().printStackTrace();
                return;
            }
        }
        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
            // This should never happen
            throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
        }

        final CellLayout layout;
        // hotseat 中添加 view
        if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 
            layout = mLauncher.getHotseat().getLayout();
            child.setOnKeyListener(new HotseatIconKeyEventListener());

            // Hide folder title in the hotseat
            if (child instanceof FolderIcon) {
                ((FolderIcon) child).setTextVisible(false);
            }

            if (computeXYFromRank) {
                x = mLauncher.getHotseat().getCellXFromOrder((int) screenId);
                y = mLauncher.getHotseat().getCellYFromOrder((int) screenId);
            } else {
                screenId = mLauncher.getHotseat().getOrderInHotseat(x, y);
            }
        } else {
            // Show folder title if not in the hotseat
            if (child instanceof FolderIcon) {
                ((FolderIcon) child).setTextVisible(true);
            }
            layout = getScreenWithId(screenId);
            child.setOnKeyListener(new IconKeyEventListener());
        }

        ViewGroup.LayoutParams genericLp = child.getLayoutParams();
        CellLayout.LayoutParams lp;
        if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
        } else {
            lp = (CellLayout.LayoutParams) genericLp;
            lp.cellX = x;
            lp.cellY = y;
            lp.cellHSpan = spanX;
            lp.cellVSpan = spanY;
        }

        if (spanX < 0 && spanY < 0) {
            lp.isLockedToGrid = false;
        }

        // Get the canonical child id to uniquely represent this view in this screen
        ItemInfo info = (ItemInfo) child.getTag();
        int childId = mLauncher.getViewIdForItem(info);

        boolean markCellsAsOccupied = !(child instanceof Folder);
        //添加view到cellLayout中
        if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
            // TODO: This branch occurs when the workspace is adding views
            // outside of the defined grid
            // maybe we should be deleting these items from the LauncherModel?
            Log.e(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
        }

        if (!(child instanceof Folder)) {
            child.setHapticFeedbackEnabled(false);
            child.setOnLongClickListener(mLongClickListener);
        }
        if (child instanceof DropTarget) {
            mDragController.addDropTarget((DropTarget) child);
        }
    }

通过上面代码可以看出其实不管是在桌面或者是在hotseat中添加view,其实都是调用的桌面或者hotseat中的CellLayout对象来添加的view。通过分析CellLayout的方法而在CellLayout中添加View其实是在CellLayout中的ShortcutsAndWidgets中添加的View。而ShortcutsAndWidgets就是一个ViewGroup,所以最后还是调用的ViewGroup的AddView来添加View的。

/**
     * 在 cellLayout 中添加 view
     * @param child 子 view
     * @param index
     * @param childId
     * @param params
     * @param markCells
     * @return
     */
    public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
            boolean markCells) {
        final LayoutParams lp = params;

        // Hotseat icons - remove text
        if (child instanceof BubbleTextView) {
            BubbleTextView bubbleChild = (BubbleTextView) child;
            bubbleChild.setTextVisibility(!mIsHotseat);
        }

        child.setScaleX(getChildrenScale());
        child.setScaleY(getChildrenScale());

        // Generate an id for each view, this assumes we have at most 256x256 cells
        // per workspace screen
        if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
            // If the horizontal or vertical span is set to -1, it is taken to
            // mean that it spans the extent of the CellLayout
            if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
            if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;

            child.setId(childId);
            if (LOGD) {
                Log.d(TAG, "Adding view to ShortcutsAndWidgetsContainer: " + child);
            }
            //在ShortcutsAndWidgets中添加VIew
            mShortcutsAndWidgets.addView(child, index, lp);

            if (markCells) markCellsAsOccupiedForView(child);

            return true;
        }
        return false;
    }

最后,我们就添加了快捷方式到workspace中。
因为是Launcher源码,所以代码量比较大,所以更多的分析和讲解都是根据代码来的,在代码中关键位置添加注释来阐述的。本文是个人学习记录,如有问题请指正。

你可能感兴趣的:(Launcher3源码分析(番外篇)-InstallShortcutReceiver)