通过调用LoaderTask#startLoader()数据加载和绑定,该方法在Launcher#oncreate()中被调用:
if (sPausedFromUserAction) {
// If the user leaves launcher, then we should just load items asynchronously when
// they return.
mModel.startLoader(true, -1);
} 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.getCurrentPage());
}
其中第二个参数不为-1时,是同步加载,只有桌面正在显示且屏幕发生旋转时才会同步加载。
看startLoader()这个方法源码:
public void startLoader(boolean isLaunching, int synchronousBindPage) {
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.
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();
mLoaderTask = new LoaderTask(mApp, isLaunching);
if (synchronousBindPage > -1 && mAllAppsLoaded && mWorkspaceLoaded) {
mLoaderTask.runBindSynchronousPage(synchronousBindPage);//同步加载时调用runBindSynchronousPage,而不是run()
} else {
sWorkerThread.setPriority(Thread.NORM_PRIORITY);
sWorker.post(mLoaderTask);//异步加载,在给线程池运行LoaderTask的run()
}
}
}
}
下面来分析异步加载时的数据加载和绑定过程。
LauncherModel$LoaderTask#run()
public void run() {
synchronized (mLock) {
mIsLoaderTaskRunning = true;
}
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.
//... ...
// First step. Load workspace first, this is necessary since adding of apps from
// managed profile in all apps is deferred until onResume. See http://b/17336902.
if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
loadAndBindWorkspace();//加载桌面数据和绑定
if (mStopped) {
break keep_running;//在onStop调用后跳出循环
}
// Whew! Hard work done. Slow us down, and wait until the UI thread has
// settled down.
//... ...
waitForIdle();
// Second step. Load all apps.
if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
loadAndBindAllApps();加载应用列表数据和绑定
// Restore the default thread priority after we are done loading items
//... ...
}
// Update the saved icons if necessary
//... ...
// 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;
}
}
其中loadAndBindWorkspace会调用loadWorkspace()和bindWorkspace(),下面先分析loadWorkspace()。
LauncherModel$LoaderTask#loadWorkspace():
1. mApp.getLauncherProvider().loadDefaultFavoritesIfNecessary(0, false);
从xml文件填充数据库favorite表,如果没有填充过。默认是default_workspace.xml,源码如下:
/**
* @param workspaceResId that can be 0 to use default or non-zero for specific resource
*/
synchronized public void loadDefaultFavoritesIfNecessary(int origWorkspaceResId,
boolean overridePrevious) {
String spKey = LauncherApplication.getSharedPreferencesKey();
SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
//如果manifest中没有写上对应的键值对,那么resId返回0
int restrictionLayoutId = getWorkspaceLayoutIdFromAppRestrictions();
boolean restrictionLayoutChanged = didRestrictionLayoutChange(sp, restrictionLayoutId);
overridePrevious |= restrictionLayoutChanged;
boolean dbCreatedNoWorkspace =
sp.getBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, false);
if (dbCreatedNoWorkspace || overridePrevious) {//如果需要初始化favorite表,进入
SharedPreferences.Editor editor = sp.edit();
// First try layout from app restrictions if it was found
int workspaceResId = restrictionLayoutId;
// If the restrictions are not set, use the resource passed to this method
if (workspaceResId == 0) {//如果restrictionLayoutId为0,就使用调用方法传进来的resId
workspaceResId = origWorkspaceResId;
}
// Use default workspace resource if none provided
if (workspaceResId == 0) {//如果调用返回传进来的resId为0,去sharedpreference拿,默认是default_workspace
workspaceResId = sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID, R.xml.default_workspace);
} else {
editor.putInt(DEFAULT_WORKSPACE_RESOURCE_ID, workspaceResId);
}
// Populate favorites table with initial favorites
editor.remove(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED);
if (!dbCreatedNoWorkspace && overridePrevious) {
if (LOGD) Log.d(TAG, "Clearing old launcher database");
// Workspace has already been loaded, clear the database.
deleteDatabase();
}
mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), workspaceResId);//将xml中的内容写到数据库
editor.commit();
}
}
default_workspace.xml的内容:
2. final Cursor c = contentResolver.query(LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
查询数据,Uri展开就是:content://com.android.launcher2.settings/favorites?notify=true
3.将数据库中的每一行包装成一个ItemInfo对象。根据列itemType中的值决定这个ItemInfo的实例是ShortcutInfo,FolderInfo,LauncherAppWidgetInfo。跟缓存这些ItemInfo有关的结构:
// sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
// LauncherModel to their ids
static final HashMap sBgItemsIdMap = new HashMap();
// 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 sBgWorkspaceItems = new ArrayList();
// sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
static final ArrayList sBgAppWidgets =
new ArrayList();
// sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
static final HashMap sBgFolders = new HashMap();
所有ItemInfo都会存到sBgItemsIdMap 。然后ItemInfo根据它的属性container,判断该ItemInfo的直接容器是文件夹还是桌面,分别放到sBgFolders 和 sBgWorkspaceItems,而widget不能包含在folder中,直接容器只能是桌面,但是widget不会被放到sBgFolders 或者 sBgWorkspaceItems,而会被放到sBgAppWidgets 。
对于ShortcutInfo,除了xml中的那属性,还有一些属性是用代码去获取的,通过LoaderTask#getShortcutInfo()
public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, UserHandle user,
Context context,
Cursor c, int iconIndex, int titleIndex, HashMap
favorite表有那些列,列名和index在LauncherSettings$Favorite中。而LauncherProvider是可以供其他应用调用的,可以通过LauncherProvider对favorite表中的数据进行增删改查。
构建AppWidgetInfo:
AppWidgetManager widgets = AppWidgetManager.getInstance(context);
final AppWidgetProviderInfo provider =
widgets.getAppWidgetInfo(appWidgetId);
appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
provider.provider);
appWidgetInfo.id = id;
appWidgetInfo.screen = c.getInt(screenIndex);
appWidgetInfo.cellX = c.getInt(cellXIndex);
appWidgetInfo.cellY = c.getInt(cellYIndex);
appWidgetInfo.spanX = c.getInt(spanXIndex);
appWidgetInfo.spanY = c.getInt(spanYIndex);
int[] minSpan = Launcher.getMinSpanForWidget(context, provider);
appWidgetInfo.minSpanX = minSpan[0];
appWidgetInfo.minSpanY = minSpan[1];
widgetinfo主要持有appWidgetId和从AppWidgetManager获取的Provider。
bindWorkspace()
private void bindWorkspace(int synchronizeBindPage) {
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;
}
final boolean isLoadingSynchronously = (synchronizeBindPage > -1);
final int currentScreen = isLoadingSynchronously ? synchronizeBindPage :
oldCallbacks.getCurrentWorkspaceScreen();
// 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();
ArrayList workspaceItems = new ArrayList();
ArrayList appWidgets =
new ArrayList();
HashMap folders = new HashMap();
HashMap itemsIdMap = new HashMap();
synchronized (sBgLock) {
workspaceItems.addAll(sBgWorkspaceItems);
appWidgets.addAll(sBgAppWidgets);
folders.putAll(sBgFolders);
itemsIdMap.putAll(sBgItemsIdMap);
}
//将当前页面和其他页面的ItemInfo分开。让当前页面的ItemInfo先绑定
ArrayList currentWorkspaceItems = new ArrayList();
ArrayList otherWorkspaceItems = new ArrayList();
ArrayList currentAppWidgets =
new ArrayList();
ArrayList otherAppWidgets =
new ArrayList();
HashMap currentFolders = new HashMap();
HashMap otherFolders = new HashMap();
// Separate the items that are on the current screen, and all the other remaining items
filterCurrentWorkspaceItems(currentScreen, workspaceItems, currentWorkspaceItems,
otherWorkspaceItems);
filterCurrentAppWidgets(currentScreen, appWidgets, currentAppWidgets,
otherAppWidgets);
filterCurrentFolders(currentScreen, itemsIdMap, folders, currentFolders,
otherFolders);
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();//告诉Launcher开始绑定了,做一些必要但不是很重要的工作。Launcher实现了这个callback
}
}
};
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);//让上面的Runnable运行在主线程
// Load items on the current page
bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
currentFolders, null);//绑定当前页的ItemInfo。这个是关键的方法,下面是详细分析
if (isLoadingSynchronously) {//只有在同步加载时才会进入
r = new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
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)
mDeferredBindRunnables.clear();
bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
(isLoadingSynchronously ? mDeferredBindRunnables : null));//绑定其他页面的ItemInfo
// Tell the workspace that we're done binding items
r = new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.finishBindingItems();//告诉Launcher绑定结束了
}
// 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) {
mDeferredBindRunnables.add(r);
} else {
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
}
看看LoaderTask#bindWorkspaceItems():
private void bindWorkspaceItems(final Callbacks oldCallbacks,
final ArrayList workspaceItems,
final ArrayList appWidgets,
final HashMap folders,
ArrayList deferredBindRunnables) {
final boolean postOnMainThread = (deferredBindRunnables != null);
// Bind the workspace items
int N = workspaceItems.size();
//其中ITEMS_CHUNK类似是网络传输的窗口大小吧。决定了一个Runnable加载多少个ItemInfo。太大cpu利用不够,太小导致开过多的子线程
for (int i = 0; i < N; i += ITEMS_CHUNK) {//循环绑定ItemInfo
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 = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindItems(workspaceItems, start, start+chunkSize);//在Launcher中绑定shortcut和folder
}
}
};
if (postOnMainThread) {
deferredBindRunnables.add(r);
} else {
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
}
// Bind the folders
if (!folders.isEmpty()) {
final Runnable r = new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindFolders(folders);//不是真正绑定folder的地方,只是处理了其他东西
}
}
};
if (postOnMainThread) {
deferredBindRunnables.add(r);
} else {
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);//将上面的Runnable放到主线程运行
}
}
// 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) {
//绑定appWidget,由于涉及remoteview,即需要通过provider跨进程通信,所以比较复杂,后面会另起一篇分析
callbacks.bindAppWidget(widget);
}
}
};
if (postOnMainThread) {
deferredBindRunnables.add(r);
} else {
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);//将上面的Runnable放到主线程运行
}
}
}
看Launcher#bindItems()
public void bindItems(final ArrayList shortcuts, final int start, final int end) {
//waitUntilResume方法是如果调用了onResume就返回false,否则返回true并且将Runnable放入一个结构中,在onResume时调用。
if (waitUntilResume(new Runnable() {
public void run() {
bindItems(shortcuts, start, end);
}
})) {
return;
}
// Get the list of added shortcuts and intersect them with the set of shortcuts here
Set newApps = new HashSet();
//在installreceiver和uninstallreceiver中更新
newApps = mSharedPrefs.getStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, newApps);
Workspace workspace = mWorkspace;
for (int i = start; i < end; i++) {//循环绑定ItemInfo
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) {
continue;
}
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
ShortcutInfo info = (ShortcutInfo) item;
String uri = info.intent.toUri(0).toString();
View shortcut = createShortcut(info);//创建一个Shortcut对象
//指定哪一页,哪个格子,占位多少,shortcut和folder占位都为1x1,把shortcut添加到workspace
workspace.addInScreen(shortcut, item.container, item.screen, item.cellX,
item.cellY, 1, 1, false);
boolean animateIconUp = false;
synchronized (newApps) {
if (newApps.contains(uri)) {//判断当前的Item是否一个刚安装的app,是的话就需要动画一下
animateIconUp = newApps.remove(uri);
}
}
if (animateIconUp) {
// Prepare the view to be animated up
shortcut.setAlpha(0f);
shortcut.setScaleX(0f);
shortcut.setScaleY(0f);
mNewShortcutAnimatePage = item.screen;
if (!mNewShortcutAnimateViews.contains(shortcut)) {
mNewShortcutAnimateViews.add(shortcut);
}
}
break;
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
(ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
(FolderInfo) item, mIconCache);//创建folder的view
workspace.addInScreen(newFolder, item.container, item.screen, item.cellX,
item.cellY, 1, 1, false);//绑定folder
break;
}
}
workspace.requestLayout();//请求重新布局
}
folder_icon.xml
View createShortcut(ShortcutInfo info) {
return createShortcut(R.layout.application,
(ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
}
View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);
favorite.applyFromShortcutInfo(info, mIconCache);
favorite.setOnClickListener(this);
return favorite;
}
application.xml:
可以看到shortcut的实例时BubbleTextView类型的。
看看folder的创建,FolderIcon#fromXml():
static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
FolderInfo folderInfo, IconCache iconCache) {
@SuppressWarnings("all") // suppress dead code warning
final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION;
if (error) {
throw new IllegalStateException("DROP_IN_ANIMATION_DURATION must be greater than " +
"INITIAL_ITEM_ANIMATION_DURATION, as sequencing of adding first two items " +
"is dependent on this");
}
FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false);
icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
icon.mFolderName.setText(folderInfo.title);
icon.mPreviewBackground = (ImageView) icon.findViewById(R.id.preview_background);
icon.setTag(folderInfo);
icon.setOnClickListener(launcher);
icon.mInfo = folderInfo;
icon.mLauncher = launcher;
icon.setContentDescription(String.format(launcher.getString(R.string.folder_name_format),
folderInfo.title));
Folder folder = Folder.fromXml(launcher);
folder.setDragController(launcher.getDragController());
folder.setFolderIcon(icon);
folder.bind(folderInfo);
icon.mFolder = folder;
icon.mFolderRingAnimator = new FolderRingAnimator(launcher, icon);
folderInfo.addListener(icon);
return icon;
}
分析bindAppWidget()
public void bindAppWidget(final LauncherAppWidgetInfo item) {
if (waitUntilResume(new Runnable() {
public void run() {
bindAppWidget(item);
}
})) {
return;
}
final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
if (DEBUG_WIDGETS) {
Log.d(TAG, "bindAppWidget: " + item);
}
final Workspace workspace = mWorkspace;
final int appWidgetId = item.appWidgetId;
final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
if (DEBUG_WIDGETS) {
Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidgetInfo.provider);
}
//mAppWidgetHost是持有所有appwidgetview和管理他们的对象,这个hostview创建过程由LauncherAppWidgetHost实现,比较复杂。
item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
item.hostView.setTag(item);
item.onBindAppWidget(this);
workspace.addInScreen(item.hostView, item.container, item.screen, item.cellX,
item.cellY, item.spanX, item.spanY, false);//将这个widget添加到mWorkspace
addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
workspace.requestLayout();
if (DEBUG_WIDGETS) {
Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
+ (SystemClock.uptimeMillis()-start) + "ms");
}
}
LauncherAppWidgetHost#createView()
public final AppWidgetHostView createView(Context context, int appWidgetId,
AppWidgetProviderInfo appWidget) {
//这是appWidget在桌面的代表
AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);
view.setOnClickHandler(mOnClickHandler);
view.setAppWidget(appWidgetId, appWidget);
synchronized (mViews) {
mViews.put(appWidgetId, view);
}
RemoteViews views;
try {
//获取一个和目标进程通信的对象,这是appWidget在目标进程的代表
views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId);
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
view.updateAppWidget(views);//绑定目标进程的代表
return view;
}
protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
AppWidgetProviderInfo appWidget) {
return new AppWidgetHostView(context, mOnClickHandler);
}