android的launcher中的資料主要是通過launcherModel這個類來處理的,model主要負責和xml檔案及資料庫的書記互動。
launcher的資料處理從launcher這個activity建立時就已經開始了:
1、launcher.java中的oncreate方法,調用了launchermodel中的startloader方法,開始加載launcher桌面需要的資料
if (!mRestoring) {
/// M: Add for smart book feature. Reset load state if database changed before.
if (isDatabaseIdChanged) {
mModel.resetLoadedState(true, true);
} else {
/**M: Added to reset the loader state, to resolve the timing state [email protected]{*/
mModel.resetLoadedState(false, 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());
}
2、去startloader中看看做了寫什麼
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) {
LauncherLog.d(TAG, "startLoader: isLaunching=" + isLaunching + ", 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.
// also, don't downgrade isLaunching if we're already running
isLaunching = isLaunching || stopLoaderLocked();
/// M: added for top package feature, load top packages from a xml file.
AllAppsList.loadTopPackage(mApp.getContext());
mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching, loadFlags);
if (LauncherLog.DEBUG) {
LauncherLog.d(TAG, "startLoader: mAllAppsLoaded = " + mAllAppsLoaded
+ ",mWorkspaceLoaded = " + mWorkspaceLoaded + ",synchronousBindPage = "
+ synchronousBindPage + ",mIsLoaderTaskRunning = "
+ mIsLoaderTaskRunning + ",mLoaderTask = " + mLoaderTask,
new Throwable("startLoader"));
}
if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
&& mAllAppsLoaded && mWorkspaceLoaded) {
mLoaderTask.runBindSynchronousPage(synchronousBindPage);
} else {
sWorkerThread.setPriority(Thread.NORM_PRIORITY);
sWorker.post(mLoaderTask);
}
}
}
}
我們可以看到,在這個方法中主要是建立了LoaderTask對象,并把它放到了消息隊列中讓系統處理。
我們接下來看看LoaderTask中的run方法做了什麼事情
public void run() {
boolean isUpgrade = false;
synchronized (mLock) {
if (DEBUG_LOADERS) {
LauncherLog.d(TAG, "Set load task running flag >>>>, mIsLaunching = " +
mIsLaunching + ",this = " + this);
}
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);
}
if (DEBUG_LOADERS) LauncherLog.d(TAG, "step 1: loading workspace");
isUpgrade = loadAndBindWorkspace();
if (mStopped) {
LauncherLog.i(TAG, "LoadTask break in the middle, this = " + this);
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) LauncherLog.d(TAG, "Setting thread priority to BACKGROUND");
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
}
waitForIdle();
// second step
if (DEBUG_LOADERS) LauncherLog.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;
}
if (DEBUG_LOADERS) {
LauncherLog.d(TAG, "Reset load task running flag <<<<, this = " + this);
}
mIsLoaderTaskRunning = false;
}
}
這裡代碼比較多,先看重點,這裡主要是調用了三個方法,首先是loadAndBindWorkspace(),這個方法是加載并綁定workspace以及它上面的快捷方式圖示、小元件和檔案夾。
然後是waitForIdle()方法,這個方法還沒有仔細看,貌似是空閑時加載,最後一個是loadAndBindAllApps()方法,就是加載并綁定所有的應用程式至launcher。
今天主要看一下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) {
LauncherLog.d(TAG, "loadAndBindWorkspace returned by stop flag.");
return isUpgradePath;
}
mWorkspaceLoaded = true;
}
}
// Bind the workspace
bindWorkspace(-, isUpgradePath);
return isUpgradePath;
}
這個方法拆分成了加載和綁定兩個方法,loadWorkspace和bindWorkspace,load方法主要負責加載資料庫中的資料到相應的容器中,可以看到,當第一次建立資料庫時,會将預設頁面中的item資料從xml檔案加載至資料庫中,先将item資料存入favorites表中,然後再将擷取的screenId和生成的screenIndex存入workspacsreens表中。而bindWorkspace則是将從資料庫中load出來的資料item顯示在螢幕上。
// sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
// created by LauncherModel that are directly on the home screen (however, no widgets or
// shortcuts within folders).
static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
/// M: sBgAddAppItems record apps added to database for that when add item to DB not finish
/// but need to bind items.
static final ArrayList<AppInfo> sBgAddAppItems = new ArrayList<AppInfo>();
/// M: sBgAddAppItems record apps added to database for that when delete item in DB not finish
/// but need to bind items.
static final ArrayList<AppInfo> sBgDelAppItems = new ArrayList<AppInfo>();
// sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
new ArrayList<LauncherAppWidgetInfo>();
// sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
static final HashMap<Long, FolderInfo> sBgFolders = new HashMap<Long, FolderInfo>();
bind方法是通過回調将容器中的資料綁定至ui。具體方法:
/**
* Binds all loaded data to actual views on the main thread.
*/
private void bindWorkspace(int synchronizeBindPage, final boolean isUpgradePath) {
final long t = SystemClock.uptimeMillis();
Runnable r;
// Don't use these two variables in any of the callback runnables.
// Otherwise we hold a reference to them.
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");
return;
}
// Save a copy of all the bg-thread collections
ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
ArrayList<LauncherAppWidgetInfo> appWidgets =
new ArrayList<LauncherAppWidgetInfo>();
HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>();
HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>();
ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
synchronized (sBgLock) {
workspaceItems.addAll(sBgWorkspaceItems);
appWidgets.addAll(sBgAppWidgets);
folders.putAll(sBgFolders);
itemsIdMap.putAll(sBgItemsIdMap);
orderedScreenIds.addAll(sBgWorkspaceScreens);
}
final boolean isLoadingSynchronously =
synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
int currScreen = isLoadingSynchronously ? synchronizeBindPage :
oldCallbacks.getCurrentWorkspaceScreen();
if (currScreen >= orderedScreenIds.size()) {
// There may be no workspace screens (just hotseat items and an empty page).
currScreen = PagedView.INVALID_RESTORE_PAGE;
}
final int currentScreen = currScreen;
final long currentScreenId = currentScreen <
? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);
// 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();
// Separate the items that are on the current screen, and all the other remaining items
ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
new ArrayList<LauncherAppWidgetInfo>();
ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
new ArrayList<LauncherAppWidgetInfo>();
HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>();
HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>();
/// M. ALPS01916589, filter the right fist screen.
int tempCurrentScreen;
if (orderedScreenIds.size() != && currentScreen >=
&& currentScreen < orderedScreenIds.size()) {
tempCurrentScreen = orderedScreenIds.get(currentScreen).intValue();
} else {
tempCurrentScreen = currentScreen;
}
filterCurrentWorkspaceItems(tempCurrentScreen, workspaceItems, currentWorkspaceItems,
otherWorkspaceItems);
filterCurrentAppWidgets(tempCurrentScreen, appWidgets, currentAppWidgets,
otherAppWidgets);
filterCurrentFolders(tempCurrentScreen, itemsIdMap, folders, currentFolders,
otherFolders);
/// M.
sortWorkspaceItemsSpatially(currentWorkspaceItems);
sortWorkspaceItemsSpatially(otherWorkspaceItems);
// 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);
bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
// 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) {
/** by xuhang
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));
// 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);
* by xuhang
*/
}
// 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);
}
}
在bind方法中首先複制了一份加載的資料,然後解綁目前workspace的所有item(源碼中其實什麼也沒做),然後将上述容器中的内容分為兩類,一類為目前頁的,一類為其他頁的。并且還做了一些其它處理如排序等。
// Separate the items that are on the current screen, and all the other remaining items
ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
new ArrayList<LauncherAppWidgetInfo>();
ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
new ArrayList<LauncherAppWidgetInfo>();
HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>();
HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>();
/// M. ALPS01916589, filter the right fist screen.
int tempCurrentScreen;
if (orderedScreenIds.size() != && currentScreen >=
&& currentScreen < orderedScreenIds.size()) {
tempCurrentScreen = orderedScreenIds.get(currentScreen).intValue();
} else {
tempCurrentScreen = currentScreen;
}
filterCurrentWorkspaceItems(tempCurrentScreen, workspaceItems, currentWorkspaceItems,
otherWorkspaceItems);
filterCurrentAppWidgets(tempCurrentScreen, appWidgets, currentAppWidgets,
otherAppWidgets);
filterCurrentFolders(tempCurrentScreen, itemsIdMap, folders, currentFolders,
otherFolders);
/// M.
sortWorkspaceItemsSpatially(currentWorkspaceItems);
sortWorkspaceItemsSpatially(otherWorkspaceItems);
接着就是通知ui我要開始綁定了。需要注意的是這是通過handle向ui線程發送一個runnable并在run中回調callback中的接口方法startBinding實作的,具體代碼如下:
// 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);
之後就開始回調workspace的資料了,首先是螢幕資料
private void bindWorkspaceScreens(final Callbacks oldCallbacks,
final ArrayList<Long> orderedScreens) {
final Runnable r = new Runnable() {
@Override
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindScreens(orderedScreens);
}
}
};
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
也是一樣的方式,後面還有很多。下面就是綁定目前頁面和其它頁面:
// 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) {
/** by xuhang
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));
最後就是通知ui綁定結束,做一些收尾工作:
// 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);
}
在launcher.java中通過實作launchermodel中的callbakcs接口來擷取回調的資料,進而顯示在螢幕上。
當然具體的過程還是比較複雜的,這裡隻闡述了大抵的過程,以後有機會的話,會更加詳細看一下。