• android7.x Launcher3源代码解析(3)---workspace和allapps载入流程


    Launcher系列目录:
    一、android7.x Launcher3源代码解析(1)—启动流程
    二、android7.x Launcher3源代码解析(2)—框架结构
    三、android7.x Launcher3源代码解析(3)—workspace和allapps载入流程

    前两篇博客分别对Lancher的启动和Launcher的框架结构进行了一些分析。这一篇。将着重開始分析界面的载入流程。

    1、总体流程

    先上一张总体的流程图吧。(图片看不清能够下载下来看或者右击新开个页面查看图片)

    这里写图片描写叙述

    先从Launcher.java的onCreate方法開始。

    protected void onCreate(Bundle savedInstanceState) {
        ......
        //建立LauncherAppState对象
        LauncherAppState.setApplicationContext(getApplicationContext());
        LauncherAppState app = LauncherAppState.getInstance();
    
        ......
        //建立LauncherModel对象
        mModel = app.setLauncher(this);
    
        //一些其它对象初始化
        ......
        setContentView(R.layout.launcher);
        setupViews();
        if (!mRestoring) {
                if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
                    // If the user leaves launcher, then we should just load items asynchronously when
                    // they return.
                    mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
                } else {
                    // We only load the page synchronously if the user rotates (or triggers a
                    // configuration change) while launcher is in the foreground
                    mModel.startLoader(mWorkspace.getRestorePage());
                }
        }
        ......
    }

    重点调用了LauncherModel的startLoader的方法,startLoader里面。最重要的就是启动了LoaderTask。mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage);

    我们接着分析LoaderTask的run方法。

    public void run() {
                ......
                keep_running: {
                    if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
                    loadAndBindWorkspace();
    
                    if (mStopped) {
                        break keep_running;
                    }
    
                    waitForIdle();
    
                    // second step
                    if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
                    loadAndBindAllApps();
    
                    waitForIdle();
    
                    // third step
                    if (DEBUG_LOADERS) Log.d(TAG, "step 3: loading deep shortcuts");
                    loadAndBindDeepShortcuts();
                }
    
                ......
            }

    这里一共就几步,loadAndBindWorkspace–>waitForIdle()—>loadAndBindAllApps()—>waitForIdle()—>loadAndBindDeepShortcuts()
    3步载入流程里面都穿插了waitForIdle,这种方法是干嘛的呢?

    private void waitForIdle() {
              ......
                synchronized (LoaderTask.this) {
                    final long workspaceWaitTime = DEBUG_LOADERS ?

    SystemClock.uptimeMillis() : 0; mHandler.postIdle(new Runnable() { public void run() { synchronized (LoaderTask.this) { mLoadAndBindStepFinished = true; if (DEBUG_LOADERS) { Log.d(TAG, "done with previous binding step"); } LoaderTask.this.notify(); } } }); while (!mStopped && !mLoadAndBindStepFinished) { try { // Just in case mFlushingWorkerThread changes but we aren't woken up, // wait no longer than 1sec at a time this.wait(1000); } catch (InterruptedException ex) { // Ignore } } ...... } }

    load数据时我们是去UI线程中处理的,UI线程正常情况下是不能堵塞的,否则有可能产生ANR。这将严重影响用户体验。

    全部这里LoaderTask在将结果发送给UI线程之后,为了保证界面绑定任务能够高效的完毕,往往会将自己的任务暂停下来,等待UI线程处理完毕。
    分析下这种方法:
    首先。创建一个UI线程闲时运行的任务,这个任务负责设置某些关键的控制标志。并将其通过PostIdle方法增加处理器的消息队列中。一旦任务得到运行。就会将mLoadAndBindStepFinished 置为true,以控制即将来临的有条件的无限等待。 最后 设置一个有条件的无限等待,等待来自UI线程的指示。

    2、workspace的载入流程

    从总流程图上能够看到,workspace的载入流程主要分为loadWorkspace();bindWorkspace(mPageToBindFirst);

    a、loadWorkspace()

    loadWorkspace()的代码实在是太多了,这里就不全部贴出来了。主要功能就是负责从数据库表中读取数据并转译为Launcher桌面项的数据结构。
    以下为步骤:
    1、进行一些预处理
    2、载入默认值

    LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary()

    之前Launcher2loadDefaultFavoritesIfNecessary这种方法,是这样载入默认布局的:

    workspaceResId = sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID, R.xml.default_workspace);

    可是Launcher3中,是这样载入的:

    int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
                                "xml", partner.getPackageName());

    这个地方,我临时还没理解。究竟默认布局是哪个?

    3、初始化数据
    清空之前的内存数据

    /** Clears all the sBg data structures */
            private void clearSBgDataStructures() {
                synchronized (sBgLock) {
                    sBgWorkspaceItems.clear();
                    sBgAppWidgets.clear();
                    sBgFolders.clear();
                    sBgItemsIdMap.clear();
                    sBgWorkspaceScreens.clear();
                }
            }

    4、查询ContentProvider,返回favorites表的结果集

    final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
                            .getInstance(mContext).updateAndGetActiveSessionCache();
    
                    final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
                    final ArrayList<Long> restoredRows = new ArrayList<Long>();
                    final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
                    if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
                    final Cursor c = contentResolver.query(contentUri, null, null, null, null);

    5、依据不同的类型,将数据保存到相应的arrayList中。
    类型包括以下这几种:

    ITEM_TYPE_APPLICATION
    ITEM_TYPE_SHORTCUT
    
    ITEM_TYPE_FOLDER
    
    ITEM_TYPE_APPWIDGET
    ITEM_TYPE_CUSTOM_APPWIDGET

    依据以上类型,将数据保存到一下全局变量里面
    sBgItemsIdMap,sBgWorkspaceItemsm,sBgAppWidgets,sBgFolders

    另外。假设有空的目录、空的屏幕,也会delete掉,终于,把全部屏幕载入进全局变量sBgWorkspaceScreens中。

    ......
               // Remove any empty folder
                        for (long folderId : LauncherAppState.getLauncherProvider()
                                .deleteEmptyFolders()) {
                            sBgWorkspaceItems.remove(sBgFolders.get(folderId));
                            sBgFolders.remove(folderId);
                            sBgItemsIdMap.remove(folderId);
                        }
    
                        ......
    
     sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
    
                    // Remove any empty screens
                    ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
                    for (ItemInfo item: sBgItemsIdMap) {
                        long screenId = item.screenId;
                        if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
                                unusedScreens.contains(screenId)) {
                            unusedScreens.remove(screenId);
                        }
                    }
    
                    // If there are any empty screens remove them, and update.
                    if (unusedScreens.size() != 0) {
                        sBgWorkspaceScreens.removeAll(unusedScreens);
                        updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
                    }

    b、bindWorkspace

    bindWorkspace的功能是将上面获取到的数据由Launcher显示出来。
    步骤:
    1、首先复制数据:

                synchronized (sBgLock) {
                    workspaceItems.addAll(sBgWorkspaceItems);
                    appWidgets.addAll(sBgAppWidgets);
                    orderedScreenIds.addAll(sBgWorkspaceScreens);
    
                    folders = sBgFolders.clone();
                    itemsIdMap = sBgItemsIdMap.clone();
                }

    2、装载数据并排序

                //装载桌面项数据
                filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
                        otherWorkspaceItems);
                //装载widget        
                filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
                        otherAppWidgets);
                //装载目录
                filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
                        otherFolders);
                //排序        
                sortWorkspaceItemsSpatially(currentWorkspaceItems);
                sortWorkspaceItemsSpatially(otherWorkspaceItems);
    

    3、開始绑定
    这里写图片描写叙述

    就上个流程图吧。

    3、应用程序apps的载入

    载入apps的主要流程。最上面那张流程图已经给出了,假设全部app没有载入,则loadAllApps();,不然直接onlyBindAllApps();

    1、loadAllApps()

    这里写图片描写叙述

    依据代码,画了下流程图,可是我不明确userProfile是个什么鬼?

    2、onlyBindAllApps()

    这里的函数比绑定workspace简单多了,直接通知Launcher绑定

                Runnable r = new Runnable() {
                    public void run() {
                        final long t = SystemClock.uptimeMillis();
                        final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                        if (callbacks != null) {
                            callbacks.bindAllApplications(list);
                            callbacks.bindAllPackages(widgetList);
                        }
                        if (DEBUG_LOADERS) {
                            Log.d(TAG, "bound all " + list.size() + " apps from cache in "
                                    + (SystemClock.uptimeMillis()-t) + "ms");
                        }
                    }
                };

    看一下Launcher的bindAllApplications函数。当然这个函数在loadAllApps函数里面也有。就是怎样绑定数据来显示呢?
    Launcher.java的bindAllApplications函数里面会给AllAppsContainerView设置数据

    if (mAppsView != null) {
                mAppsView.setApps(apps);
            }

    再跟代码到AllAppsContainerView。

    public void setApps(List<AppInfo> apps) {
            mApps.setApps(apps);
        }

    这个apps在哪里用到呢?比較明显的就是onFinishInflate()函数,

    .....
            // Load the all apps recycler view
            mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
            mAppsRecyclerView.setApps(mApps);
            mAppsRecyclerView.setLayoutManager(mLayoutManager);
            mAppsRecyclerView.setAdapter(mAdapter);
            mAppsRecyclerView.setHasFixedSize(true);
    .....

    设置给了recyclerView。非常显然,Launcher的应用程序界面就是一个自己定义的RecyclerView,给这个recyclerview绑定的adpter是AllAppsGridAdapter。看一下这个adpter的onCreateViewHolder函数,非常明显。这里依据不同的类型载入了不同的布局(应用程序界面有app和目录。头上还有个搜索框。用recyclerview的这个功能是最easy实现的),关于Recyclerview怎样能够依据不同的类型载入不同的布局,能够參考我非常久之前写的博客 RecyclerView的不同position载入不同View实现


    好了,Launcher3的workspace和应用程序apps的载入流程就说到这,后面还会对Launcher里面的内容做详细的分析。

  • 相关阅读:
    iOS下的四舍五入
    下载私有库 classdump命令 privateFrameworks
    苹果账号下添加测试设备
    新的Mac下如何配置开发者账号信息
    open开发笔记
    C#面向对象基础抽象类
    C#面向对象基础继承
    C#面向对象基础接口
    C#面向对象基础对象引用
    COJ1165(Nearest Numbers)
  • 原文地址:https://www.cnblogs.com/liguangsunls/p/7264626.html
Copyright © 2020-2023  润新知