码迷,mamicode.com
首页 > 其他好文 > 详细

Launcher3--加载流程

时间:2016-07-01 16:25:18      阅读:168      评论:0      收藏:0      [点我收藏+]

标签:

    Launcher也是一个普通的应用程序,只不过在主入口中加入<<Home Category>>属性,这样在开机启动后,就会首先找到这个应用,第一个加载。
     <category android:name="android.intent.category.HOME" />
    那分析Launcher的加载流程时,也可以按照一般的应用来分析就行了。

一、Application类的加载
    如果应用继承了Application类,那么该应用启动时就会首先执行继承了Application的类的onCreate()方法,在Launcher3中就是LauncherApplication类了。
    代码位置:launcher3\src\main\java\com\android\launcher3\LauncherApplication.java
public class LauncherApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        LauncherAppState.setApplicationContext(this);
        LauncherAppState.getInstance();
    }

    @Override
    public void onTerminate() {
        super.onTerminate();
        LauncherAppState.getInstance().onTerminate();
    }
}
    可以看到,其主要工作是在LauncherAppState类里执行的:
    1)设置Application的上下文,赋值给sContext,sContext是一个静态变量;
    2)设置LauncherAppState实例,是一个单例模式,在创建对象时进行了各个变量的初始化以及广播的注册、数据变化观察者。
    public static void setApplicationContext(Context context) {
        if (sContext != null) {
            Log.w(Launcher.TAG, "setApplicationContext called twice! old=" + sContext + " new=" + context);
        }
        sContext = context.getApplicationContext();
    }
    private LauncherAppState() {
        if (sContext == null) {
            throw new IllegalStateException("LauncherAppState inited before app context set");
        }

        Log.v(Launcher.TAG, "LauncherAppState inited");

        if (sContext.getResources().getBoolean(R.bool.debug_memory_enabled)) {
            MemoryTracker.startTrackingMe(sContext, "L");
        }

        // set sIsScreenXLarge and mScreenDensity *before* creating icon cache
        mIsScreenLarge = isScreenLarge(sContext.getResources());// 判断是否大屏幕
        mScreenDensity = sContext.getResources().getDisplayMetrics().density;// 像素密度

        recreateWidgetPreviewDb();// 小部件和快捷图标数据库操作对象
        mIconCache = new IconCache(sContext);// 应用图标缓存对象

        mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
        mBuildInfo = BuildInfo.loadByName(sContext.getString(R.string.build_info_class));
        mModel = new LauncherModel(this, mIconCache, mAppFilter);//初始化LauncherModel
        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(sContext);
        launcherApps.addOnAppsChangedCallback(mModel);

        // Register intent receivers
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_LOCALE_CHANGED);//Locale发生了变化,如中英文的切换
        filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);//Configuration发生变化,如横竖屏切换
        sContext.registerReceiver(mModel, filter);
        filter = new IntentFilter();
        filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);//搜索提供者发生了变化
        sContext.registerReceiver(mModel, filter);
        filter = new IntentFilter();
        filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);//搜索类别或者默认的发生了变化
        sContext.registerReceiver(mModel, filter);

        // Register for changes to the favorites
        ContentResolver resolver = sContext.getContentResolver();
        resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true,
                mFavoritesObserver);// 观察桌面快捷方式是否发生变化
    }

二、Launcher类
    初始化完成之后就进入到主Activity--Launcher。
    代码位置:launcher3\src\main\java\com\android\launcher3\Launcher.java
public class Launcher extends Activity
        implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
                   View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener {
    Launcher继承于Activity,实现各个接口,这样就构成了整个Launcher的展示(桌面和抽屉)和操作(点击、长按、拖拽等等),从onCreate开始分析。
        // LauncherCallbacks在LauncherExtension中实现,LauncherExtension继承于Launcher,是Launcher的扩展,
        // 用来扩展Launcher的功能,但是目前没有启用,android:enabled="false",涉及到相关的内容就不需要关注了。
        if (mLauncherCallbacks != null) {
            mLauncherCallbacks.preOnCreate();
        }
    LauncherCallbacks在LauncherExtension中实现,LauncherExtension继承于Launcher,是Launcher的扩展,用来扩展Launcher的功能,但是目前没有启用(android:enabled="false"),涉及到相关的内容就直接忽略掉。
        LauncherAppState.setApplicationContext(getApplicationContext());
        LauncherAppState app = LauncherAppState.getInstance();
        LauncherAppState.getLauncherProvider().setLauncherProviderChangeListener(this);
    前面两行跟LauncherApplication中一样的,第三行设置了LauncherProviderChangeListener监听,用来监听LauncherProvider中数据变化,该监听接口在Launcher中实现,
    @Override
    public void onLauncherProviderChange() {
        if (mLauncherCallbacks != null) {
            mLauncherCallbacks.onLauncherProviderChange();
        }
    }
    在LauncherExtension中这个方法的实现为空,就当是一个预留,在回到onCreate中,
        // Lazy-initialize the dynamic grid
        DeviceProfile grid = app.initDynamicGrid(this);

        // the LauncherApplication should call this, but in case of Instrumentation it might not be present yet
        mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
                Context.MODE_PRIVATE);// 初始化SharedPreference,文件名为com.android.launcher3.prefs
        mIsSafeModeEnabled = getPackageManager().isSafeMode();// 是否是安全模式,关机启动Recovery Mode可以选择打开安全模式
        mModel = app.setLauncher(this);// 获取LauncherModel对象,在LauncherAppState已经初始化
        mIconCache = app.getIconCache();// 获取IconCache对象,在LauncherAppState已经初始化
        mIconCache.flushInvalidIcons(grid);// 清除尺寸不符的icon缓存对象
        mDragController = new DragController(this);// 初始化DragController对象,DragController用来处理拖拽操作
        mInflater = getLayoutInflater();// 获取LayoutInflater

        mStats = new Stats(this);

        mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);// 获取AppWidgetManager实例

        mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);// LauncherAppWidgetHost,桌面插件宿主
        mAppWidgetHost.startListening();// 开启LauncherAppWidgetHost的监听,以便发生变化时做出响应

        // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
        // this also ensures that any synchronous binding below doesn't re-trigger another
        // LauncherModel load.
        mPaused = false;
    还是一些初始化的工作,看注释就可以了。
checkForLocaleChange();// 检查Locale的变化
    // 检查Locale是否发生变化,获取系统当前的Local和Launcher中保存的相比较,如果Locale、mcc、mnc中有一个发生了变化就认为Locale变化了
    private void checkForLocaleChange() {
        if (sLocaleConfiguration == null) {// 启动Launcher时sLocaleConfiguration肯定为null,先初始化,然后读取保存的值,赋值给sLocaleConfiguration,再回调checkForLocaleChange
            new AsyncTask<Void, Void, LocaleConfiguration>() {
                @Override
                protected LocaleConfiguration doInBackground(Void... unused) {
                    LocaleConfiguration localeConfiguration = new LocaleConfiguration();
                    readConfiguration(Launcher.this, localeConfiguration);
                    return localeConfiguration;
                }

                @Override
                protected void onPostExecute(LocaleConfiguration result) {
                    sLocaleConfiguration = result;
                    checkForLocaleChange();  // recursive, but now with a locale configuration
                }
            }.execute();
            return;
        }

        final Configuration configuration = getResources().getConfiguration();// 读取系统当前的Configuration,里面保存了Locale、mcc、mnc

        final String previousLocale = sLocaleConfiguration.locale;
        final String locale = configuration.locale.toString();

        final int previousMcc = sLocaleConfiguration.mcc;
        final int mcc = configuration.mcc;

        final int previousMnc = sLocaleConfiguration.mnc;
        final int mnc = configuration.mnc;

        boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc;// 比较

        if (localeChanged) {// 如果发生了变化,将新的值写入到launcher.preferences文件中
            sLocaleConfiguration.locale = locale;
            sLocaleConfiguration.mcc = mcc;
            sLocaleConfiguration.mnc = mnc;

            mIconCache.flush();

            final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
            new AsyncTask<Void, Void, Void>() {
                public Void doInBackground(Void ... args) {
                    writeConfiguration(Launcher.this, localeConfiguration);
                    return null;
                }
            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
        }
    }

    private static class LocaleConfiguration {
        public String locale;// 语言环境,如zh_CN
        public int mcc = -1;// 移动国家码
        public int mnc = -1;// 移动网络码
    }
    readConfiguration和writeConfiguration时读文件和写文件,代码就不贴了。
    回到onCreate中,
        setContentView(R.layout.launcher);
    Launcher是一个Activity,当然也需要加载布局了,这里layout-land和layout-port中都有launcher.xml,是为了区分横竖屏切换的情况,一般手机屏幕比较小,Launcher固定竖屏,但是在平板中,是可以横竖屏切换的,这里我们只要看竖屏port目录下的就可以了。
        setupViews();// 获取控件并初始化
        grid.layout(this);// 放置布局中的各个控件

        registerContentObservers();// 注册内容观察者,AppWidgetResetObserver,监听AppWidget是否重置,以便做出响应的处理

        lockAllApps();// 空方法

        mSavedState = savedInstanceState;
        restoreState(mSavedState);// 从保存的状态恢复
    上面的代码片段也都有注释没啥说的,继续往下看,     
        // 调运startLoader方法加载app
        if (!mRestoring) {// 判断是否正在恢复,如果第一次启动mSavedState为null,restoreState直接返回,mRestoring为false,然后执行大括号代码
            if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
                // If the user leaves launcher, then we should just load items asynchronously when
                // they return.
                mModel.startLoader(true, 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(true, mWorkspace.getRestorePage());
            }
        }
    这一段代码是用来异步加载桌面的应用快捷图标、小部件和所有应用图标,是最重要的一步,待会儿单独分析其流程。
        // For handling default keys
        mDefaultKeySsb = new SpannableStringBuilder();
        Selection.setSelection(mDefaultKeySsb, 0);

        IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
        registerReceiver(mCloseSystemDialogsReceiver, filter);

        // On large interfaces, we want the screen to auto-rotate based on the current orientation
        // 对大屏幕,希望Launcher可以横竖屏切换
        unlockScreenOrientation(true);

        if (mLauncherCallbacks != null) {
            mLauncherCallbacks.onCreate(savedInstanceState);
            if (mLauncherCallbacks.hasLauncherOverlay()) {
                ViewStub stub = (ViewStub) findViewById(R.id.launcher_overlay_stub);
                mLauncherOverlayContainer = (InsettableFrameLayout) stub.inflate();
                mLauncherOverlay = mLauncherCallbacks.setLauncherOverlayView(
                        mLauncherOverlayContainer, mLauncherOverlayCallbacks);
                mWorkspace.setLauncherOverlay(mLauncherOverlay);
            }
        }

        // 第一次启动时加载用户提示界面
        if (shouldShowIntroScreen()) {
            showIntroScreen();
        } else {
            showFirstRunActivity();
            showFirstRunClings();
        }
技术分享
    第一次启动时的提示界面,没啥说的,自己开发应用时可以借鉴下。    

三、数据加载和展示
    在Launcher的onCreate过程中会加载数据,我们将这个过程单独拎出来,startLoaderLauncherModel中实现,
    代码位置:launcher3\src\main\java\com\android\launcher3\LauncherModel.java
    public void startLoader(boolean isLaunching, int synchronousBindPage) {
        startLoader(isLaunching, synchronousBindPage, LOADER_FLAG_NONE);
    }

    public void startLoader(boolean isLaunching, int synchronousBindPage, int loadFlags) {
        synchronized (mLock) {// 将该代码块锁住,同时只能有一个线程执行它
            if (DEBUG_LOADERS) {
                Log.d(TAG, "startLoader isLaunching=" + isLaunching);
            }

            // Clear any deferred bind-runnables from the synchronized load process
            // We must do this before any loading/binding is scheduled below.
            // 清除延迟执行的绑定app的线程
            synchronized (mDeferredBindRunnables) {
                mDeferredBindRunnables.clear();
            }

            // Don't bother to start the thread if we know it's not going to do anything
            if (mCallbacks != null && mCallbacks.get() != null) {// Callbacks在Launcher中实现
                // If there is already one running, tell it to stop.
                // also, don't downgrade isLaunching if we're already running
                isLaunching = isLaunching || stopLoaderLocked();
                mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching, loadFlags);
                if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
                        && mAllAppsLoaded && mWorkspaceLoaded) {// launcher不在前台运行 && 所有app已经加载 && workspace已经加载
                    mLoaderTask.runBindSynchronousPage(synchronousBindPage);
                } else {
                    sWorkerThread.setPriority(Thread.NORM_PRIORITY);
                    sWorker.post(mLoaderTask);
                }
            }
        }
    }
    执行到mLoaderTask执行块,直接看到LoaderTask的run方法,
        public void run() {
            boolean isUpgrade = false;

            synchronized (mLock) {
                mIsLoaderTaskRunning = true;
            }
            // Optimize for end-user experience: if the Launcher is up and // running with the
            // All Apps interface in the foreground, load All Apps first. Otherwise, load the
            // workspace first (default).
            keep_running: {
                // Elevate priority when Home launches for the first time to avoid
                // starving at boot time. Staring at a blank home is not cool.
                synchronized (mLock) {
                    if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " +
                            (mIsLaunching ? "DEFAULT" : "BACKGROUND"));
                    android.os.Process.setThreadPriority(mIsLaunching
                            ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
                }
                // 分两步执行:1.loading workspace 2.loading workspace
                if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
                isUpgrade = loadAndBindWorkspace();

                if (mStopped) {
                    break keep_running;
                }

                // Whew! Hard work done.  Slow us down, and wait until the UI thread has
                // settled down.
                synchronized (mLock) {
                    if (mIsLaunching) {
                        if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
                        android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    }
                }
                waitForIdle();// loading all apps之前等待

                // second step
                if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
                loadAndBindAllApps();

                // Restore the default thread priority after we are done loading items
                synchronized (mLock) {
                    android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
                }
            }

            // Update the saved icons if necessary
            if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
            synchronized (sBgLock) {
                for (Object key : sBgDbIconCache.keySet()) {
                    updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key));
                }
                sBgDbIconCache.clear();
            }

            if (LauncherAppState.isDisableAllApps()) {
                // Ensure that all the applications that are in the system are
                // represented on the home screen.
                if (!UPGRADE_USE_MORE_APPS_FOLDER || !isUpgrade) {
                    verifyApplications();
                }
            }

            // Clear out this reference, otherwise we end up holding it until all of the
            // callback runnables are done.
            mContext = null;

            synchronized (mLock) {
                // If we are still the last one to be scheduled, remove ourselves.
                if (mLoaderTask == this) {
                    mLoaderTask = null;
                }
                mIsLoaderTaskRunning = false;
            }
        }
分两大步执行:
第一步:加载和绑定workspace--loadAndBindWorkspace
        /** Returns whether this is an upgrade path */
        private boolean loadAndBindWorkspace() {
            mIsLoadingAndBindingWorkspace = true;

            // Load the workspace
            if (DEBUG_LOADERS) {
                Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
            }

            boolean isUpgradePath = false;
            if (!mWorkspaceLoaded) {
                isUpgradePath = loadWorkspace();
                synchronized (LoaderTask.this) {
                    if (mStopped) {
                        return isUpgradePath;
                    }
                    mWorkspaceLoaded = true;
                }
            }

            // Bind the workspace
            bindWorkspace(-1, isUpgradePath);
            return isUpgradePath;
        }
    并没有直接进行加载,只是对一些状态进行了更新和条件判断,loadWorkspace和bindWorkspace才是实际操作。
1.loadWorkspace(加载Workspace上要显示的数据)
    loadWorkspace方法非常长,代码就不全部贴出了,但是执行步骤还是非常清晰的。
1)初始化后面要用到的对象实例
final Context context = mContext;
final ContentResolver contentResolver = context.getContentResolver();// 用来保存加载数据后的一些信息
final PackageManager manager = context.getPackageManager();// 初始化PackageManager
final AppWidgetManager widgets = AppWidgetManager.getInstance(context);// 初始化AppWidgetManager
final boolean isSafeMode = manager.isSafeMode();// 是否安全模式启动
final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
final boolean isSdCardReady = context.registerReceiver(null,
new IntentFilter(StartupReceiver.SYSTEM_READY)) != null;// SdCard是否就绪

LauncherAppState app = LauncherAppState.getInstance();
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
int countX = (int) grid.numColumns;// workspace列数
int countY = (int) grid.numRows;// workspace行数
2)加载默认配置
                LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
    // 加载默认的配置,保存到数据库中
    synchronized public void loadDefaultFavoritesIfNecessary() {
        String spKey = LauncherAppState.getSharedPreferencesKey();
        SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
        // 判断数据库是否未创建,
        if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
            Log.d(TAG, "loading default workspace");

            AutoInstallsLayout loader = AutoInstallsLayout.get(getContext(),
                    mOpenHelper.mAppWidgetHost, mOpenHelper);

            if (loader == null) {
                final Partner partner = Partner.get(getContext().getPackageManager());
                if (partner != null && partner.hasDefaultLayout()) {
                    final Resources partnerRes = partner.getResources();
                    int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
                            "xml", partner.getPackageName());
                    if (workspaceResId != 0) {
                        loader = new DefaultLayoutParser(getContext(), mOpenHelper.mAppWidgetHost,
                                mOpenHelper, partnerRes, workspaceResId);
                    }
                }
            }

            final boolean usingExternallyProvidedLayout = loader != null;
            if (loader == null) {
                loader = getDefaultLayoutParser();
            }
            // Populate favorites table with initial favorites
            if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
                    && usingExternallyProvidedLayout) {
                // Unable to load external layout. Cleanup and load the internal layout.
                createEmptyDB();
                mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
                        getDefaultLayoutParser());
            }
            clearFlagEmptyDbCreated();
        }
    }
    如果数据库还没有创建,就会加载默认的配置(default_workspace_xxx.xml),并保存到数据库中。
3)读取数据库,获取需要加载的应用快捷方式和AppWidget
    整个读取的过程是在一个同步代码块中,在此之前我们先看几个重要的全局变量
          sBgWorkspaceItems--保存ItemInfo
sBgAppWidgets--保存AppWidget
sBgFolders--存放文件夹
sBgItemsIdMap--保存ItemInfo和其Id
sBgDbIconCache--应用图标
sBgWorkspaceScreens--保存Workspace
    a)遍历cursor,读取每一个app信息,根据itemType不同类型,分类保存到刚才的几个变量中。分这几种类型:ITEM_TYPE_APPLICATIONITEM_TYPE_SHORTCUTITEM_TYPE_SHORTCUTITEM_TYPE_APPWIDGET
    b)读取完数据库之后,将需要移除和更新的item进行移除和更新;
    c)读取workspace screen数据库信息,如果有未使用过的则将其从数据库中移除。

2.bindWorkspace
    应用信息读取完之后,刚才的几个变量中就存储了该信息,然后将其绑定到workspace中去,这个过程也是很复杂的,我们一步一步来看。
1)不直接使用上面提到的几个全局变量,重新定义局部变量来处理
            // Save a copy of all the bg-thread collections
            // 不直接操作全局变量,将其赋值给局部变量
            ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
            ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<LauncherAppWidgetInfo>();
            HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>();
            HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>();
            ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
            synchronized (sBgLock) {
                workspaceItems.addAll(sBgWorkspaceItems);
                appWidgets.addAll(sBgAppWidgets);
                folders.putAll(sBgFolders);
                itemsIdMap.putAll(sBgItemsIdMap);
                orderedScreenIds.addAll(sBgWorkspaceScreens);
            }

            final boolean isLoadingSynchronously = synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
            int currScreen = isLoadingSynchronously ? synchronizeBindPage : oldCallbacks.getCurrentWorkspaceScreen();
            if (currScreen >= orderedScreenIds.size()) {
                // There may be no workspace screens (just hotseat items and an empty page).
                currScreen = PagedView.INVALID_RESTORE_PAGE;
            }
            final int currentScreen = currScreen;// 当前screen
            final long currentScreenId = currentScreen < 0 ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);// 当前screen id

            // Load all the items that are on the current page first (and in the process, unbind
            // all the existing workspace items before we call startBinding() below.
            unbindWorkspaceItemsOnMainThread();// 先解除绑定
2)根据item中的screenID将items分成当前screen和其他screen,并进行排序
            // Separate the items that are on the current screen, and all the other remaining items
            ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();// 存放当前workspace上的items
            ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();// 存放除当前workspace之外的items
            ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<LauncherAppWidgetInfo>();// 存放当前workspace上的appwidgets
            ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<LauncherAppWidgetInfo>();// 存放除当前workspace之外的appwidgets
            HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>();// 存放当前workspace上的folder
            HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>();// 存放除当前workspace之外的folder

            filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems, otherWorkspaceItems);// 过滤items,区分当前screen和其他screen上的items
            filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets, otherAppWidgets);// 同上
            filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders, otherFolders);// 同上
            sortWorkspaceItemsSpatially(currentWorkspaceItems);// 对workspace上的items进行排序,按照从上到下和从左到右的顺序
            sortWorkspaceItemsSpatially(otherWorkspaceItems);// 同上
3)runnable执行块,告诉workspace要开始绑定items了,startBinding方法在Launcher中实现,做一些清除工作
            // Tell the workspace that we're about to start binding items
            r = new Runnable() {
                public void run() {
                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        callbacks.startBinding();
                    }
                }
            };
            runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
代码位置:launcher3\src\main\java\com\android\launcher3\Launcher.java
    /**
     * Refreshes the shortcuts shown on the workspace.
     *
     * Implementation of the method from LauncherModel.Callbacks.
     */
    public void startBinding() {
        setWorkspaceLoading(true);

        // If we're starting binding all over again, clear any bind calls we'd postponed in
        // the past (see waitUntilResume) -- we don't need them since we're starting binding
        // from scratch again
        mBindOnResumeCallbacks.clear();

        // Clear the workspace because it's going to be rebound
        mWorkspace.clearDropTargets();
        mWorkspace.removeAllWorkspaceScreens();

        mWidgetsToAdvance.clear();
        if (mHotseat != null) {
            mHotseat.resetLayout();
        }
    }
4)绑定workspace screen
            bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
        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, MAIN_THREAD_BINDING_RUNNABLE);
        }
    有两个参数,一个是Callback对象,回调方法都在Launcher中实现,另一个是已经排序好的screen id,
    代码位置:launcher3\src\main\java\com\android\launcher3\Launcher.java
    @Override
    public void bindScreens(ArrayList<Long> orderedScreenIds) {
        bindAddScreens(orderedScreenIds);

        // If there are no screens, we need to have an empty screen
        // 如果没有需要添加screen,那我们就添加一个空白的screen
        if (orderedScreenIds.size() == 0) {
            mWorkspace.addExtraEmptyScreen();
        }

        // Create the custom content page (this call updates mDefaultScreen which calls
        // setCurrentPage() so ensure that all pages are added before calling this).
        if (hasCustomContentToLeft()) {
            mWorkspace.createCustomContentContainer();
            populateCustomContentContainer();
        }
    }
    @Override
    public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
        // Log to disk
        Launcher.addDumpLog(TAG, "11683562 - bindAddScreens()", true);
        Launcher.addDumpLog(TAG, "11683562 -   orderedScreenIds: " +
                TextUtils.join(", ", orderedScreenIds), true);
        int count = orderedScreenIds.size();
        for (int i = 0; i < count; i++) {
            mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
        }
    }
    通过for循环遍历,分别插入一个新的workspace screen,该方法在Workspace中实现,
    代码位置:launcher3\src\main\java\com\android\launcher3\Workspace.java
    public long 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();
        }
        return insertNewWorkspaceScreen(screenId, insertIndex);
    }
    // 插入一个新的workspace screen
    public long insertNewWorkspaceScreen(long screenId, int insertIndex) {
        // Log to disk
        Launcher.addDumpLog(TAG, "11683562 - insertNewWorkspaceScreen(): " + screenId + " at index: " + insertIndex, true);

        if (mWorkspaceScreens.containsKey(screenId)) {
            throw new RuntimeException("Screen id " + screenId + " already exists!");
        }

        CellLayout newScreen = (CellLayout) mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);

        newScreen.setOnLongClickListener(mLongClickListener);
        newScreen.setOnClickListener(mLauncher);
        newScreen.setSoundEffectsEnabled(false);
        mWorkspaceScreens.put(screenId, newScreen);
        mScreenOrder.add(insertIndex, screenId);
        addView(newScreen, insertIndex);
        return screenId;
    }
    其实就是创建一个CellLayout,然后添加到Workspace中。
5)Workspace绑定完成之后,就是将items、widgets和folders放到上面去
            // Load items on the current page
            bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, currentFolders, null);
            if (isLoadingSynchronously) {
                r = new Runnable() {
                    public void run() {
                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                        if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
                            callbacks.onPageBoundSynchronously(currentScreen);
                        }
                    }
                };
                runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
            }

            // Load all the remaining pages (if we are loading synchronously, we want to defer this
            // work until after the first render)
            synchronized (mDeferredBindRunnables) {
                mDeferredBindRunnables.clear();
            }
            bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
                    (isLoadingSynchronously ? mDeferredBindRunnables : null));
    当前页和其它页分别加载,都是调运bindWorkspaceItems来实现的,看一下该方法的实现过程,
a)批量加载itmes
            // 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);// 批量绑定,批量大小为ITEMS_CHUNK,如果一共少于ITEMS_CHUNK,那就一次全部绑定
                final Runnable r = new Runnable() {
                    @Override
                    public void run() {
                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                        if (callbacks != null) {
                            callbacks.bindItems(workspaceItems, start, start+chunkSize,
                                    false);
                        }
                    }
                };
                if (postOnMainThread) {
                    synchronized (deferredBindRunnables) {
                        deferredBindRunnables.add(r);
                    }
                } else {
                    runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
                }
            }
    回调到Launcher的bindItems方法,
    /**
     * Bind the items start-end from the list.
     *
     * Implementation of the method from LauncherModel.Callbacks.
     */
    public void bindItems(final ArrayList<ItemInfo> 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)) {// 当Launcher处于pause状态时,不进行绑定,待resume时再执行
            return;
        }

        // Get the list of added shortcuts and intersect them with the set of shortcuts here
        final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
        final Collection<Animator> bounceAnims = new ArrayList<Animator>();
        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
            if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
                    mHotseat == null) {// 如果绑定的item要放置在Hotseat,但是又没有配置Hotseat,直接跳过
                continue;
            }

            // 根据item的类型,区分绑定
            switch (item.itemType) {
                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:// 快捷图标
                    ShortcutInfo info = (ShortcutInfo) item;
                    View shortcut = createShortcut(info);// 创建快捷图标视图

                    /*
                     * TODO: FIX collision case
                     */
                    if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
                        CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
                        if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {// 判断item将要放置的位置是否被占据了
                            View v = cl.getChildAt(item.cellX, item.cellY);
                            Object tag = v.getTag();
                            String desc = "Collision while binding workspace item: " + item + ". Collides with " + tag;
                            if (LauncherAppState.isDogfoodBuild()) {
                                throw (new RuntimeException(desc));
                            } else {
                                Log.d(TAG, desc);
                            }
                        }
                    }

                    workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
                            item.cellY, 1, 1);// 将快捷图标添加到Workspace screen的指定位置,占据一格
                    if (animateIcons) {
                        // Animate all the applications up now
                        shortcut.setAlpha(0f);
                        shortcut.setScaleX(0f);
                        shortcut.setScaleY(0f);
                        bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
                        newShortcutsScreenId = item.screenId;
                    }
                    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.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
                            item.cellY, 1, 1);
                    break;
                default:
                    throw new RuntimeException("Invalid Item Type");
            }
        }

        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();
    }
    根据item的类型,分别加载,首先获取item信息,创建快捷图标,然后将快捷图标放置到指定位置addInScreenFromBind,
    // At bind time, we use the rank (screenId) to compute x and y for hotseat items.
    // See implementation for parameter definition.
    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);
    }
    /**
     * 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.
     */
    void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
                     boolean insert, boolean computeXYFromRank) {
        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;// 先要获取快捷图标的父视图
        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);// 添加快捷图标的时候,是否需要标记所占据的位置,除了folder外,都需要标记
        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?
            Launcher.addDumpLog(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout", true);
        }

        if (!(child instanceof Folder)) {
            child.setHapticFeedbackEnabled(false);
            child.setOnLongClickListener(mLongClickListener);
        }
        if (child instanceof DropTarget) {
            mDragController.addDropTarget((DropTarget) child);// 添加拖拽对象
        }
    }
    先获取快捷图标的父视图,分Hotseat和Desktop;设置布局参数,确定快捷图标放置的位置;父视图将快捷图标添加到指定位置,
    代码位置:launcher3\src\main\java\com\android\launcher3\CellLayout.java
    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) {// 如果是在Hotseat上的图标,将文字去除
            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);

            mShortcutsAndWidgets.addView(child, index, lp);// 最终放入指定位置

            if (markCells) markCellsAsOccupiedForView(child);// 标记所占据的位置,除了folder外,都需要标记

            return true;
        }
        return false;
    }
    最后设置触摸反馈和长安监听以及拖拽对象的添加。
b)加载folder
            // Bind the folders
            if (!folders.isEmpty()) {
                final Runnable r = new Runnable() {
                    public void run() {
                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                        if (callbacks != null) {
                            callbacks.bindFolders(folders);
                        }
                    }
                };
                if (postOnMainThread) {
                    synchronized (deferredBindRunnables) {
                        deferredBindRunnables.add(r);
                    }
                } else {
                    runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
                }
            }
    也是回调到Launcher的bindFolders方法.
    /**
     * Implementation of the method from LauncherModel.Callbacks.
     */
    public void bindFolders(final HashMap<Long, FolderInfo> folders) {
        Runnable r = new Runnable() {
            public void run() {
                bindFolders(folders);
            }
        };
        if (waitUntilResume(r)) {
            return;
        }
        sFolders.clear();
        sFolders.putAll(folders);
    }
    这样就结束了,将folders添加到sFolders的HaspMap中。可能有点奇怪,怎么没有像绑定Workspace items那样将其添加到父视图中?因为之前的过程已经添加过了,对于folder而言,它的快捷图标也是保存在workspaceItems中的,这里绑定folders只是获取folders的信息,用于对文件夹的操作,并不需要将其添加到父视图中。
c)加载widget
            // 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);
                        }
                    }
                };
                if (postOnMainThread) {
                    deferredBindRunnables.add(r);
                } else {
                    runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
                }
            }
    回调到Launcher的bindAppWidget方法中去了,
    /**
     * Add the views for a widget to the workspace.
     *
     * Implementation of the method from LauncherModel.Callbacks.
     */
    public void bindAppWidget(final LauncherAppWidgetInfo item) {
        Runnable r = new Runnable() {
            public void run() {
                bindAppWidget(item);
            }
        };
        if (waitUntilResume(r)) {
            return;
        }

        final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
        if (DEBUG_WIDGETS) {
            Log.d(TAG, "bindAppWidget: " + item);
        }
        final Workspace workspace = mWorkspace;

        AppWidgetProviderInfo appWidgetInfo;
        if (!mIsSafeModeEnabled
                && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0)
                && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) {

            appWidgetInfo = mModel.findAppWidgetProviderInfoWithComponent(this, item.providerName);// 获取AppWidgetProviderInfo
            if (appWidgetInfo == null) {
                if (DEBUG_WIDGETS) {
                    Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
                            + " belongs to component " + item.providerName
                            + ", as the povider is null");
                }
                LauncherModel.deleteItemFromDatabase(this, item);// 如果系统中找不到该AppWidget,将其从数据库中删除
                return;
            }
            // Note: This assumes that the id remap broadcast is received before this step.
            // If that is not the case, the id remap will be ignored and user may see the
            // click to setup view.
            PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo, null, null);// 创建PendingAddWidgetInfo实例
            pendingInfo.spanX = item.spanX;
            pendingInfo.spanY = item.spanY;
            pendingInfo.minSpanX = item.minSpanX;
            pendingInfo.minSpanY = item.minSpanY;
            Bundle options = AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo);// 获取appwidget最大最小宽度、高度

            int newWidgetId = mAppWidgetHost.allocateAppWidgetId();// 给AppWidget分配一个id
            boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(newWidgetId, appWidgetInfo, options);// 用AppWidgetManager来绑定AppWidget

            // TODO consider showing a permission dialog when the widget is clicked.
            if (!success) {// 如果绑定失败了,删除刚刚分配的id,并将其从数据库中移除,不在继续执行
                mAppWidgetHost.deleteAppWidgetId(newWidgetId);
                if (DEBUG_WIDGETS) {
                    Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
                            + " belongs to component " + item.providerName
                            + ", as the launcher is unable to bing a new widget id");
                }
                LauncherModel.deleteItemFromDatabase(this, item);
                return;
            }

            item.appWidgetId = newWidgetId;

            // If the widget has a configure activity, it is still needs to set it up, otherwise
            // the widget is ready to go.
            item.restoreStatus = (appWidgetInfo.configure == null)
                    ? LauncherAppWidgetInfo.RESTORE_COMPLETED
                    : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;

            LauncherModel.updateItemInDatabase(this, item);// 更新AppWidget在数据库中的信息
        }

        if (!mIsSafeModeEnabled && item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
            final int appWidgetId = item.appWidgetId;
            appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
            if (DEBUG_WIDGETS) {
                Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidgetInfo.provider);
            }

            item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);// 创建AppWidget视图
        } else {
            appWidgetInfo = null;
            PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item,
                    mIsSafeModeEnabled);
            view.updateIcon(mIconCache);
            item.hostView = view;
            item.hostView.updateAppWidget(null);
            item.hostView.setOnClickListener(this);
        }

        item.hostView.setTag(item);
        item.onBindAppWidget(this);

        workspace.addInScreen(item.hostView, item.container, item.screenId, item.cellX,
                item.cellY, item.spanX, item.spanY, false);// 将AppWidget添加到Workspace的指定位置
        addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);

        workspace.requestLayout();

        if (DEBUG_WIDGETS) {
            Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
                    + (SystemClock.uptimeMillis()-start) + "ms");
        }
    }
    其做法跟添加items类似的,就不在赘述了。
    最后通知items的绑定完成
            // Tell the workspace that we're done binding items
            r = new Runnable() {
                public void run() {
                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        callbacks.finishBindingItems(isUpgradePath);
                    }

                    // If we're profiling, ensure this is the last thing in the queue.
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "bound workspace in "
                                + (SystemClock.uptimeMillis()-t) + "ms");
                    }

                    mIsLoadingAndBindingWorkspace = false;
                }
            };
            if (isLoadingSynchronously) {
                synchronized (mDeferredBindRunnables) {
                    mDeferredBindRunnables.add(r);
                }
            } else {
                runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
            }
    /**
     * Callback saying that there aren't any more items to bind.
     *
     * Implementation of the method from LauncherModel.Callbacks.
     */
    public void finishBindingItems(final boolean upgradePath) {
        Runnable r = new Runnable() {
            public void run() {
                finishBindingItems(upgradePath);
            }
        };
        if (waitUntilResume(r)) {
            return;
        }
        if (mSavedState != null) {
            if (!mWorkspace.hasFocus()) {
                mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
            }
            mSavedState = null;
        }

        mWorkspace.restoreInstanceStateForRemainingPages();

        setWorkspaceLoading(false); //  workspace已经加载结束了
        sendLoadingCompleteBroadcastIfNecessary();// 第一次加载完成时会发送广播

        // 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.
        if (sPendingAddItem != null) {
            final long screenId = completeAdd(sPendingAddItem);

            // TODO: this moves the user to the page where the pending item was added. Ideally,
            // the screen would be guaranteed to exist after bind, and the page would be set through
            // the workspace restore process.
            mWorkspace.post(new Runnable() {
                @Override
                public void run() {
                    mWorkspace.snapToScreenId(screenId);// // 滑动指定的screenId的screen上
                }
            });
            sPendingAddItem = null;
        }

        if (upgradePath) {
            mWorkspace.getUniqueComponents(true, null);
            mIntentsOnWorkspaceFromUpgradePath = mWorkspace.getUniqueComponents(true, null);
        }
        PackageInstallerCompat.getInstance(this).onFinishBind();

        if (mLauncherCallbacks != null) {
            mLauncherCallbacks.finishBindingItems(upgradePath);
        }
    }
    这样整个loadAndBindWorkspace过程就结束了,接着下一步。
第二步:loadAndBindAllApps
        private void loadAndBindAllApps() {
            if (DEBUG_LOADERS) {
                Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
            }
            if (!mAllAppsLoaded) {// 如果还没有加载,就加载所有APP,否则是需要绑定就行了
                loadAllApps();
                synchronized (LoaderTask.this) {
                    if (mStopped) {
                        return;
                    }
                    mAllAppsLoaded = true;
                }
            } else {
                onlyBindAllApps();
            }
        }
    分了两种情况,如果所有app已经加载过了,就只需要绑定就行了,否则的话,加载所有app,第一次启动肯定是加载所有的,我们按照这种情况来分析。
        private void loadAllApps() {
            final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;

            final Callbacks oldCallbacks = mCallbacks.get();
            if (oldCallbacks == null) {
                // This launcher has exited and nobody bothered to tell us.  Just bail.
                Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
                return;
            }

            final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);

            final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();

            // Clear the list of apps
            mBgAllAppsList.clear();// 清除所有app列表
            SharedPreferences prefs = mContext.getSharedPreferences(
                    LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
            for (UserHandleCompat user : profiles) {
                // Query for the set of apps
                final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
                List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);// 获取需要显示在Launcher上的activity列表
                if (DEBUG_LOADERS) {
                    Log.d(TAG, "getActivityList took "
                            + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
                    Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
                }
                // Fail if we don't have any apps
                // TODO: Fix this. Only fail for the current user.
                if (apps == null || apps.isEmpty()) {// 没有需要显示的,直接返回
                    return;
                }
                // Sort the applications by name
                final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
                Collections.sort(apps, new LauncherModel.ShortcutNameComparator(mLabelCache));// 排序
                if (DEBUG_LOADERS) {
                    Log.d(TAG, "sort took " + (SystemClock.uptimeMillis()-sortTime) + "ms");
                }

                // Create the ApplicationInfos
                for (int i = 0; i < apps.size(); i++) {
                    LauncherActivityInfoCompat app = apps.get(i);
                    // This builds the icon bitmaps.
                    mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, mLabelCache));// 创建应用图标对象,并添加到所有APP列表中

                }

                if (ADD_MANAGED_PROFILE_SHORTCUTS && !user.equals(UserHandleCompat.myUserHandle())) {
                    // Add shortcuts for packages which were installed while launcher was dead.
                    String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX + mUserManager.getSerialNumberForUser(user);
                    Set<String> packagesAdded = prefs.getStringSet(shortcutsSetKey, Collections.EMPTY_SET);
                    HashSet<String> newPackageSet = new HashSet<String>();

                    for (LauncherActivityInfoCompat info : apps) {
                        String packageName = info.getComponentName().getPackageName();
                        if (!packagesAdded.contains(packageName)
                                && !newPackageSet.contains(packageName)) {
                            InstallShortcutReceiver.queueInstallShortcut(info, mContext);
                        }
                        newPackageSet.add(packageName);
                    }

                    prefs.edit().putStringSet(shortcutsSetKey, newPackageSet).commit();
                }
            }
            // Huh? Shouldn't this be inside the Runnable below?
            final ArrayList<AppInfo> added = mBgAllAppsList.added;// 获取自上次更新(notify()广播)后新增加的应用清单,如果是开机初次启动Launcher,那么added就是mBgAllAppsList
            mBgAllAppsList.added = new ArrayList<AppInfo>();// 将AllAppsList的added清空,不影响后续新增的app

            // Post callback on main thread
            mHandler.post(new Runnable() {
                public void run() {
                    final long bindTime = SystemClock.uptimeMillis();
                    final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        callbacks.bindAllApplications(added);
                        if (DEBUG_LOADERS) {
                            Log.d(TAG, "bound " + added.size() + " apps in "
                                    + (SystemClock.uptimeMillis() - bindTime) + "ms");
                        }
                    } else {
                        Log.i(TAG, "not binding apps: no Launcher activity");
                    }
                }
            });

            if (DEBUG_LOADERS) {
                Log.d(TAG, "Icons processed in "
                        + (SystemClock.uptimeMillis() - loadTime) + "ms");
            }
        }

        public void dumpState() {
            synchronized (sBgLock) {
                Log.d(TAG, "mLoaderTask.mContext=" + mContext);
                Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching);
                Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
                Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
                Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
            }
        }
    }
    1.获取需要显示到Launcher中的app列表,创建app图标
    2.绑定app--bindAllApplications
    /**
     * Add the icons for all apps.
     *
     * Implementation of the method from LauncherModel.Callbacks.
     */
    public void bindAllApplications(final ArrayList<AppInfo> apps) {
        if (LauncherAppState.isDisableAllApps()) {// 判断是否禁用所有app,就是所有应用都显示在一级目录
            if (mIntentsOnWorkspaceFromUpgradePath != null) {
                if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) {
                    getHotseat().addAllAppsFolder(mIconCache, apps,
                            mIntentsOnWorkspaceFromUpgradePath, Launcher.this, mWorkspace);
                }
                mIntentsOnWorkspaceFromUpgradePath = null;
            }
            if (mAppsCustomizeContent != null) {
                mAppsCustomizeContent.onPackagesUpdated(
                        LauncherModel.getSortedWidgetsAndShortcuts(this));
            }
        } else {
            if (mAppsCustomizeContent != null) {
                mAppsCustomizeContent.setApps(apps);
                mAppsCustomizeContent.onPackagesUpdated(LauncherModel.getSortedWidgetsAndShortcuts(this));
            }
        }
        if (mLauncherCallbacks != null) {
            mLauncherCallbacks.bindAllApplications(apps);
        }
    }
    首先判断是否禁用所有app,就是所有应用都显示在桌面上,我们这里没有禁用所有app页面,直接到else代码块,
    // 设置需要显示的app,并排序,更新数据
    public void setApps(ArrayList<AppInfo> list) {
        if (!LauncherAppState.isDisableAllApps()) {
            mApps = list;
            Collections.sort(mApps, LauncherModel.getAppNameComparator());
            updatePageCountsAndInvalidateData();
        }
    }
   我们跟一下updatePageCountsAndInvalidateData方法,看看到底怎么个过程,
    private void updatePageCountsAndInvalidateData() {
        if (mInBulkBind) {
            mNeedToUpdatePageCountsAndInvalidateData = true;
        } else {
            updatePageCounts();
            invalidateOnDataChange();
            mNeedToUpdatePageCountsAndInvalidateData = false;
        }
    }
    updatePageCounts是计算并更新需要的page数量,直接看invalidateOnDataChange方法,
    /**
     * We should call thise method whenever the core data changes (mApps, mWidgets) so that we can
     * appropriately determine when to invalidate the PagedView page data.  In cases where the data
     * has yet to be set, we can requestLayout() and wait for onDataReady() to be called in the
     * next onMeasure() pass, which will trigger an invalidatePageData() itself.
     */
    private void invalidateOnDataChange() {
        if (!isDataReady()) {
            // The next layout pass will trigger data-ready if both widgets and apps are set, so
            // request a layout to trigger the page data when ready.
            requestLayout();
        } else {
            cancelAllTasks();
            invalidatePageData();
        }
    }
    protected void invalidatePageData() {
        invalidatePageData(-1, false);
    }
    protected void invalidatePageData(int currentPage, boolean immediateAndOnly) {
        if (!mIsDataReady) {
            return;
        }

        if (mContentIsRefreshable) {
            // Force all scrolling-related behavior to end
            forceFinishScroller();

            // Update all the pages
            syncPages();

            // We must force a measure after we've loaded the pages to update the content width and
            // to determine the full scroll width
            measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));

            // Set a new page as the current page if necessary
            // 设置当前显示页
            if (currentPage > -1) {
                setCurrentPage(Math.min(getPageCount() - 1, currentPage));
            }

            // Mark each of the pages as dirty
            final int count = getChildCount();
            mDirtyPageContent.clear();
            for (int i = 0; i < count; ++i) {
                mDirtyPageContent.add(true);
            }

            // Load any pages that are necessary for the current window of views
            loadAssociatedPages(mCurrentPage, immediateAndOnly);
            requestLayout();
        }
        if (isPageMoving()) {
            // If the page is moving, then snap it to the final position to ensure we don't get
            // stuck between pages
            snapToDestination();
        }
    }
    这个方法是重点,也分了几步来执行,我们分步来看:
1)syncPages
    是一个抽象方法,在AppsCustomizePagedView中实现,
代码位置:launcher3\src\main\java\com\android\launcher3\AppsCustomizePagedView.java
    @Override
    public void syncPages() {
        disablePagedViewAnimations();

        // 移除所有视图和任务
        removeAllViews();
        cancelAllTasks();

        Context context = getContext();
        if (mContentType == ContentType.Applications) {
            for (int i = 0; i < mNumAppsPages; ++i) {
                AppsCustomizeCellLayout layout = new AppsCustomizeCellLayout(context);
                setupPage(layout);
                addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT,
                        LayoutParams.MATCH_PARENT));
            }
        } else if (mContentType == ContentType.Widgets) {
            for (int j = 0; j < mNumWidgetPages; ++j) {
                PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX,
                        mWidgetCountY);
                setupPage(layout);
                addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT,
                        LayoutParams.MATCH_PARENT));
            }
        } else {
            throw new RuntimeException("Invalid ContentType");
        }

        enablePagedViewAnimations();
    }
    app和widget来分别添加,我们这里只需了解如何添加app页面的,widget完全类似。
    a)生成
AppsCustomizeCellLayout对象
    b)
setupPage,设置page
    // 设置page的表格、背景色
    private void setupPage(AppsCustomizeCellLayout layout) {
        layout.setGridSize(mCellCountX, mCellCountY);// 设置页面表格数

        // Note: We force a measure here to get around the fact that when we do layout calculations
        // immediately after syncing, we don't have a proper width.  That said, we already know the
        // expected page width, so we can actually optimize by hiding all the TextView-based
        // children that are expensive to measure, and let that happen naturally later.
        setVisibilityOnChildren(layout, View.GONE);
        int widthSpec = MeasureSpec.makeMeasureSpec(mContentWidth, MeasureSpec.AT_MOST);
        int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST);
        layout.measure(widthSpec, heightSpec);

        // 设置page背景色
        Drawable bg = getContext().getResources().getDrawable(R.drawable.quantum_panel);
        if (bg != null) {
            bg.setAlpha(mPageBackgroundsVisible ? 255: 0);
            layout.setBackground(bg);
        }

        setVisibilityOnChildren(layout, View.VISIBLE);
    }
    c)addView,将CellLayout添加到page中
2)重新测量,设置当前页等
            // We must force a measure after we've loaded the pages to update the content width and
            // to determine the full scroll width
            measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));

            // Set a new page as the current page if necessary
            // 设置当前显示页
            if (currentPage > -1) {
                setCurrentPage(Math.min(getPageCount() - 1, currentPage));
            }
3)loadAssociatedPages
    protected void loadAssociatedPages(int page, boolean immediateAndOnly) {
        if (mContentIsRefreshable) {
            final int count = getChildCount();
            if (page < count) {
                int lowerPageBound = getAssociatedLowerPageBound(page);
                int upperPageBound = getAssociatedUpperPageBound(page);
                if (DEBUG) Log.d(TAG, "loadAssociatedPages: " + lowerPageBound + "/"
                        + upperPageBound);
                // First, clear any pages that should no longer be loaded
                for (int i = 0; i < count; ++i) {
                    Page layout = (Page) getPageAt(i);
                    if ((i < lowerPageBound) || (i > upperPageBound)) {
                        if (layout.getPageChildCount() > 0) {
                            layout.removeAllViewsOnPage();
                        }
                        mDirtyPageContent.set(i, true);
                    }
                }
                // Next, load any new pages
                for (int i = 0; i < count; ++i) {
                    if ((i != page) && immediateAndOnly) {
                        continue;
                    }
                    if (lowerPageBound <= i && i <= upperPageBound) {
                        if (mDirtyPageContent.get(i)) {
                            syncPageItems(i, (i == page) && immediateAndOnly);
                            mDirtyPageContent.set(i, false);
                        }
                    }
                }
            }
        }
    }
    先清除不需要加载的pages,然后加载page及items--syncPageItems,也是一个抽象方法在AppsCustomizePagedView中实现,
    @Override
    public void syncPageItems(int page, boolean immediate) {
        if (mContentType == ContentType.Widgets) {
            syncWidgetPageItems(page, immediate);
        } else {
            syncAppsPageItems(page, immediate);
        }
    }
    // 添加items到page上
    public void syncAppsPageItems(int page, boolean immediate) {
        // ensure that we have the right number of items on the pages
        final boolean isRtl = isLayoutRtl();// 是否从右向左排列,一般都是从左向右
        int numCells = mCellCountX * mCellCountY;// 每页的格数,及可加载的app数量
        int startIndex = page * numCells;// 开始位置
        int endIndex = Math.min(startIndex + numCells, mApps.size());// 结束位置,如果不满页,就是app数量的总数
        AppsCustomizeCellLayout layout = (AppsCustomizeCellLayout) getPageAt(page);// 根据page号获取AppsCustomizeCellLayout

        layout.removeAllViewsOnPage();
        ArrayList<Object> items = new ArrayList<Object>();
        ArrayList<Bitmap> images = new ArrayList<Bitmap>();
        for (int i = startIndex; i < endIndex; ++i) {// 循环添加items
            AppInfo info = mApps.get(i);
            BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(R.layout.apps_customize_application, layout, false);
            icon.applyFromApplicationInfo(info);
            icon.setOnClickListener(mLauncher);
            icon.setOnLongClickListener(this);
            icon.setOnTouchListener(this);
            icon.setOnKeyListener(this);
            icon.setOnFocusChangeListener(layout.mFocusHandlerView);
//            icon.setTextColor(Color.WHITE); // modify text color

            int index = i - startIndex;
            // 就算item所在表格位置
            int x = index % mCellCountX;
            int y = index / mCellCountX;
            if (isRtl) {
                x = mCellCountX - x - 1;
            }
            layout.addViewToCellLayout(icon, -1, i, new CellLayout.LayoutParams(x,y, 1,1), false);

            items.add(info);
            images.add(info.iconBitmap);
        }

        enableHwLayersOnVisiblePages();
    }
    代码中都有注释,比较好理解,先计算需要放置的位置,然后创建BubbleTextView实例,最后将其添加到page中。整个
setApps的过程还是非常长的,最终目的就是将app显示到所有app列表中。
接着会执行
onPackagesUpdated,package的更新操作,就不再展开了。
Widget也是类似的。
    这样加载allapp的过程就结束了。


Launcher3--加载流程

标签:

原文地址:http://blog.csdn.net/dingfengnupt88/article/details/51800057

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!