編輯:關於Android編程
Launcher也是一個普通的應用程序,只不過在主入口中加入<
那分析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() { @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 () { 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過程中會加載數據,我們將這個過程單獨拎出來,startLoader在LauncherModel中實現,
代碼位置: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_APPLICATION、ITEM_TYPE_SHORTCUT、ITEM_TYPE_SHORTCUT、ITEM_TYPE_APPWIDGET
b)讀取完數據庫之後,將需要移除和更新的item進行移除和更新;
c)讀取workspace screen數據庫信息,如果有未使用過的則將其從數據庫中移除。
2.bindWorkspace
應用信息讀取完之後,剛才的幾個變量中就存儲了該信息,然後將其綁定到workspace中去,這個過程也是很復雜的,我們一步一步來看。
1)不直接使用上面提到的幾個全局變量,重新定義局部變量來處理
// Save a copy of all the bg-thread collections // 不直接操作全局變量,將其賦值給局部變量 ArrayListworkspaceItems = new ArrayList (); ArrayList appWidgets = new ArrayList (); HashMap folders = new HashMap (); HashMap itemsIdMap = new HashMap (); ArrayList orderedScreenIds = new ArrayList (); 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 ArrayListcurrentWorkspaceItems = new ArrayList ();// 存放當前workspace上的items ArrayList otherWorkspaceItems = new ArrayList ();// 存放除當前workspace之外的items ArrayList currentAppWidgets = new ArrayList ();// 存放當前workspace上的appwidgets ArrayList otherAppWidgets = new ArrayList ();// 存放除當前workspace之外的appwidgets HashMap currentFolders = new HashMap ();// 存放當前workspace上的folder HashMap otherFolders = new HashMap ();// 存放除當前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 ArrayListorderedScreens) { 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(ArrayListorderedScreenIds) { 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(ArrayListorderedScreenIds) { // 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 ArrayListshortcuts, 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 bounceAnims = new ArrayList(); 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 HashMapfolders) { 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 Listprofiles = 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 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 packagesAdded = prefs.getStringSet(shortcutsSetKey, Collections.EMPTY_SET); HashSet newPackageSet = new HashSet (); 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 added = mBgAllAppsList.added;// 獲取自上次更新(notify()廣播)後新增加的應用清單,如果是開機初次啟動Launcher,那麼added就是mBgAllAppsList mBgAllAppsList.added = new ArrayList();// 將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 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 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
代碼中都有注釋,比較好理解,先計算需要放置的位置,然後創建BubbleTextView實例,最後將其添加到page中。整個setApps的過程還是非常長的,最終目的就是將app顯示到所有app列表中。
接著會執行onPackagesUpdated,package的更新操作,就不再展開了。Widget也是類似的。這樣加載allapp的過程就結束了。
一、問題描述 使用BordercastReceiver和Service組件實現下述功能:1.當手機處於來電狀態,啟動監聽服務,對來電進行監聽錄音。2.設置電話黑名單,當
第一步 :獲取ShareSDK 為了集成ShareSDK,您首先需要到ShareSDK官方網站注冊並且創建應用,獲得ShareSDK的Appkey,然後到SDK的下載頁
先看一下singleTop啟動模式的說明:可以有多個實例,但是不允許此Activity的多個實例疊加。即,如果此Activity有實例在棧頂的時候,啟動這個Activit
純屬好奇心驅動寫的一個學習性Demo,效果如下:兩個帶圓弧的線就是由三點確認的一個貝塞爾曲線:在Android已經有提供畫貝塞爾曲線的接口,三個點傳進去,效果就出來了。貝