1. 程式人生 > >Android Launcher載入流程原始碼分析

Android Launcher載入流程原始碼分析

Launcher載入流程分析

最近開始接手Launcher模組,為了更好的技術積累,也看到很多大神在CSDN上發的博文,就有了在CSDN寫部落格的想法,這篇博文是我在研究了一段時間Launcher3後寫的,可能有不對的,望大家拍磚。首先我們可以先參考這篇http://blog.csdn.net/yanbober/article/details/50525559博文,這篇博文介紹了launcher的程式碼主流程框架,這裡我直接貼程式碼分析。

Launcher首次啟動是通過ActivityManagerService.systemReady方法啟動的,在Android系統啟動中會啟動system_server程序,在SystemServer.java中可以找到呼叫ActivityManagerService.systemReady的地方,來看一下ActivityManagerService中的systemReady方法

public void systemReady(final Runnable goingCallback) {
    ...//省略
    startHomeActivityLocked(currentUserId, "systemReady");
    ...
}

繼續看startHomeActivityLocked()方法

boolean startHomeActivityLocked(int userId, String reason) {
        if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
                && mTopAction == null
) { // We are running in factory test mode, but unable to find // the factory test app, so just sit around displaying the // error message and don't try to start anything. return false; } Intent intent = getHomeIntent();//這裡得到Launcher的Intent ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId); if
(aInfo != null) { intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name)); // Don't do this if the home app is currently being // instrumented. aInfo = new ActivityInfo(aInfo); aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId); ProcessRecord app = getProcessRecordLocked(aInfo.processName, aInfo.applicationInfo.uid, true); if (app == null || app.instrumentationClass == null) { intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); //這裡就會啟動 mActivityStarter.startHomeActivityLocked(intent, aInfo, reason); } /// M: PerfService for recording the last pause activity information. @{ if (mStackSupervisor.mLastResumedActivity.packageName == null || mStackSupervisor.isUpdatedLastActivityWhenStartHome(aInfo.packageName, aInfo.name)) { mStackSupervisor.mLastResumedActivity.packageName = aInfo.packageName; mStackSupervisor.mLastResumedActivity.activityName = aInfo.name; mStackSupervisor.mLastResumedActivity.activityType = ActivityRecord.HOME_ACTIVITY_TYPE; } /// @} } else { Slog.wtf(TAG, "No home screen found for " + intent, new Throwable()); } return true; }

來看下getHomeIntent()方法

Intent getHomeIntent() {
        Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null);
        intent.setComponent(mTopComponent);
        intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
        if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
            intent.addCategory(Intent.CATEGORY_HOME);//CATEGORY_HOME
        }
        return intent;
    }

在Launcher3中的AndroidManifest.xml中有註冊這個Category的activity

<activity
            android:name="com.android.launcher3.Launcher"
            android:launchMode="singleTask"
            android:clearTaskOnLaunch="true"
            android:stateNotNeeded="true"
            android:theme="@style/Theme"
            android:configChanges="mcc|mnc"
            android:windowSoftInputMode="adjustPan"
            android:screenOrientation="portrait"
            android:resumeWhilePausing="true"
            android:taskAffinity=""
            android:enabled="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.HOME" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.MONKEY"/>
            </intent-filter>
        </activity>

簡單分析完了Launcher是怎麼被啟動的,現在就來看Launcher本身的啟動和載入流程,現在來看Launcher.java的onCreate方法,我在一些地方加了註釋

@Override
protected void onCreate(Bundle savedInstanceState) {
    /*這兩行初始化LauncherAppState,這個單例物件,會註冊應用安裝、解除安裝、更新,配置變化等廣播,同時會
    初始化LauncherModel,裡面有一個內部類LoaderTask用來獲取資料,初始化桌面*/
    LauncherAppState.setApplicationContext(getApplicationContext());
    LauncherAppState app = LauncherAppState.getInstance();
    /*new InvariantDeviceProfile物件,從名字看意思是不變的裝置相關引數儲存類,裡面會初始化管理橫
    豎屏的兩個DeviceProfile物件*/
    app.RenewInvariantDeviceProfile();
    //獲得橫豎屏的DeviceProfile物件
    mDeviceProfile = getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE ?
                        app.getInvariantDeviceProfile().landscapeProfile
                            : app.getInvariantDeviceProfile().portraitProfile;   
    mModel = app.setLauncher(this);//獲取在LauncherAppState中new LauncherModel物件
    mIconCache = app.getIconCache();//桌面圖示快取類

    mDragController = new DragController(this);//拖拽控制類
    mInflater = getLayoutInflater();
    mStateTransitionAnimation = new LauncherStateTransitionAnimation(this);//動畫管理類
    mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);//widget管理類

    mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
    mAppWidgetHost.startListening();
    setupViews();//初始化佈局控制元件
    mDeviceProfile.layout(this);//根據裝置配置調整佈局
    //呼叫mModel.startLoader方法開始載入非同步載入桌面的資料,如app,folder,widget等
    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());
            }
     }
     //是否啟動向導介面
     if (shouldShowIntroScreen()) {
            showIntroScreen();
        } else {
            showFirstRunActivity();
            showFirstRunClings();
     }  

以上就是Launcher.java中onCreate方法中的部分程式碼,以上很多地方都用到了LauncherAppState這個單例物件,現在主要看這個類的構造方法,這個構造方法會初始化很多關鍵物件

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");
        }

        mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);
        mIconCache = new IconCache(sContext, mInvariantDeviceProfile);//存放Icon的物件
        mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);

        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載入桌面的類

        /*下面這行就是註冊APP安裝,更新,解除安裝的廣播,LauncherAppsCompat.getInstance(sContext)
        會根據Sdk的版本得到不同的例項,當前分析的是M版本的所以得到的是LauncherAppsCompatV16這個
        單例物件,呼叫這個addOnAppsChangedCallback方法,呼叫registerForPackageIntents();
        這個方法就是註冊APP安裝,更新,解除安裝的廣播的*/
        LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);

        // Register intent receivers
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
        filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
        // For handling managed profiles
        filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
        filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);

        if (LauncherLog.DEBUG) {
            LauncherLog.d(TAG, "LauncherAppState: mIconCache = " + mIconCache + ", mModel = "
                    + mModel + ", this = " + this);
        }

        sContext.registerReceiver(mModel, filter);
        UserManagerCompat.getInstance(sContext).enableAndResetCache();

        mPowerManager = (PowerManager) sContext.getSystemService(Context.POWER_SERVICE);

    }

桌面其實就是手機中的APP的入口,放的就是APP的快捷方式,widget等,所有首先我們要獲取這些資訊,然後把這些資訊用UI的方法顯示在桌面上,但是獲取這些資訊是一個耗時的操作,不可能在主執行緒裡面去操作,所以需要用非同步執行緒的方式獲取,在LauncherModel類中就有一個非同步執行緒sWorkerThread,這個非同步執行緒中有一個Handler sWorker = new Handler(sWorkerThread.getLooper());還有一個主執行緒的Handler—mHandler = new DeferredHandler();這個Handler是封裝好的,上文說到onCreate中mModle.startLoader程式碼就是來載入桌面的,現在我們進入LauncherModel這個類去分析startLoader方法,

public void startLoader(int synchronousBindPage, int loadFlags) {
        // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
        InstallShortcutReceiver.enableInstallQueue();
        synchronized (mLock) {
            if (DEBUG_LOADERS) {
                LauncherLog.d(TAG, "startLoader:  mCallbacks = " + mCallbacks);
            }

            // Clear any deferred bind-runnables from the synchronized load process
            // We must do this before any loading/binding is scheduled below.
            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) {
                // If there is already one running, tell it to stop.
                stopLoaderLocked();
                /// M: op01, 0p02, added for top package feature, load top packages from a xml file.
                AllAppsListPluginEx.loadTopPackage(mApp.getContext());
                mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
                if (LauncherLog.DEBUG) {
                    LauncherLog.d(TAG, "startLoader: mAllAppsLoaded = " + mAllAppsLoaded
                            + ",mWorkspaceLoaded = " + mWorkspaceLoaded + ",synchronousBindPage = "
                            + synchronousBindPage + ",mIsLoaderTaskRunning = "
                            + mIsLoaderTaskRunning + ",mLoaderTask = " + mLoaderTask);
                }

                if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
                        && mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) {
                    mLoaderTask.runBindSynchronousPage(synchronousBindPage);
                } else {
                    sWorkerThread.setPriority(Thread.NORM_PRIORITY);
                    /*通過分析,第一次載入最後會走到這裡,sWorker上面已經說過非同步執行緒的Handler,
                    執行mLoaderTask這個Runnable,繼續看mLoaderTask這個執行緒的run()方法*/
                    sWorker.post(mLoaderTask);
                }
            }
        }
    }
public void run() {
            synchronized (mLock) {
                if (DEBUG_LOADERS) {
                    LauncherLog.d(TAG, "Set load task running flag >>>>, mIsLaunching = " +
                            ",this = " + this);
                }

                if (mStopped) {
                    return;
                }
                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: {
                if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
                loadAndBindWorkspace();//看名字,應該就知道這個就是來載入和bind

                if (mStopped) {
                    LauncherLog.i(TAG, "LoadTask break in the middle, this = " + this);
                    break keep_running;
                }

                waitForIdle();

                // second step
                if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
                /*這個是載入所有APP,桌面最下面那一排中間的全部app按鈕,國內Launcher基本上
                都會遮蔽這個*/
                loadAndBindAllApps();
   }

繼續分析loadAndBindWorkspace()這個方法;

private void loadAndBindWorkspace() {
            mIsLoadingAndBindingWorkspace = true;//更新狀態

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

            if (!mWorkspaceLoaded) {
                loadWorkspace();//這個方法就是去載入app等資訊
                synchronized (LoaderTask.this) {
                    if (mStopped) {
                        LauncherLog.d(TAG, "loadAndBindWorkspace returned by stop flag.");
                        return;
                    }
                    mWorkspaceLoaded = true;
                }
            }

            // Bind the workspace
            bindWorkspace(-1);//bind,就是建立檢視view,更新UI
        }

先看loadWorkspace()這個方法,這個方法程式碼很多,就不全部貼出來,只分析裡面的一些關鍵性程式碼

private void loadWorkspace() {
       ...
       if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
                // append the user's Launcher2 shortcuts
                Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
                LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
            } else {
                // Make sure the default workspace is loaded
                Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
                /*如果資料庫中沒有資料,就從預設的defaultxxx.xml中解析出佈局檔案,並存入資料庫
                favorites表,存的就是所有的app,widget,folder(資料夾)資訊,如位置,icon
                title等,還有workspacescreens表,存的就是桌面每屏的id和順序
                下面的操作都會去查詢這些資料庫*/
                LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
            }
            synchronized (sBgLock) {
                clearSBgDataStructures();
                final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
                        .getInstance(mContext).updateAndGetActiveSessionCache();
                //從資料庫獲取螢幕的資訊,比如螢幕的個數
                sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
                //查詢Favorites表
                final Cursor c = contentResolver.query(contentUri, null, null, null, null);
                while (!mStopped && c.moveToNext()) {
                        try {
                            int itemType = c.getInt(itemTypeIndex);
                            boolean restored = 0 != c.getInt(restoredIndex);
                            boolean allowMissingTarget = false;
                            container = c.getInt(containerIndex);

                            switch (itemType) {
                            //app的型別,就是桌面的每一個APP圖示
                            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                                id = c.getLong(idIndex);
                                intentDescription = c.getString(intentIndex);
                                serialNumber = c.getInt(profileIdIndex);
                                user = allUsers.get(serialNumber);
                                int promiseType = c.getInt(restoredIndex);
                                int disabledState = 0;
                                boolean itemReplaced = false;
                                if (user == null) {
                                    // User has been deleted remove the item.
                                    itemsToRemove.add(id);
                                    continue;
                                }
                                try {
                                    intent = Intent.parseUri(intentDescription, 0);
                                    ComponentName cn = intent.getComponent();
                                    if (cn != null && cn.getPackageName() != null) {
                                        //判斷APP是否可用
                                        boolean validPkg = launcherApps.isPackageEnabledForProfile(
                                                cn.getPackageName(), user);
                                        boolean validComponent = validPkg &&
                                                launcherApps.isActivityEnabledForProfile(cn, user);

                                        if (validComponent) {
                                            if (restored) {
                                                // no special handling necessary for this item
                                                restoredRows.add(id);
                                                restored = false;

                                            ...

                                } else if (itemType ==
                                        LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
                                    //得到app的資訊,對應一個ShortcutInfo
                                    info = getAppShortcutInfo(manager, intent, user, context, c,
                                            cursorIconInfo.iconIndex, titleIndex,
                                            allowMissingTarget, useLowResIcon);
                                } else {
                                    info = getShortcutInfo(c, context, titleIndex, cursorIconInfo);

                                    // App shortcuts that used to be automatically added to Launcher
                                    // didn't always have the correct intent flags set, so do that
                                    // here
                                    if (intent.getAction() != null &&
                                        intent.getCategories() != null &&
                                        intent.getAction().equals(Intent.ACTION_MAIN) &&
                                        intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
                                        intent.addFlags(
                                            Intent.FLAG_ACTIVITY_NEW_TASK |
                                            Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
                                    }
                                }

                                if (info != null) {
                                    info.id = id;
                                    info.intent = intent;
                                    info.container = container;
                                    info.screenId = c.getInt(screenIndex);
                                    info.cellX = c.getInt(cellXIndex);
                                    info.cellY = c.getInt(cellYIndex);
                                    info.rank = c.getInt(rankIndex);
                                    info.spanX = 1;
                                    info.spanY = 1;
                                    info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
                                    if (info.promisedIntent != null) {
                                        info.promisedIntent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
                                    }
                                    info.isDisabled = disabledState;
                                    if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
                                        info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
                                    }

                                    // check & update map of what's occupied
                                    if (!checkItemPlacement(occupied, info, sBgWorkspaceScreens)) {
                                        itemsToRemove.add(id);
                                        break;
                                    }

                                    if (restored) {
                                        ComponentName cn = info.getTargetComponent();
                                        if (cn != null) {
                                            Integer progress = installingPkgs.get(cn.getPackageName());
                                            if (progress != null) {
                                                info.setInstallProgress(progress);
                                            } else {
                                                info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
                                            }
                                        }
                                    }

                                    switch (container) {
                                    //DESKTOP就是桌面中可滑動的部分
                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
                                    //HOTSEAT就是桌面底部放4個常用app的地方
                                    case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
                                        sBgWorkspaceItems.add(info);//放入這個集合裡面
                                        break;
                                    default:
                                        // Item is in a user folder
                                        FolderInfo folderInfo =
                                                findOrMakeFolder(sBgFolders, container);
                                        folderInfo.add(info);
                                        break;
                                    }
                                    sBgItemsIdMap.put(info.id, info);//資料夾中的app
                                } else {
                                    throw new RuntimeException("Unexpected null ShortcutInfo");
                                }
                                break;
                            //資料夾型別,放在sBgFolders這個集合中
                            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                                id = c.getLong(idIndex);
                                FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
                                ....
                            //widget型別    
                            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                            case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
                                // Read all Launcher-specific widget details
                                boolean customWidget = itemType ==
                                    LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
}

以上這些都是從資料庫中將各種型別的資訊讀取出來,如app,folder,widget等,然後放在不同的集合裡面,
sBgWorkspaceItems—–這裡面放的所有桌面的item
sBgFolders—–這裡面放的folder
sBgAppWidgets—–這裡面放的widget
分析完了,載入資訊的過程,那這些app,folder,widget是怎麼顯示到桌面上的呢,現在我們就來分析,在上面的loadAndBindWorkspace()方法中,執行完上面的loadworkspace後,就會執行bindworkspace方法,這個方法就把這些顯示到桌面的的,繼續分析程式碼

private void bindWorkspace(int synchronizeBindPage) {
            ...
            // Save a copy of all the bg-thread collections
            ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
            ArrayList<LauncherAppWidgetInfo> appWidgets =
                    new ArrayList<LauncherAppWidgetInfo>();
            ArrayList<Long> orderedScreenIds = new ArrayList<Long>();

            final LongArrayMap<FolderInfo> folders;
            final LongArrayMap<ItemInfo> itemsIdMap;

            //複製一份上面獲取到的各種資訊集合
            synchronized (sBgLock) {
                workspaceItems.addAll(sBgWorkspaceItems);
                appWidgets.addAll(sBgAppWidgets);
                orderedScreenIds.addAll(sBgWorkspaceScreens);

                folders = sBgFolders.clone();
                itemsIdMap = sBgItemsIdMap.clone();
            }

            ...

            ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
            ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
            ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
                    new ArrayList<LauncherAppWidgetInfo>();
            ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
                    new ArrayList<LauncherAppWidgetInfo>();
            LongArrayMap<FolderInfo> currentFolders = new LongArrayMap<>();
            LongArrayMap<FolderInfo> otherFolders = new LongArrayMap<>();

            ...

            //過濾當前頁面的item和其他頁面的
            filterCurrentWorkspaceItems(tempCurrentScreen, workspaceItems, currentWorkspaceItems,
                    otherWorkspaceItems);
            filterCurrentAppWidgets(tempCurrentScreen, appWidgets, currentAppWidgets,
                    otherAppWidgets);
            filterCurrentFolders(tempCurrentScreen, itemsIdMap, folders, currentFolders,
                    otherFolders);
            ...

            // Tell the workspace that we're about to start binding items
            r = new Runnable() {
                public void run() {
                    //這個回撥一直會用到,Luancher.java實現了這個回撥介面,
                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        //實際呼叫Launcher.java中的startbinding方法,做一些準備操作
                        callbacks.startBinding();
                    }
                }
            };
            runOnMainThread(r);//在主執行緒中執行這個runnable

            //建立桌面屏的個數
            bindWorkspaceScreens(oldCallbacks, orderedScreenIds);

            // Load items on the current page
            //建立當前桌面的圖示
            bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
                    currentFolders, null);
            ...

            //繫結其他桌面的圖示
            bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
                    (isLoadingSynchronously ? mDeferredBindRunnables : null));

            // Tell the workspace that we're done binding items
            r = new Runnable() {
                public void run() {
                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        //完成繫結
                        callbacks.finishBindingItems();
                    }

                    mIsLoadingAndBindingWorkspace = false;

                    // Run all the bind complete runnables after workspace is bound.
                    if (!mBindCompleteRunnables.isEmpty()) {
                        synchronized (mBindCompleteRunnables) {
                            for (final Runnable r : mBindCompleteRunnables) {
                                runOnWorkerThread(r);
                            }
                            mBindCompleteRunnables.clear();
                        }
                    }

                    // 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");
                    }

                }
            };
            if (isLoadingSynchronously) {
                synchronized (mDeferredBindRunnables) {
                    mDeferredBindRunnables.add(r);
                }
            } else {
                runOnMainThread(r);//在主執行緒執行
            }
        }

上面這個流程基本上是很條理清晰的,我們來看一下runOnMainThread這個方法,在Activiy中內部會自帶這個方法,但是在這個LauncherModel內是怎麼實現的呢,其實就是用我們上面提到的主執行緒的mHander的post方法

@Thunk void runOnMainThread(Runnable r) {
        if (sWorkerThread.getThreadId() == Process.myTid()) {
            // If we are on the worker thread, post onto the main handler
            mHandler.post(r);
        } else {
            r.run();
        }
    }

上面說過,Luancher.java實現了Callback這個回撥介面,那我們現在就來分析Luancher是這麼實現這些回撥的,看Luancher.java的程式碼

public void startBinding() {
        setWorkspaceLoading(true);
        if (LauncherLog.DEBUG) {
            LauncherLog.d(TAG, "startBinding: this = " + this);
        }

        // 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
        //開始bind前,做一些清除操作
        mBindOnResumeCallbacks.clear();

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

        mWidgetsToAdvance.clear();
        if (mHotseat != null) {
            mHotseat.resetLayout();
        }
        if (LauncherLog.DEBUG) {
            LauncherLog.d(TAG, "startBinding: mIsLoadingWorkspace = " + mIsLoadingWorkspace);
        }
    }

先看bindscreens()方法

@Override
    public void bindScreens(ArrayList<Long> orderedScreenIds) {
        //呼叫這個方法,繼續看這個方法
        bindAddScreens(orderedScreenIds);

        // If there are no screens, we need to have an empty 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();
        }
    }

先來看一下桌面的介面佈局,用一張圖來展示,圖片擷取於http://blog.csdn.net/yanbober/article/details/50525559,如有侵權請告知
這裡寫圖片描述
Workspace繼承於PagedView,是一個自定義的ViewGroup,用來滑動,每一個螢幕就是一個CellLayout
理解了這些,下面的程式碼就很容易看懂了

@Override
    public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
        int count = orderedScreenIds.size();
        //for迴圈,有多少屏就add多少CellLayout,
        for (int i = 0; i < count; i++) {
            mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
        }
    }

繼續來看binditems()方法

public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
                          final boolean forceAnimateIcons) {
        /*下面的這兩段程式碼會經常用到,它的作用就是保證桌面可見即執行onresume之後才執行這些方法
        如果桌面不可見,這個waitUntilResume(r)會把這個runnable放入集合中,等到執行onresume
        的時候去執行這個執行緒*/
        Runnable r = new Runnable() {
            public void run() {
                bindItems(shortcuts, start, end, forceAnimateIcons);
            }
        };
        if (waitUntilResume(r)) {
            return;
        }

        // Get the list of added shortcuts and intersect them with the set of shortcuts here
        final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
        final Collection<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);
            if (LauncherLog.DEBUG) {
                LauncherLog.d(TAG, "bindItems: start = " + start + ", end = " + end
                        + "item = " + item + ", this = " + this);
            }

            // 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:
                    ShortcutInfo info = (ShortcutInfo) item;
                    view = createShortcut(info);//建立app快捷方式的view

                    /*
                     * 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)) {
                            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);
                            }
                        }
                    }
                    break;
                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                    //建立資料夾的view
                    view = FolderIcon.fromXml(R.layout.folder_icon, this,
                            (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
                            (FolderInfo) item, mIconCache);
                    break;
                default:
                    throw new RuntimeException("Invalid Item Type");
            }

            //將view新增進workspace中
            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;
            }
        }

        ...
        workspace.requestLayout();
    }

bindfolders()方法

public void bindFolders(final LongArrayMap<FolderInfo> folders) {
      if (LauncherLog.DEBUG) {
            LauncherLog.d(TAG, "bindFolders: this = " + this);
       }

       Runnable r = new Runnable() {
            public void run() {
                bindFolders(folders);
            }
        };
        if (waitUntilResume(r)) {
            return;
        }
        /*並沒有做什麼操作,只是clone資料夾集合,因為在上面的binditems中的建立資料夾view
        FolderIcon.fromXml(R.layout.folder_icon, this, (ViewGroup)已經把
        開啟folder的佈局初始化好了,具體的大家可以去看仔細的程式碼*/
        sFolders = folders.clone();
    }

還有一個bindWidget()這個方法略微複雜,這裡就不說了,程式碼流程差不多,具體的大家可以去看詳細的程式碼,最後還一個收尾的finishBindingItems方法

public void finishBindingItems() {
        if (LauncherLog.DEBUG) {
            LauncherLog.d(TAG, "finishBindingItems: mSavedState = " + mSavedState
                + ", mSavedInstanceState = " + mSavedInstanceState + ", this = " + this);
        }
        Runnable r = new Runnable() {
            public void run() {
                finishBindingItems();
            }
        };
        if (waitUntilResume(r)) {
            return;
        }
        if (mSavedState != null) {
            if (!mWorkspace.hasFocus()) {
                View view = mWorkspace.getChildAt(mWorkspace.getCurrentPage());
                if (view != null) {
                    view.requestFocus();
                }
            }
            mSavedState = null;
        }

        mWorkspace.restoreInstanceStateForRemainingPages();

        setWorkspaceLoading(false);//設定狀態
        sendLoadingCompleteBroadcastIfNecessary();//launcher是否首次啟動,儲存狀態

        // 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);
                }
            });
            sPendingAddItem = null;
        }

        InstallShortcutReceiver.disableAndFlushInstallQueue(this);

        if (mLauncherCallbacks != null) {
            mLauncherCallbacks.finishBindingItems(false);
        }
    }

以上這些就是Launcher介面的主要載入流程,裡面還有很多的細節都沒有講,比如workspace,celllayout怎麼新增這些view,還有桌面的滑動snap,拖拽drag流程,還有Launcher的各種模式,資料夾等。後面有時間會繼續分析。
這是我第一次發部落格,裡面難免會有些錯誤或沒考慮的問題,希望大家指出