Launcher3的主界面是
packages/apps/Launcher3/src/com/android/launcher3/Launcher.java
首先分析onCreate
@Override
protected void onCreate(Bundle savedInstanceState) {
if (DEBUG_STRICT_MODE) {
//StrictMode被稱爲嚴苛模式,google提供用來進行測試的類
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
if (LauncherAppState.PROFILE_STARTUP) {
Trace.beginSection("Launcher-onCreate");
}
if (mLauncherCallbacks != null) {
mLauncherCallbacks.preOnCreate();
}
WallpaperColorInfo wallpaperColorInfo = WallpaperColorInfo.getInstance(this);
wallpaperColorInfo.setOnThemeChangeListener(this);
overrideTheme(wallpaperColorInfo.isDark(), wallpaperColorInfo.supportsDarkText());
super.onCreate(savedInstanceState);
//單例模式,初始化LauncherAppState
LauncherAppState app = LauncherAppState.getInstance(this);
// Load configuration-specific DeviceProfile
//初始化手機固件信息對象DeviceProfile
mDeviceProfile = app.getInvariantDeviceProfile().getDeviceProfile(this);
if (isInMultiWindowModeCompat()) {
Display display = getWindowManager().getDefaultDisplay();
Point mwSize = new Point();
display.getSize(mwSize);
mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
}
//獲取屏幕方向
mOrientation = getResources().getConfiguration().orientation;
mSharedPrefs = Utilities.getPrefs(this); //獲取sharedPreferences
mIsSafeModeEnabled = getPackageManager().isSafeMode();
mModel = app.setLauncher(this); //獲取LauncherModel實例
mModelWriter = mModel.getWriter(mDeviceProfile.isVerticalBarLayout());
mIconCache = app.getIconCache();//獲取IconCache實例,此類主要保存圖標信息
mAccessibilityDelegate = new LauncherAccessibilityDelegate(this);
//拖拽
mDragController = new DragController(this);
mAllAppsController = new AllAppsTransitionController(this);
mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, mAllAppsController);
mAppWidgetManager = AppWidgetManagerCompat.getInstance(this); //獲取AppWidgetManager實例,用來管理widge
mAppWidgetHost = new LauncherAppWidgetHost(this);
if (Utilities.ATLEAST_MARSHMALLOW) {
mAppWidgetHost.addProviderChangeListener(this);
}
mAppWidgetHost.startListening();
// 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;
//設置佈局,此時佈局是不包含參數佈局
mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null);
//初始化View,進行各種View的初始化事件綁定
setupViews();
mDeviceProfile.layout(this, false /* notifyListeners */);//佈局的參數設置
loadExtractedColorsAndColorItems();
mPopupDataProvider = new PopupDataProvider(this);
((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
.addAccessibilityStateChangeListener(this);
lockAllApps();
restoreState(savedInstanceState);
if (LauncherAppState.PROFILE_STARTUP) {
Trace.endSection();
}
// We only load the page synchronously if the user rotates (or triggers a
// configuration change) while launcher is in the foreground
int currentScreen = PagedView.INVALID_RESTORE_PAGE;
if (savedInstanceState != null) {
currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen);
}
if (!mModel.startLoader(currentScreen)) {
// If we are not binding synchronously, show a fade in animation when
// the first page bind completes.
mDragLayer.setAlpha(0);
} else {
// Pages bound synchronously.
mWorkspace.setCurrentPage(currentScreen);
setWorkspaceLoading(true);
}
// For handling default keys
mDefaultKeySsb = new SpannableStringBuilder();
Selection.setSelection(mDefaultKeySsb, 0);
mRotationEnabled = getResources().getBoolean(R.bool.allow_rotation);
// In case we are on a device with locked rotation, we should look at preferences to check
// if the user has specifically allowed rotation.
if (!mRotationEnabled) {
mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext());
mRotationPrefChangeHandler = new RotationPrefChangeHandler();
mSharedPrefs.registerOnSharedPreferenceChangeListener(mRotationPrefChangeHandler);
}
if (PinItemDragListener.handleDragRequest(this, getIntent())) {
// Temporarily enable the rotation
mRotationEnabled = true;
}
// On large interfaces, or on devices that a user has specifically enabled screen rotation,
// we want the screen to auto-rotate based on the current orientation
setOrientation();
setContentView(mLauncherView);
// Listen for broadcasts
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_USER_PRESENT); // When the device wakes up + keyguard is gone
registerReceiver(mReceiver, filter);
mShouldFadeInScrim = true;
getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onCreate(savedInstanceState);
}
}
private LauncherAppState(Context context) {
if (getLocalProvider(context) == null) {
throw new RuntimeException(
"Initializing LauncherAppState in the absence of LauncherProvider");
}
Log.v(Launcher.TAG, "LauncherAppState initiated");
Preconditions.assertUIThread();
mContext = context;
if (TestingUtils.MEMORY_DUMP_ENABLED) {
TestingUtils.startTrackingMemory(mContext);
}
//初始化固定的設備配置
mInvariantDeviceProfile = new InvariantDeviceProfile(mContext);
//初始化圖標管理工具
mIconCache = new IconCache(mContext, mInvariantDeviceProfile);
//初始化Widget加載緩存工具
mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
//初始化廣播
mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
LauncherAppsCompat.getInstance(mContext).addOnAppsChangedCallback(mModel);
// Register intent receivers
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
// For handling managed profiles
filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
// For extracting colors from the wallpaper
if (Utilities.ATLEAST_NOUGAT) {
// TODO: add a broadcast entry to the manifest for pre-N.
filter.addAction(Intent.ACTION_WALLPAPER_CHANGED);
}
//註冊廣播
mContext.registerReceiver(mModel, filter);
UserManagerCompat.getInstance(mContext).enableAndResetCache();
new ConfigMonitor(mContext).register();
ExtractionUtils.startColorExtractionServiceIfNecessary(mContext);
if (!mContext.getResources().getBoolean(R.bool.notification_badging_enabled)) {
mNotificationBadgingObserver = null;
} else {
// Register an observer to rebind the notification listener when badging is re-enabled.
mNotificationBadgingObserver = new SettingsObserver.Secure(
mContext.getContentResolver()) {
@Override
public void onSettingChanged(boolean isNotificationBadgingEnabled) {
if (isNotificationBadgingEnabled) {
NotificationListener.requestRebind(new ComponentName(
mContext, NotificationListener.class));
}
}
};
mNotificationBadgingObserver.register(NOTIFICATION_BADGING);
}
}
從代碼中可以看出,首先調用了LauncherAppState.getInstance(this)來初始化一個單例對象。LauncherAppState裏面保存了一些比較常用的對象,方便其他地方通過單例來獲取,比如IconCache、LauncherModel等。
public static LauncherAppState getInstance(final Context context) {
if (INSTANCE == null) {
if (Looper.myLooper() == Looper.getMainLooper()) {
INSTANCE = new LauncherAppState(context.getApplicationContext());
} else {
try {
return new MainThreadExecutor().submit(new Callable<LauncherAppState>() {
@Override
public LauncherAppState call() throws Exception {
return LauncherAppState.getInstance(context);
}
}).get();
} catch (InterruptedException|ExecutionException e) {
throw new RuntimeException(e);
}
}
}
return INSTANCE;
}
注意這裏初始化使用的application的Context,因爲單例作爲static對象,生命週期是與application生命週期一樣長的,如果這裏使用了Activity的Contxet,會導致activity退出後,該Context依然被單例持有而無法回收,於是出現內存泄漏。
private LauncherAppState(Context context) {
if (getLocalProvider(context) == null) {
throw new RuntimeException(
"Initializing LauncherAppState in the absence of LauncherProvider");
}
Log.v(Launcher.TAG, "LauncherAppState initiated");
Preconditions.assertUIThread();
mContext = context;
if (TestingUtils.MEMORY_DUMP_ENABLED) {
TestingUtils.startTrackingMemory(mContext);
}
//初始化固定的設備配置
mInvariantDeviceProfile = new InvariantDeviceProfile(mContext);
//初始化圖標管理工具
mIconCache = new IconCache(mContext, mInvariantDeviceProfile);
//初始化Widget加載混存工具
mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
//初始化廣播
mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
LauncherAppsCompat.getInstance(mContext).addOnAppsChangedCallback(mModel);
// Register intent receivers
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
// For handling managed profiles
filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
// For extracting colors from the wallpaper
if (Utilities.ATLEAST_NOUGAT) {
// TODO: add a broadcast entry to the manifest for pre-N.
filter.addAction(Intent.ACTION_WALLPAPER_CHANGED);
}
mContext.registerReceiver(mModel, filter);
UserManagerCompat.getInstance(mContext).enableAndResetCache();
new ConfigMonitor(mContext).register();
ExtractionUtils.startColorExtractionServiceIfNecessary(mContext);
if (!mContext.getResources().getBoolean(R.bool.notification_badging_enabled)) {
mNotificationBadgingObserver = null;
} else {
// Register an observer to rebind the notification listener when badging is re-enabled.
mNotificationBadgingObserver = new SettingsObserver.Secure(
mContext.getContentResolver()) {
@Override
public void onSettingChanged(boolean isNotificationBadgingEnabled) {
if (isNotificationBadgingEnabled) {
NotificationListener.requestRebind(new ComponentName(
mContext, NotificationListener.class));
}
}
};
mNotificationBadgingObserver.register(NOTIFICATION_BADGING);
}
}
這裏面其實是各個對象的實例創建過程,並且註冊了一些系統事件的監聽。創建完成LauncherAppState後,會執行mModel = app.setLauncher(this); //獲取LauncherModel實例
LauncherModel setLauncher(Launcher launcher) {
getLocalProvider(mContext).setLauncherProviderChangeListener(launcher);
mModel.initialize(launcher);
return mModel;
}
Launcher3/src/com/android/launcher3/LauncherModel.java
public void initialize(Callbacks callbacks) {
synchronized (mLock) {
Preconditions.assertUIThread();
mCallbacks = new WeakReference<>(callbacks);
}
}
這裏將傳過來的Callbacks對象(也就是Launcher,Launcher實現了Callbacks接口)保存爲了弱引用。同樣是基於避免內存泄漏的考慮。這裏的LauncherModel是LauncherAppState內部的一個成員變量,生命週期也是比Launcher這個Activity要長。
總結一下onCreate的工作:初始化對象、加載佈局、註冊一些事件監聽、以及開啓數據加載。
數據加載是通過mModel.startLoader。現在進入LauncherModel
Launcher3/src/com/android/launcher3/LauncherModel.java
/**
* Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
* @return true if the page could be bound synchronously.
*/
public boolean startLoader(int synchronousBindPage) {
// Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING);
synchronized (mLock) {
// Don't bother to start the thread if we know it's not going to do anything
if (mCallbacks != null && mCallbacks.get() != null) {
final Callbacks oldCallbacks = mCallbacks.get();
// Clear any pending bind-runnables from the synchronized load process.
mUiExecutor.execute(new Runnable() {
public void run() {
oldCallbacks.clearPendingBinds();
}
});
// If there is already one running, tell it to stop.
stopLoader();
LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel,
mBgAllAppsList, synchronousBindPage, mCallbacks);
if (mModelLoaded && !mIsLoaderTaskRunning) {
// Divide the set of loaded items into those that we are binding synchronously,
// and everything else that is to be bound normally (asynchronously).
loaderResults.bindWorkspace();
// For now, continue posting the binding of AllApps as there are other
// issues that arise from that.
loaderResults.bindAllApps();
loaderResults.bindDeepShortcuts();
loaderResults.bindWidgets();
return true;
} else {
startLoaderForResults(loaderResults);
}
}
}
return false;
}
public void startLoaderForResults(LoaderResults results) {
synchronized (mLock) {
stopLoader();
mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, sBgDataModel, results);
runOnWorkerThread(mLoaderTask);
}
}
/** Runs the specified runnable immediately if called from the worker thread, otherwise it is
* posted on the worker thread handler. */
private static void runOnWorkerThread(Runnable r) {
if (sWorkerThread.getThreadId() == Process.myTid()) {
r.run();
} else {
// If we are not on the worker thread, then post to the worker handler
sWorker.post(r);
}
}
從上面代碼可以看到,數據加載調用的是工作線程,避免堵塞主線程。工作線程使用的是LoaderTask。
Launcher3/src/com/android/launcher3/model/LoaderTask.java
public void run() {
synchronized (this) {
// Skip fast if we are already stopped.
if (mStopped) {
return;
}
}
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
long now = 0;
if (DEBUG_LOADERS) Log.d(TAG, "step 1.1: loading workspace");
loadWorkspace();
verifyNotStopped();
if (DEBUG_LOADERS) Log.d(TAG, "step 1.2: bind workspace workspace");
mResults.bindWorkspace();
// Take a break
if (DEBUG_LOADERS) {
Log.d(TAG, "step 1 completed, wait for idle");
now = SystemClock.uptimeMillis();
}
waitForIdle();
if (DEBUG_LOADERS) Log.d(TAG, "Waited " + (SystemClock.uptimeMillis() - now) + "ms");
verifyNotStopped();
// second step
if (DEBUG_LOADERS) Log.d(TAG, "step 2.1: loading all apps");
loadAllApps();
if (DEBUG_LOADERS) Log.d(TAG, "step 2.2: Binding all apps");
verifyNotStopped();
mResults.bindAllApps();
verifyNotStopped();
if (DEBUG_LOADERS) Log.d(TAG, "step 2.3: Update icon cache");
updateIconCache();
// Take a break
if (DEBUG_LOADERS) {
Log.d(TAG, "step 2 completed, wait for idle");
now = SystemClock.uptimeMillis();
}
waitForIdle();
if (DEBUG_LOADERS) Log.d(TAG, "Waited " + (SystemClock.uptimeMillis() - now) + "ms");
verifyNotStopped();
// third step
if (DEBUG_LOADERS) Log.d(TAG, "step 3.1: loading deep shortcuts");
loadDeepShortcuts();
verifyNotStopped();
if (DEBUG_LOADERS) Log.d(TAG, "step 3.2: bind deep shortcuts");
mResults.bindDeepShortcuts();
// Take a break
if (DEBUG_LOADERS) Log.d(TAG, "step 3 completed, wait for idle");
waitForIdle();
verifyNotStopped();
// fourth step
if (DEBUG_LOADERS) Log.d(TAG, "step 4.1: loading widgets");
mBgDataModel.widgetsModel.update(mApp, null);
verifyNotStopped();
if (DEBUG_LOADERS) Log.d(TAG, "step 4.2: Binding widgets");
mResults.bindWidgets();
transaction.commit();
} catch (CancellationException e) {
// Loader stopped, ignore
if (DEBUG_LOADERS) {
Log.d(TAG, "Loader cancelled", e);
}
}
}
在上面代碼中,主要有4步,由於Launcher裏面數據比較多,在這裏採用了分批加載、分批綁定的做法。
這裏以Workspace爲例,分析加載和綁定。加載桌面是loadWorkspace方法,需要先了解BgDataModel類(Launcher3/src/com/android/launcher3/model/BgDataModel.java)。
BgDataModel是android O版本新增的,對memory緩存進行了封裝,可以看到有workspaceItems(所有應用圖標數據對應的ItemInfo),appWidgets(所有AppWidgets數據對應的LauncherAppWidgetInfo)等等。
public final LongArrayMap<ItemInfo> itemsIdMap = new LongArrayMap<>();
public final ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
public final LongArrayMap<FolderInfo> folders = new LongArrayMap<>();
public final ArrayList<Long> workspaceScreens = new ArrayList<>();
public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>();
public boolean hasShortcutHostPermission;
public final MultiHashMap<ComponentKey, String> deepShortcutMap = new MultiHashMap<>();
public final WidgetsModel widgetsModel = new WidgetsModel();
loadWorkspace的代碼比較長,主要做兩件事情:1、加載默認配置xml文件中的items,並把默認數據寫入數據庫。2、加載數據庫中的數據到緩存。先看加載默認佈局
if (clearDb) {
Log.d(TAG, "loadWorkspace: resetting launcher database");
LauncherSettings.Settings.call(contentResolver,
LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
}
最終會其會調用LauncherProvider的loadDefaultFavoritesIfNecessary()方法。
再分析加載數據庫
final LoaderCursor c = new LoaderCursor(contentResolver.query(
LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp);
1、通過LauncherSettings.Favorites.CONTENT_URI查詢Favorites表中所有內容,拿到cursor。
2、遍歷cursor,進行數據的整理。每一行數據都有一個對應的itemType,標誌着這一行數據對應得的是一個應用、還是一個Widget或文件夾等,不同類型會進行不同的處理。
3、對於圖標類型(itemType是ITEM_TYPE_SHORTCUT,ITEM_TYPE_APPLICATION,ITEM_TYPE_DEEP_SHORTCUT
),首先經過一系列判斷,判斷其是否還可用(比如應用在Launcher未啓動時被卸載導致不可用),不可用的話就標記爲可刪除,繼續循環。如果可用的話,就根據當前cursor的內容,生成一個ShortInfo對象,保存到BgDataModel。
4、對於文件夾類型(itemType是ITEM_TYPE_FOLDER
),直接生成一個對應的FolderInfo對象,保存到BgDataModel。
5、對於文件類型(itemType是ITEM_TYPE_FOLDER),直接生成一個對應的Folder對象,保存到BgDataModel。
6、對於AppWidget(itemType是ITEM_TYPE_APPWIDGET
,ITEM_TYPE_CUSTOM_APPWIDGET
),也需要經過是否可用的判斷,但是可用條件與圖標類型是有差異的。如果可用,生成一個LauncherAppWidgetInfo對象,保存到BgDataModel。
7、經過上述流程,現在所有數據庫裏讀出的內容已經分類完畢,並且保存到了內存(BgDataModel)中。然後開始處理之前標記爲可刪除的內容。顯示從數據庫中刪除對應的行,然後還要判斷此次刪除操作是否帶來了其他需要刪除的內容。比如某個文件夾或者某一頁只有一個圖標,這個圖標因爲某些原因被刪掉了,那麼此文件夾或頁面也需要被刪掉。
數據加載完畢後開始綁定數據使用mResults.bindWorkspace()
Launcher3/src/com/android/launcher3/model/LoaderResults.java
// Separate the items that are on the current screen, and all the other remaining items
ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
otherWorkspaceItems);
filterCurrentWorkspaceItems(currentScreenId, appWidgets, currentAppWidgets,
otherAppWidgets);
sortWorkspaceItemsSpatially(currentWorkspaceItems);
sortWorkspaceItemsSpatially(otherWorkspaceItems);
上面這段代碼是把Launcher啓動後默認顯示出來的那一頁所擁有的數據篩選到
currentWorkspaceItems與currentAppWidgets,其他頁的數據篩選到otherWorkspaceItems與otherAppWidgets。然後對每個list,按照從上到下,從左到右的順序進行排序。然後可以開始綁定了。
// Tell the workspace that we're about to start binding items
r = new Runnable() {
public void run() {
Callbacks callbacks = mCallbacks.get();
if (callbacks != null) {
callbacks.clearPendingBinds();
callbacks.startBinding();
}
}
};
mUiExecutor.execute(r);
// Bind workspace screens
mUiExecutor.execute(new Runnable() {
@Override
public void run() {
Callbacks callbacks = mCallbacks.get();
if (callbacks != null) {
callbacks.bindScreens(orderedScreenIds);
}
}
});
Executor mainExecutor = mUiExecutor;
// Load items on the current page.
bindWorkspaceItems(currentWorkspaceItems, currentAppWidgets, mainExecutor);
上面的Callbacks就是Launcher activity實例,首先通知Launcher要開始綁定(callbacks.startBinding()),然後把空白頁添加到View tree中(callbacks.bindScreens(orderedScreenIds)),之後先綁定默認頁面的所有元素( bindWorkspaceItems(currentWorkspaceItems, currentAppWidgets, mainExecutor)),當然這些所有的操作都是通過mUiExecutor放到主線程執行的。
private void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems,
final ArrayList<LauncherAppWidgetInfo> appWidgets,
final Executor executor) {
// Bind the workspace items
int N = workspaceItems.size();
for (int i = 0; i < N; i += ITEMS_CHUNK) {
final int start = i;
final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
final Runnable r = new Runnable() {
@Override
public void run() {
Callbacks callbacks = mCallbacks.get();
if (callbacks != null) {
callbacks.bindItems(workspaceItems.subList(start, start+chunkSize), false);
}
}
};
executor.execute(r);
}
// Bind the widgets, one at a time
N = appWidgets.size();
for (int i = 0; i < N; i++) {
final ItemInfo widget = appWidgets.get(i);
final Runnable r = new Runnable() {
public void run() {
Callbacks callbacks = mCallbacks.get();
if (callbacks != null) {
callbacks.bindItems(Collections.singletonList(widget), false);
}
}
};
executor.execute(r);
}
}
最後通過callbacks調用在Launcher中的bindItems方法
@Override
public void bindItems(final List<ItemInfo> items, final boolean forceAnimateIcons) {
...
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
ShortcutInfo info = (ShortcutInfo) item;
view = createShortcut(info);
break;
}
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
view = FolderIcon.fromXml(R.layout.folder_icon, this,
(ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
(FolderInfo) item);
break;
}
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: {
view = inflateAppWidget((LauncherAppWidgetInfo) item);
if (view == null) {
continue;
}
break;
}
default:
throw new RuntimeException("Invalid Item Type");
}
...
workspace.addInScreenFromBind(view, item);
...
}
上面的代碼主要是根據不同的itemType來生產不同的View,然後通過addInScreenFromBind函數將View add到相應的ViewGroup中去。
默認頁的元素綁定完了,然後繼續綁定其他頁面的元素。
// In case of validFirstPage, only bind the first screen, and defer binding the
// remaining screens after first onDraw (and an optional the fade animation whichever
// happens later).
// This ensures that the first screen is immediately visible (eg. during rotation)
// In case of !validFirstPage, bind all pages one after other.
final Executor deferredExecutor =
validFirstPage ? new ViewOnDrawExecutor(mUiExecutor) : mainExecutor;
mainExecutor.execute(new Runnable() {
@Override
public void run() {
Callbacks callbacks = mCallbacks.get();
if (callbacks != null) {
callbacks.finishFirstPageBind(
validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null);
}
}
});
bindWorkspaceItems(otherWorkspaceItems, otherAppWidgets, deferredExecutor);
從上面代碼可以看出,Launcher爲了讓默認頁儘快顯示,自定義了一個ViewOnDrawExecutor,這裏面會讓綁定其他頁的操作在綁定完第一頁的元素並且第一次onDraw執行完之後才執行。讀者有興趣的話可以去看看這個Executor的實現。
桌面數據的加載與綁定完之後,我們看這裏執行了一個waitForIdle的函數,然後纔是繼續執行第二步,這裏面涉及到一個應用啓動優化的技術。我們知道應用的啓動優化可以有延遲加載、懶加載、異步加載等手段。而用一個名爲IdleHandler
的類,就可以比較方便的實現延遲加載。
參考資料: