Android 4.0 Launcher2源码分析——Launcher内容加载详细过程

本文来自http://blog.csdn.net/chenshaoyang0011 转载请申明文章出处!

文中如有纰漏之处,望不吝指教~~~欢迎讨论,共同学习~~~

Launcher在应用启动的时候,需要加载AppWidget,shortcut等内容项,通过调用LauncherModel.startLoader(),开始加载的工作。launcherModel中加载好的内容会通过

LauncherModel.Callbacks接口的回调函数将数据传给需要的组件,那先来看看Callbacks的定义:

public interface Callbacks {
        public boolean setLoadOnResume();
        public int getCurrentWorkspaceScreen();
        public void startBinding();
        public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end);
        public void bindFolders(HashMap<Long,FolderInfo> folders);
        public void finishBindingItems();
        public void bindAppWidget(LauncherAppWidgetInfo info);
        public void bindAllApplications(ArrayList<ApplicationInfo> apps);
        public void bindAppsAdded(ArrayList<ApplicationInfo> apps);
        public void bindAppsUpdated(ArrayList<ApplicationInfo> apps);
        public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent);
        public void bindPackagesUpdated();
        public boolean isAllAppsVisible();
        public void bindSearchablesChanged();
    }
简单的了解下每个方法的用途:

setLoadOnResume()     由于Launcher继承自Activity,因此Launcher可能会处于paused状态(onPause()被调用),则有可能在这段时间内资源可能

发生了改变,如应用被删除或新应用安装,因此需要在onResume()中调用此方法进行重新加载。

getCurrentWorkspace()    获取当前屏幕的序号

startBinding()     通知Launcher加载开始,并更新Workspace上的shortcuts

bindItems(ArrayList<ItemInfo> shortcuts, int start, int end)     加载一批内容项到Workspace,加载的内容项包括,Application、shortcut、folder。

bindFolders(HashMap<Long, FolderInfo> folders)    加载folder的内容

finishBindingItems()    通知Launcher加载结束。

bindAppWidget(LauncherAppWidgetInfo item)    加载AppWidget到Workspace

bindAllApplications(final ArrayList<ApplicationInfo> apps)   在All Apps页加载所有应用的Icon

bindAppsAdded(ArrayList<ApplicationInfo> apps)   通知Launcher一个新的应用被安装,并加载这个应用

bindAppsUpdated(ArrayList<ApplicationInfo> apps)  通知Launcher一个应用发生了更新

bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent)    通知Launcher一个应用被删除了

bindPackagesUpdated()   通知Launcher多个应用发生了更新

isAllAppsVisible()用于在加载的过程中记录当前Launcher的状态,返回true则当前显示的All Apps

bindSearchablesChanged()当搜索/删除框状态发生改变时调用

了解了每个方法的作用之后,就可以开始进一步的分析了。

首先让我们回顾一下整个加载过程的流程是怎样的


通过在Launcher中调用LauncherModel.startLoader()方法,开始加载内容。

public void startLoader(Context context, boolean isLaunching) {
        synchronized (mLock) {
            ......
            // Don't bother to start the thread if we know it's not going to do anything
            if (mCallbacks != null && mCallbacks.get() != null) {
                ......
                mLoaderTask = new LoaderTask(context, isLaunching);
                sWorkerThread.setPriority(Thread.NORM_PRIORITY);
                sWorker.post(mLoaderTask);
            }
        }
    }
mLoaderTask是一个Runnable,被添加到消息队列之后,它的run() 方法会被调用。
public void run() {
            ......
            keep_running: {
                ......
                if (loadWorkspaceFirst) {
                    ......
                    loadAndBindWorkspace();
                } else {
                    ......
                }

                if (mStopped) {
                    break keep_running;
                }

                ......
                waitForIdle();

                // second step
                if (loadWorkspaceFirst) {
                    ......
                    loadAndBindAllApps();
                } else {
                    ......
                }
                ......
            }
            ......
        }

加载的工作由两部分组成,第一部分是为Workspace加载内容第二部分则是为AllApps加载内容。每一部分的加载又可以分为两个步骤:1、由LauncherModel完成,主要

工作是从数据库中读取信息,并且按类别将内容项分装到不同的据结构中。2、由Launcher来完成,通过LauncherModel.Callbacks接口定义的回调方法,从LauncherModel

中获取的数据,将其显示到桌面

一、Workspace内容加载

run()中首先会调用loadAndBindWorkspace()方法开始Workspace的加载工作。

private void loadAndBindWorkspace() {
            ...
            if (!mWorkspaceLoaded) {
                loadWorkspace();
                synchronized (LoaderTask.this) {
                    if (mStopped) {
                        return;
                    }
                    mWorkspaceLoaded = true;
                }
            }

            // Bind the workspace
            bindWorkspace();
        }

因为WorkspaceLoaded=false,所以会调用loadWorkspace()读取内容数据,等数据读取完毕之后,再调用bindWorkspace()将数据

加载到Workspace中。

private void loadWorkspace() {
            ......

            //存放container为CONTAINER_DESKTOP和CONTAINER_HOTSEAT类型的item
            sWorkspaceItems.clear();
            
            //存放所有的AppWidget类型
            sAppWidgets.clear();

            //存放的FolderInfo.id和FolderInfo组成的映射对
            sFolders.clear();

            //所有的item的id和ItemInfo组成的映射对
            sItemsIdMap.clear();
            sDbIconCache.clear();

            final ArrayList<Long> itemsToRemove = new ArrayList<Long>();

            final Cursor c = contentResolver.query(
                    LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);

            // +1 for the hotseat (it can be larger than the workspace)
            // Load workspace in reverse order to ensure that latest items are loaded first (and
            // before any earlier duplicates)
            //代表屏幕中的每一个单位的方格是否被占用。
            //第一维表示分屏的序号,其中最后一个代表Hotseat
            //第二维表示x方向方格的序号
            //第三维表示y方向方格的序号
            final ItemInfo occupied[][][] =
                    new ItemInfo[Launcher.SCREEN_COUNT + 1][mCellCountX + 1][mCellCountY + 1];

            try {
                ......

                while (!mStopped && c.moveToNext()) {
                    try {
                        int itemType = c.getInt(itemTypeIndex);

                        switch (itemType) {
                        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                            intentDescription = c.getString(intentIndex);
                            try {
                                intent = Intent.parseUri(intentDescription, 0);
                            } catch (URISyntaxException e) {
                                continue;
                            }

                            if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
                                info = getShortcutInfo(manager, intent, context, c, iconIndex,
                                        titleIndex, mLabelCache);
                            } else {
                                info = getShortcutInfo(c, context, iconTypeIndex,
                                        iconPackageIndex, iconResourceIndex, iconIndex,
                                        titleIndex);
                            }

                            if (info != null) {
                                ......

                                // check & update map of what's occupied
                                //检查这个item所占的空间是否空闲,true表示空闲
                                if (!checkItemPlacement(occupied, info)) {
                                    break;
                                }

                                switch (container) {
                                case LauncherSettings.Favorites.CONTAINER_DESKTOP:
                                case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
                                    //当加载的item类型为ITEM_TYPE_APPLICATION或者ITEM_TYPE_SHORTCUT
                                    //并且所属的container为CONTAINER_DESKTOP或者CONTAINER_HOTSEAT时
                                    //将其添加到sWorkspaceItems中
                                    sWorkspaceItems.add(info);
                                    break;
                                default:
                                    // Item is in a user folder
                                    //如果item的container不是上述两者,则代表它处于一个folder中
                                    //将其添加到所属的folderInfo中
                                    FolderInfo folderInfo =
                                            findOrMakeFolder(sFolders, container);
                                    folderInfo.add(info);
                                    break;
                                }
                                //所有的ITEM_TYPE_APPLICATION和ITEM_TYPE_SHORTCUT类型的item都需要
                                //加入到sItemsIdMap的映射对中。
                                sItemsIdMap.put(info.id, info);

                                // now that we've loaded everthing re-save it with the
                                // icon in case it disappears somehow.
                                queueIconToBeChecked(sDbIconCache, info, c, iconIndex);
                            } else {
                                // Failed to load the shortcut, probably because the
                                // activity manager couldn't resolve it (maybe the app
                                // was uninstalled), or the db row was somehow screwed up.
                                // Delete it.
                                id = c.getLong(idIndex);
                                Log.e(TAG, "Error loading shortcut " + id + ", removing it");
                                contentResolver.delete(LauncherSettings.Favorites.getContentUri(
                                            id, false), null, null);
                            }
                            break;

                        case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                            id = c.getLong(idIndex);
                            FolderInfo folderInfo = findOrMakeFolder(sFolders, id);
                            .....
                            // check & update map of what's occupied
                            if (!checkItemPlacement(occupied, folderInfo)) {
                                break;
                            }
                            switch (container) {
                                case LauncherSettings.Favorites.CONTAINER_DESKTOP:
                                case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
                                    //folderInfo类型的item也需要添加到sWorkspaceItems中
                                    sWorkspaceItems.add(folderInfo);
                                    break;
                            }
                            //添加到sItemsIdMap映射对中
                            sItemsIdMap.put(folderInfo.id, folderInfo);
                            //添加到sFolder映射对中
                            sFolders.put(folderInfo.id, folderInfo);
                            break;

                        case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                            // Read all Launcher-specific widget details
                            int appWidgetId = c.getInt(appWidgetIdIndex);
                            id = c.getLong(idIndex);

                            final AppWidgetProviderInfo provider =
                                    widgets.getAppWidgetInfo(appWidgetId);

                            if (!isSafeMode && (provider == null || provider.provider == null ||
                                    provider.provider.getPackageName() == null)) {
                                ......
                                itemsToRemove.add(id);
                            } else {
                                appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId);
                                ......

                                container = c.getInt(containerIndex);
                                ......
                                appWidgetInfo.container = c.getInt(containerIndex);

                                // check & update map of what's occupied
                                if (!checkItemPlacement(occupied, appWidgetInfo)) {
                                    break;
                                }
                                //添加到sItemsIdMap映射对
                                sItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
                                //添加到sAppWidgets
                                sAppWidgets.add(appWidgetInfo);
                            }
                            break;
                        }
                    } catch (Exception e) {
                        ......
                    }
                }
            } finally {
                c.close();
            }

            ......
        }

loadWorkspace的工作就是从ContentProvider获取指定URI中的数据,并将它们分类存放到指定的数据结构中。分类的标准有两条:1、item的类型。包括ITEM_TYPE_APPLICATION  ,ITEM_TYPE_SHORTCUT  ,ITEM_TYPE_FOLDER,ITEM_TYPE_APPWIDGET四类。2、item所属的容器。包括CONTAINER_DESKTOP,

CONTAINER_HOTSEAT以及其它(主要指文件夹)。LauncherModel在读取完数据之后,通过LauncherModel.bindWorkspace()将数据传给到Launcher。进入LauncherModel.bindWorkspace()中:

 private void bindWorkspace() {
            ......
            mHandler.post(new Runnable() {
                public void run() {
                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        //开始绑定
                        callbacks.startBinding();
                    }
                }
            });

            ......
            for (int i=0; i<N; i+=ITEMS_CHUNK) {
                final int start = i;
                final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
                mHandler.post(new Runnable() {
                    public void run() {
                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                        if (callbacks != null) {
                            //绑定application、shortcut、folder三种内容
                            callbacks.bindItems(workspaceItems, start, start+chunkSize);
                        }
                    }
                });
            }
            ......
            mHandler.post(new Runnable() {
                public void run() {
                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        //绑定folder
                        callbacks.bindFolders(folders);
                    }
                }
            });
            ......
            for (int i=0; i<N; i++) {
                final LauncherAppWidgetInfo widget = sAppWidgets.get(i);
                if (widget.screen == currentScreen) {
                    mHandler.post(new Runnable() {
                        public void run() {
                            Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                            if (callbacks != null) {
                                //绑定当前屏的AppWidget
                                callbacks.bindAppWidget(widget);
                            }
                        }
                    });
                }
            }
           
            for (int i=0; i<N; i++) {
                final LauncherAppWidgetInfo widget = sAppWidgets.get(i);
                if (widget.screen != currentScreen) {
                    mHandler.post(new Runnable() {
                        public void run() {
                            Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                            if (callbacks != null) {
                                //绑定其它屏的AppWidget
                                callbacks.bindAppWidget(widget);
                            }
                        }
                    });
                }
            }
           
            mHandler.post(new Runnable() {
                public void run() {
                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        //结束绑定
                        callbacks.finishBindingItems();
                    }
                }
            });
            ......
        }

可以看到,Launcher的内容绑定分为五步:分别对应着startBinding()、bindItems()、bindFolders()、bindAppWidgets()、finishBindingItems()的调用

Step1:调用Callbacks.startBinding()

由于Launcher实现了Callbacks接口,Launcher中的startBinding()被调用,进入Launcher.startBinding();

    /**
     * Refreshes the shortcuts shown on the workspace.
     *
     * Implementation of the method from LauncherModel.Callbacks.
     */
    public void startBinding() {
        final Workspace workspace = mWorkspace;

        mWorkspace.clearDropTargets();
        int count = workspace.getChildCount();
        for (int i = 0; i < count; i++) {
            // Use removeAllViewsInLayout() to avoid an extra requestLayout() and invalidate().
            final CellLayout layoutParent = (CellLayout) workspace.getChildAt(i);
            layoutParent.removeAllViewsInLayout();
        }
        if (mHotseat != null) {
            mHotseat.resetLayout();
        }
    }

从方法中的内容我们可以看到,当被通知开始加载Workspace中内容时,Launcher重置了Workspace中的内容,Hotseat也通过resetLayout方法进行重置。

    void resetLayout() {
        mContent.removeAllViewsInLayout();

        // Add the Apps button
        Context context = getContext();
        LayoutInflater inflater = LayoutInflater.from(context);
        BubbleTextView allAppsButton = (BubbleTextView)
                inflater.inflate(R.layout.application, mContent, false);
        ......

        // Note: We do this to ensure that the hotseat is always laid out in the orientation of
        // the hotseat in order regardless of which orientation they were added
        int x = getCellXFromOrder(sAllAppsButtonRank);
        int y = getCellYFromOrder(sAllAppsButtonRank);
        mContent.addViewToCellLayout(allAppsButton, -1, 0, new CellLayout.LayoutParams(x,y,1,1),
                true);
    }

Hotseat中清空了装载的内容,然后重新加载allAppsButton。从这里也可以看到allAppsButton是固定到了Hotseat中,不同于Hotseat中的其他控件。

Step2:调用Callbacks.bindItems(ArrayList<ItemInfo> shortcuts, int start, int end)

准备工作完成之后,现在可以开始正式的加载工作了,首先被调用的是bindItems()方法

     /**
     * Bind the items start-end from the list.
     *
     * Implementation of the method from LauncherModel.Callbacks.
     */
    public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end) {
        setLoadOnResume();

        final Workspace workspace = mWorkspace;
        for (int i=start; i<end; i++) {
            final ItemInfo item = shortcuts.get(i);
            ......
            switch (item.itemType) {
                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                    View shortcut = createShortcut((ShortcutInfo)item);
                    workspace.addInScreen(shortcut, item.container, item.screen, item.cellX,
                            item.cellY, 1, 1, false);
                    break;
                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                    FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
                            (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
                            (FolderInfo) item, mIconCache);
                    workspace.addInScreen(newFolder, item.container, item.screen, item.cellX,
                            item.cellY, 1, 1, false);
                    break;
            }
        }
        workspace.requestLayout();
    }
通过这个方法,将application、shortcut、folder三种item通过Workspace.addInScreen()添加到Workspace中
    /**
     * 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 screen 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.
     */
    void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY,
            boolean insert) {
        ......

        //Workspace一共有五个分屏,每个分屏是一个CellLayout
        final CellLayout layout;
        if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
            layout = mLauncher.getHotseat().getLayout();
            child.setOnKeyListener(null);

            ......
            if (screen < 0) {
                screen = mLauncher.getHotseat().getOrderInHotseat(x, y);
            } else {
                // Note: We do this to ensure that the hotseat is always laid out in the orientation
                // of the hotseat in order regardless of which orientation they were added
                //获取child的位置,返回true添加成功,false失败
                x = mLauncher.getHotseat().getCellXFromOrder(screen);
                y = mLauncher.getHotseat().getCellYFromOrder(screen);
            }
        } else {
            // Show folder title if not in the hotseat
            if (child instanceof FolderIcon) {
                ((FolderIcon) child).setTextVisible(true);
            }

            layout = (CellLayout) getChildAt(screen);
            child.setOnKeyListener(new IconKeyEventListener());
        }

        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
        if (lp == null) {
            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
        } else {
            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
        int childId = LauncherModel.getCellLayoutChildId(container, screen, x, y, spanX, spanY);
        boolean markCellsAsOccupied = !(child instanceof Folder);
        //将child添加到CellLayout中去
        if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
            ......
        }

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

通过addInScreen()就能将child添加到指定的CellLayout中去。CellLayout共有六个,Workspace中五个,Hotseat一个。

Step3:调用Callbacks.bindFolders(HashMap<Long, FolderInfo> folders)

Launcher.bindFolders()中的代码只有三行:

public void bindFolders(HashMap<Long, FolderInfo> folders) {
        setLoadOnResume();
        sFolders.clear();
        sFolders.putAll(folders);
    }
获取到当前的Folder的映射表。

Step4:调用Callbacks.bindAppWidgets(LauncherAppWidgetInfo item)

现在开始加载AppWidget到Workspace:

     /**
     * Add the views for a widget to the workspace.
     *
     * Implementation of the method from LauncherModel.Callbacks.
     */
    public void bindAppWidget(LauncherAppWidgetInfo item) {
        setLoadOnResume();

        ......
        final Workspace workspace = mWorkspace;

        final int appWidgetId = item.appWidgetId;
        final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
        ......

        item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);

        item.hostView.setAppWidget(appWidgetId, appWidgetInfo);
        item.hostView.setTag(item);

        workspace.addInScreen(item.hostView, item.container, item.screen, item.cellX,
                item.cellY, item.spanX, item.spanY, false);

        addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);

        workspace.requestLayout();

        ......
    }

先获取到AppWidget的相关信息之后,调用Workspace.addInScreen()添加到Workspace。AppWidget是Android系统的一大特色,可

以在桌面上快捷的获取实时信息和对一些指定应用进行控制。AppWidget是需要自动更新的(如果应用中设置了更新),因此除了

将其添加到桌面我们需要更具需要设置自动更新。进而调用addWidgetToAutoAdvanceifNeeded()来实现此功能,关于如何实现自动

新AppWidget的话题,本文暂不做分析。bindAppWidgets()一共被调用两次,这样做的目的是增加流畅感,第一次调用的时候为

当前显示的分屏添加AppWidget,第二次调用的时候为其他未显示的分屏添加AppWidget。这样就给用户带来了一种流畅的用户体验。

Step5:调用Callbacks.finishBindingItems()

通过上面的操作,所有item就已经悉数被添加到Workspace当中,此时调用finishBindingItems()通知Launcher添加完毕。

     /**
     * Callback saying that there aren't any more items to bind.
     *
     * Implementation of the method from LauncherModel.Callbacks.
     */
    public void finishBindingItems() {
        setLoadOnResume();
        ......
        mWorkspaceLoading = false;

        // If we received the result of any pending adds while the loader was running (e.g. the
        // widget configuration forced an orientation change), process them now.
        for (int i = 0; i < sPendingAddList.size(); i++) {
            completeAdd(sPendingAddList.get(i));
        }
        sPendingAddList.clear();
        ......
        mWorkspace.post(mBuildLayersRunnable);
    }

当Workspace正在加载的时候,有一些操作发生却还未执行,在finishBindingItems()中来执行这些操作,调用completeAdd()来完成

还未来得及完成的操作。紧接着又向Workspace的消息队列里加入了mBuildLayersRunnable,mBuildLayersRunnable是Runnable的

一个实例,它的功能就是迫使每个View都完成渲染的工作,即及时的现实到桌面中现好了所有需要的内容了。那下一步就是需要向

All Apps页中加载内容了。

二、AllApps的内容加载

回到mLoaderTask.run()方法中,当bindWorkspace()执行结束之后,并通过waitForIdle()确认加载完成之后,就会调用

loadAndBindAllApps()来为AllApps页面加载内容。

private void loadAndBindAllApps() {
            ......
            if (!mAllAppsLoaded) {
                //批量加载app和widget信息
                loadAllAppsByBatch();
                ......
            } else {
                //无需重复加载,直接绑定
                onlyBindAllApps();
            }
        }

AllApps中的加载过程和Workspace中的加载过程大致是相同的,只是All Apps的加载和绑定过程被放到同一个方loadAllAppsByBatch()中执行:

       /**
        *批量的向加载内容
        */
        private void loadAllAppsByBatch() {
            ......
            //设置Intent的action为ACTION_MAIN,category为CATEGORY_LAUNCHER
            //这样就筛选出桌面上显示的启动项了。
            final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);

            final PackageManager packageManager = mContext.getPackageManager();
            List<ResolveInfo> apps = null;

            int N = Integer.MAX_VALUE;

            int startIndex;
            int i=0;
            int batchSize = -1;
            while (i < N && !mStopped) {
                if (i == 0) {
                    mAllAppsList.clear();
                    ......
                    //查询所有应该在桌面上显示的app
                    apps = packageManager.queryIntentActivities(mainIntent, 0);
                    ......
                    
                    N = apps.size();
                    ......
                    if (mBatchSize == 0) {
                        //mBatchSize==0表示一次性加载所有的应用
                        batchSize = N;
                    } else {
                        batchSize = mBatchSize;
                    }

                    ......
                    //将获取到的app的信息按名字进行排序
                    Collections.sort(apps,
                            new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache));
                    ......
                }

                ......

                startIndex = i;
                //添加一批应用信息到mAllAppsList,每一批添加N个
                for (int j=0; i<N && j<batchSize; j++) {
                    // This builds the icon bitmaps.
                    mAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i),
                            mIconCache, mLabelCache));
                    i++;
                }

                //i < batchSize表示添加的是第一批信息
                final boolean first = i <= batchSize;
                final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                final ArrayList<ApplicationInfo> added = mAllAppsList.added;

                //每添加完一批之后,将added重新清空
                mAllAppsList.added = new ArrayList<ApplicationInfo>();

                mHandler.post(new Runnable() {
                    public void run() {
                        ......
                        //Launcher实现了Callbacks接口,将获取到的数据回调给Launcher
                        if (callbacks != null) {
                            if (first) {
                                callbacks.bindAllApplications(added);
                            } else {
                                callbacks.bindAppsAdded(added);
                            }
                            ......
                        } else {
                           ......
                        }
                    }
                });
                ......
            }
            ......
        }

过程还是挺简单的,首先当然是查询所有的App了,通过向PackagedManager发送指定的Intent就能够获得安装好的应用的信息。查

询完毕之后,将数据封装到ArrayList<ApplicationInfo>对象中,然后通过Callbacks.bindAllApplication()或Callbacks.bindAppsAdded()

将数据传给Launcher。Launcher中的操作也比加载Workspace时简单多,毕竟这里只需要加载Icon。

   /**
     * Add the icons for all apps.
     *
     * Implementation of the method from LauncherModel.Callbacks.
     */
    public void bindAllApplications(final ArrayList<ApplicationInfo> apps) {
        ......
        // We just post the call to setApps so the user sees the progress bar
        // disappear-- otherwise, it just looks like the progress bar froze
        // which doesn't look great
        mAppsCustomizeTabHost.post(new Runnable() {
            public void run() {
                if (mAppsCustomizeContent != null) {
                    mAppsCustomizeContent.setApps(apps);
                }
            }
        });
    }


    /**
     * A package was installed.
     *
     * Implementation of the method from LauncherModel.Callbacks.
     */
    public void bindAppsAdded(ArrayList<ApplicationInfo> apps) {
        setLoadOnResume();
        ......

        if (mAppsCustomizeContent != null) {
            mAppsCustomizeContent.addApps(apps);
        }
    }

这样All Apps页面的加载也完成了。


到这一步,Launcher内容的加载过程也就完成了。

  • 21
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 18
    评论
### 回答1: Android LauncherAndroid系统中的一个应用程序,它是用户与设备交互的主要界面。它提供了桌面、应用程序列表、小部件等功能,用户可以通过它来启动应用程序、查看通知、管理设备等。 Android Launcher源码分析主要包括以下几个方面: 1. 桌面布局:Android Launcher的桌面布局是通过GridView实现的,它可以显示应用程序图标和小部件。在源码中,可以看到GridView的相关代码,包括布局、适配器等。 2. 应用程序列表:Android Launcher的应用程序列表是通过ListView实现的,它可以显示所有安装的应用程序。在源码中,可以看到ListView的相关代码,包括布局、适配器等。 3. 搜索功能:Android Launcher提供了搜索功能,用户可以通过输入关键字来搜索应用程序、联系人等。在源码中,可以看到搜索框的相关代码,包括布局、事件处理等。 4. 桌面小部件:Android Launcher支持桌面小部件,用户可以在桌面上添加各种小部件,如天气、时钟、日历等。在源码中,可以看到小部件的相关代码,包括布局、事件处理等。 5. 动画效果:Android Launcher提供了各种动画效果,如应用程序图标的放大缩小、桌面的滑动等。在源码中,可以看到动画效果的相关代码,包括属性动画、插值器等。 总之,Android Launcher源码分析涉及到很多方面,需要深入研究才能掌握。 ### 回答2: Android launcher是一个重要的应用程序,它是用户的桌面界面,负责管理应用程序、小部件、壁纸等的展示和操作。在Android开发中,我们可以使用默认的系统Launcher也可以自定义Launcher,下面就来分析一下Android Launcher源码。 1. Launcher的结构分析 Launcher的展示分为三个层次:桌面、工作区和屏幕。在源码中,它们分别对应着Launcher、Workspace和CellLayout。Launcher类是整个应用程序的外壳,它管理了整个应用程序的生命周期。Workspace管理着桌面上的工作区,它可以管理多个屏幕。而CellLayout则是每个工作区上的单元格容器,用于展示应用程序和小部件的图标。 2. Launcher的主界面 Launcher的主界面一般包括一个搜索栏、一个Dock栏和一个应用程序列表。其中搜索栏和Dock栏是Launcher的核心部分,它们的实现都是需要注意的: 2.1 搜索栏 Launcher的搜索栏是通过SearchView实现的,需要处理SearchView的监听事件和搜索逻辑。其中监听事件可以根据具体需求进行定制,比如支持模糊搜索、联想搜索等。 2.2 Dock栏 Dock栏是一个固定在桌面底部的横向图标栏,它可以管理常用的应用程序和快捷方式。Dock栏的实现需要考虑如下几个问题: - 图标的布局 - 图标的拖动 - 图标的排序 - 图标的管理 3. Workspace实现分析 Workspace是Launcher的核心部分,它基本实现了所有的界面交互逻辑。在Workspace上,用户可以添加、移动、删除应用程序和小部件。Workspace的实现需要考虑如下几个问题: 3.1 桌面上的应用程序和小部件布局 Workspace上的每个屏幕都是通过CellLayout实现的,它负责管理应用程序和小部件的布局和显示。 3.2 图标的拖动和排序 用户可以通过拖动来移动和排序图标,需要考虑如下几个问题: - 拖动的实现逻辑 - 滑动过程的动画效果 - 拖动时的图标缩放效果 3.3 图标的删除和添加 用户可以通过长按图标来触发删除操作,也可以通过添加菜单来添加新的应用程序和小部件。 4. 总结 以上是针对Android Launcher源码分析,这些内容只是基础的使用方法和思路,具体的实现还需要根据自己的需求和场景进行定制和优化。在实现Launcher的时候,需要注意性能问题,尽量避免不必要的计算和操作;同时,还需要考虑到用户的交互体验,保证操作的流畅和直观。 ### 回答3: Android LauncherAndroid手机主屏幕上最常见的应用,它提供了一个桌面环境和一些关键功能,如快速访问应用程序、设置壁纸、小部件和通知中心等。本文将对Android Launcher源码进行分析,重点关注Launcher的核心组件:Workspace、Folder、App Drawer和Widget。 首先,Workspace是Launcher中最核心的组件之一,它是桌面上的容器,用于展示应用程序图标和小部件。Workspace是一个基于GridView组件自定义的ViewGroup,用于支持滑动手势和快捷方式的编辑管理。Framework层通过Workspace类读取和更新数据,它通过扩展Adapter来创建UI元素,并通过GridLayoutManager装填与交换元素。 其次,Folder是Launcher用于存储和管理应用程序快捷方式的组件。Folder的UI元素扩展自ViewGroup,可以包含多个应用快捷方式,在Folder中用户可以自由地添加、删除和排序快捷方式。Folder同时提供一个搜索框,用于帮助用户快速搜索自己的应用程序。 其次,App Drawer是Launcher的另一个核心组件,它是一个垂直滚动的列表,用于展示所有的应用程序。App Drawer是按字母排序的,同时可以通过搜索框查找用户需要的特定应用程序。App Drawer实现类似于Workspace,只不过它只横向的有一个方向轴且没有操作修改排序功能。 最后,Widget是一个独特的组件,它是一些非小部件的快捷方式,用户可以将它们放置在桌面上。Widget允许用户在主屏幕上快速访问特定功能或信息,例如天气、电子邮件和日历等。Widget UI元素的设计与Folder和App Drawer不同,它们提供更多信息和控制,通常需要桌面空间大才能放下。 总体而言,Android Launcher源码分析需要涉及多个关键组件,Workspace、Folder、App Drawer和Widget是其中最重要的几个。这些组件深度整合在Launcher中,可以帮助用户快速访问他们需要的应用程序和信息。因此,熟练掌握这些核心组件的基本实现和逻辑是Android开发者不可或缺的技能之一。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值