Launcher3学习记录-Launcher第一次启动时的快捷方式Widget加载流程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Launcher3学习记录-Launcher第一次启动时的快捷方式Widget加载流程相关的知识,希望对你有一定的参考价值。

 

Launcher3的主Activity是Launcher.java,在onCreate()方法中可以找到数据下载的入口。mModel 是类LauncherModel的引用。

1         if (!mModel.startLoader(mWorkspace.getRestorePage())) {
2             // If we are not binding synchronously, show a fade in animation when
3             // the first page bind completes.
4             mDragLayer.setAlpha(0);
5         } else {
6             setWorkspaceLoading(true);
7         }

在startLoader方法中可以看到,启动了一个名为LoaderTask的子线程。

 1     public boolean startLoader(int synchronousBindPage) {
 2         // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
 3         InstallShortcutReceiver.enableInstallQueue();
 4         synchronized (mLock) {
 5             // Don‘t bother to start the thread if we know it‘s not going to do anything
 6             if (mCallbacks != null && mCallbacks.get() != null) {
 7                 final Callbacks oldCallbacks = mCallbacks.get();
 8                 // Clear any pending bind-runnables from the synchronized load process.
 9                 runOnMainThread(new Runnable() {
10                     public void run() {
11                         oldCallbacks.clearPendingBinds();
12                     }
13                 });
14 
15                 // If there is already one running, tell it to stop.
16                 stopLoaderLocked();
17                 mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage);
18                 // TODO: mDeepShortcutsLoaded does not need to be true for synchronous bind.
19                 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE && mAllAppsLoaded
20                         && mWorkspaceLoaded && mDeepShortcutsLoaded && !mIsLoaderTaskRunning) {
21                     mLoaderTask.runBindSynchronousPage(synchronousBindPage);
22                     return true;
23                 } else {
24                     sWorkerThread.setPriority(Thread.NORM_PRIORITY);
25                     sWorker.post(mLoaderTask);
26                 }
27             }
28         }
29         return false;
30     }

可以看到,LoaderTask 类 实现了Runnable接口,run()方法中明确了加载的三步骤:1、loadAndBindWorkspace()  加载桌面,包括加载桌面上的应用、widget、快捷方式等图标;2、loadAndBindAllApps() 加载AllApps 界面;3、loadAndBindDeepShortcuts() 加载 deep shortcuts ,这个是7.1的ROM上才会有的功能。

 1     private class LoaderTask implements Runnable {
 2         public void run() {
 3             synchronized (mLock) {
 4                 if (mStopped) {
 5                     return;
 6                 }
 7                 mIsLoaderTaskRunning = true;
 8             }
 9             // Optimize for end-user experience: if the Launcher is up and // running with the
10             // All Apps interface in the foreground, load All Apps first. Otherwise, load the
11             // workspace first (default).
12             keep_running: {
13                 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
14                 loadAndBindWorkspace();
15 
16                 if (mStopped) {
17                     break keep_running;
18                 }
19 
20                 waitForIdle();
21 
22                 // second step
23                 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
24                 loadAndBindAllApps();
25 
26                 waitForIdle();
27 
28                 // third step
29                 if (DEBUG_LOADERS) Log.d(TAG, "step 3: loading deep shortcuts");
30                 loadAndBindDeepShortcuts();
31             }
32 
33             // Clear out this reference, otherwise we end up holding it until all of the
34             // callback runnables are done.
35             mContext = null;
36 
37             synchronized (mLock) {
38                 // If we are still the last one to be scheduled, remove ourselves.
39                 if (mLoaderTask == this) {
40                     mLoaderTask = null;
41                 }
42                 mIsLoaderTaskRunning = false;
43                 mHasLoaderCompletedOnce = true;
44             }
45         }
46     }

接下来看下loadAndBindWorkspace() 方法,其中loadWorkspace()方法 加载数据,bindWorkspace()方法 将快捷方式和widget显示到界面上。

 1         private void loadAndBindWorkspace() {
 2             mIsLoadingAndBindingWorkspace = true;
 3 
 4             // Load the workspace
 5             if (DEBUG_LOADERS) {
 6                 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
 7             }
 8 
 9             if (!mWorkspaceLoaded) {
10                 loadWorkspace();
11                 synchronized (LoaderTask.this) {
12                     if (mStopped) {
13                         return;
14                     }
15                     mWorkspaceLoaded = true;
16                 }
17             }
18 
19             // Bind the workspace
20             bindWorkspace(mPageToBindFirst);
21         }

loadWorkspace()方法就比较复杂了,但是大致分为几部:1、加载launcher的默认布局;2、查询并遍历数据库,将数据缓存起来,同时将异常数据添加到待删除数组中,比如被禁用的应用、已卸载的应用 等异常数据;3、删除异常数据,异常的图标、异常的文件夹、异常的页面;4、重新整理剩下的数据。

 1         private void loadWorkspace() {
 2             ...
 3             Log.d(TAG, "loadWorkspace: loading default favorites");
 4             LauncherSettings.Settings.call(contentResolver, LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);
 5             
 6                 sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));//加载页面顺序
 7                 
 8                 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
 9                 if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
10                 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
11                 while (!mStopped && c.moveToNext()) {
12                     int itemType = c.getInt(itemTypeIndex);
13                     container = c.getInt(containerIndex);
14                     switch (itemType) {
15                     case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
16                     case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
17                     case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
18                     ...
19                     sBgWorkspaceItems.add(info);
20                     sBgItemsIdMap.put(info.id, info);
21                     break;
22                     case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
23                     ...
24                     sBgWorkspaceItems.add(folderInfo);
25                     sBgItemsIdMap.put(folderInfo.id, folderInfo);
26                     sBgFolders.put(folderInfo.id, folderInfo);
27                     break;
28                     case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
29                     case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
30                     sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
31                     sBgAppWidgets.add(appWidgetInfo);
32                     break;
33                     }
34                 }
35             ...
36         }

这段加载过程将数据库中有用的数据,基本都加载到内存中来了,下面是各个数据的注释:

    // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
    // LauncherModel to their ids
    static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>();

    // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
    //       created by LauncherModel that are directly on the home screen (however, no widgets or
    //       shortcuts within folders).
    static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();

    // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
    static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
        new ArrayList<LauncherAppWidgetInfo>();

    // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
    static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>();

    // sBgWorkspaceScreens is the ordered set of workspace screens.
    static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();

加载完数据,接下来就得将数据转化成View绑定到界面了,bindWorkspace()方法中可以看到,分别调了bindWorkspaceScreens、bindWorkspaceItems方法,而这两个方法中,又分别调到了Launcher.java的bindScreens、bindItems、bindAppWidget

        private void bindWorkspace(int synchronizeBindPage) {
            ...
            bindWorkspaceScreens(oldCallbacks, orderedScreenIds);

            Executor mainExecutor = new DeferredMainThreadExecutor();
            // Load items on the current page.
            bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, mainExecutor);

            // In case of validFirstPage, only bind the first screen, and defer binding the
            // remaining screens after first onDraw (and an optional the fade animation whichever
            // happens later).
            // This ensures that the first screen is immediately visible (eg. during rotation)
            // In case of !validFirstPage, bind all pages one after other.
            final Executor deferredExecutor =
                    validFirstPage ? new ViewOnDrawExecutor(mHandler) : mainExecutor;

            mainExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        callbacks.finishFirstPageBind(
                                validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null);
                    }
                }
            });

            bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, deferredExecutor);
            ...
        }
        private void bindWorkspaceScreens(final Callbacks oldCallbacks,
                final ArrayList<Long> orderedScreens) {
            final Runnable r = new Runnable() {
                @Override
                public void run() {
                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        callbacks.bindScreens(orderedScreens);
                    }
                }
            };
            runOnMainThread(r);
        }

        private void bindWorkspaceItems(final Callbacks oldCallbacks,
                final ArrayList<ItemInfo> workspaceItems,
                final ArrayList<LauncherAppWidgetInfo> appWidgets,
                final Executor executor) {

            // Bind the workspace items
            int N = workspaceItems.size();
            for (int i = 0; i < N; i += ITEMS_CHUNK) {
                final int start = i;
                final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
                final Runnable r = new Runnable() {
                    @Override
                    public void run() {
                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                        if (callbacks != null) {
                            callbacks.bindItems(workspaceItems, start, start+chunkSize,
                                    false);
                        }
                    }
                };
                executor.execute(r);
            }

            // Bind the widgets, one at a time
            N = appWidgets.size();
            for (int i = 0; i < N; i++) {
                final LauncherAppWidgetInfo widget = appWidgets.get(i);
                final Runnable r = new Runnable() {
                    public void run() {
                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                        if (callbacks != null) {
                            callbacks.bindAppWidget(widget);
                        }
                    }
                };
                executor.execute(r);
            }
        }

通过下面代码可以看到,bindScreens()方法跟踪到最后,Workspace的 addView()方法,将一个CellLayout 添加到Workspace;而bindItems()方法跟踪到最后,走到了 CellLayout的 addViewToCellLayout()方法,被添加到CellLayout的ShortcutAndWidgetContainer 中去了。

    @Override
    public void bindScreens(ArrayList<Long> orderedScreenIds) {
        ...
        bindAddScreens(orderedScreenIds);
        ...
    }

    private void bindAddScreens(ArrayList<Long> orderedScreenIds) {
        int count = orderedScreenIds.size();
        for (int i = 0; i < count; i++) {
            long screenId = orderedScreenIds.get(i);
            if (!FeatureFlags.QSB_ON_FIRST_SCREEN || screenId != Workspace.FIRST_SCREEN_ID) {
                // No need to bind the first screen, as its always bound.
                mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
            }
        }
    }
    @Override
    public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
                          final boolean forceAnimateIcons) {
        ...
        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
            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:
                    ShortcutInfo info = (ShortcutInfo) item;
                    view = createShortcut(info);
                    break;
                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                    view = FolderIcon.fromXml(R.layout.folder_icon, this,
                            (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
                            (FolderInfo) item, mIconCache);
                    break;
                default:
                    throw new RuntimeException("Invalid Item Type");
            }
            ...
            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;
            }
        }
        ...
    }
    /**
     * Add the views for a widget to the workspace.
     *
     * Implementation of the method from LauncherModel.Callbacks.
     */
    public void bindAppWidget(final LauncherAppWidgetInfo item) {
        ...
        final LauncherAppWidgetProviderInfo appWidgetInfo;

        if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
            // If the provider is not ready, bind as a pending widget.
            appWidgetInfo = null;
        } else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
            // The widget id is not valid. Try to find the widget based on the provider info.
            appWidgetInfo = mAppWidgetManager.findProvider(item.providerName, item.user);
        } else {
            appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(item.appWidgetId);
        }
        ...

        if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
            ...
            item.minSpanX = appWidgetInfo.minSpanX;
            item.minSpanY = appWidgetInfo.minSpanY;
            addAppWidgetToWorkspace(
                    mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo),
                    item, appWidgetInfo, false);
        } else {
            PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item, false);
            view.updateIcon(mIconCache);
            view.updateAppWidget(null);
            view.setOnClickListener(this);
            addAppWidgetToWorkspace(view, item, null, false);
        }
        ...
    }

    private void addAppWidgetToWorkspace(
            AppWidgetHostView hostView, LauncherAppWidgetInfo item,
            LauncherAppWidgetProviderInfo appWidgetInfo, boolean insert) {
        hostView.setTag(item);
        item.onBindAppWidget(this, hostView);

        hostView.setFocusable(true);
        hostView.setOnFocusChangeListener(mFocusHandler);

        mWorkspace.addInScreen(hostView, item.container, item.screenId,
                item.cellX, item.cellY, item.spanX, item.spanY, insert);

        if (!item.isCustomWidget()) {
            addWidgetToAutoAdvanceIfNeeded(hostView, appWidgetInfo);
        }
    }
    public void insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
        // Find the index to insert this view into.  If the empty screen exists, then
        // insert it before that.
        int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
        if (insertIndex < 0) {
            insertIndex = mScreenOrder.size();
        }
        insertNewWorkspaceScreen(screenId, insertIndex);
    }

    public CellLayout insertNewWorkspaceScreen(long screenId, int insertIndex) {
        if (mWorkspaceScreens.containsKey(screenId)) {
            throw new RuntimeException("Screen id " + screenId + " already exists!");
        }

        // Inflate the cell layout, but do not add it automatically so that we can get the newly
        // created CellLayout.
        CellLayout newScreen = (CellLayout) mLauncher.getLayoutInflater().inflate(
                        R.layout.workspace_screen, this, false /* attachToRoot */);
        newScreen.setOnLongClickListener(mLongClickListener);
        newScreen.setOnClickListener(mLauncher);
        newScreen.setSoundEffectsEnabled(false);
        mWorkspaceScreens.put(screenId, newScreen);
        mScreenOrder.add(insertIndex, screenId);
        addView(newScreen, insertIndex);

        if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
            newScreen.enableAccessibleDrag(true, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
        }

        return newScreen;
    }

    // At bind time, we use the rank (screenId) to compute x and y for hotseat items.
    // See implementation for parameter definition.
    public void addInScreenFromBind(View child, long container, long screenId, int x, int y,
            int spanX, int spanY) {
        addInScreen(child, container, screenId, x, y, spanX, spanY, false, true);
    }
    
    void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
            boolean insert) {
        addInScreen(child, container, screenId, x, y, spanX, spanY, insert, false);
    }
    void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
            boolean insert, boolean computeXYFromRank) {
        ...
        final CellLayout layout;
        if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
            layout = mLauncher.getHotseat().getLayout();
            ...
        } else {
            layout = getScreenWithId(screenId);
            ...
        }
        ...
        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");
        }
        ...
    }
    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);
            }
            mShortcutsAndWidgets.addView(child, index, lp);

            if (markCells) markCellsAsOccupiedForView(child);

            return true;
        }
        return false;
    }

到这里,Launcher第一次启动时 加载快捷方式、Widget的流程就结束了。

以上是关于Launcher3学习记录-Launcher第一次启动时的快捷方式Widget加载流程的主要内容,如果未能解决你的问题,请参考以下文章

系统方向学习总结5--Launcher3实现动态指针时钟功能

系统方向学习总结5--Launcher3实现动态指针时钟功能

系统方向学习总结6 --Launcher3拖拽分析之Workspace

系统方向学习总结6 --Launcher3拖拽分析之Workspace

系统方向学习总结6 --Launcher3拖拽分析之Workspace

系统方向学习总结6 --Launcher3拖拽分析之Workspace