《Android进阶》之第二篇 launcher
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) {
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); return true;
}
return false;
}
allapp这就是加载每个icon到view的那个位置
1、将就的地方 launcher.java
static int[] getSpanForWidget(Context context, ComponentName component, int minWidth,
int minHeight) {
// Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context, component, null); Rect padding = new Rect(20, 20, 300, 300);
// We want to account for the extra amount of padding that we are adding to the widget
// to ensure that it gets the full amount of space that it has requested
int requiredWidth = minWidth + padding.left + padding.right;
int requiredHeight = minHeight + padding.top + padding.bottom;
return CellLayout.rectToCell(requiredWidth, requiredHeight, null);
}
2、launcher.java
/**
* Add the icons for all apps.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindAllApplications(final ArrayList<AppInfo> apps) {
if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
if (mIntentsOnWorkspaceFromUpgradePath != null) {
if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) {
getHotseat().addAllAppsFolder(mIconCache, apps,
mIntentsOnWorkspaceFromUpgradePath, Launcher.this, mWorkspace);
}
mIntentsOnWorkspaceFromUpgradePath = null;
}
} else {
if (mAppsCustomizeContent != null) {
mAppsCustomizeContent.setApps(apps);
}
}
}
3、判断是否在桌面
public boolean isAllAppsVisible() {
return (mState == State.APPS_CUSTOMIZE) || (mOnResumeState == State.APPS_CUSTOMIZE);
}
1、Callbacks接口
LauncherModel里面,需要先分析一个Callbacks接口。
public interface Callbacks {
public boolean setLoadOnResume();
public int getCurrentWorkspaceScreen();
public void startBinding();
public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
boolean forceAnimateIcons);
public void bindScreens(ArrayList<Long> orderedScreenIds);
public void bindAddScreens(ArrayList<Long> orderedScreenIds);
public void bindFolders(HashMap<Long,FolderInfo> folders);
public void finishBindingItems(boolean upgradePath);
public void bindAppWidget(LauncherAppWidgetInfo info);
public void bindAllApplications(ArrayList<AppInfo> apps);
public void bindAppsAdded(ArrayList<Long> newScreens,
ArrayList<ItemInfo> addNotAnimated,
ArrayList<ItemInfo> addAnimated,
ArrayList<AppInfo> addedApps);
public void bindAppsUpdated(ArrayList<AppInfo> apps);
public void bindComponentsRemoved(ArrayList<String> packageNames,
ArrayList<AppInfo> appInfos,
boolean matchPackageNamesOnly);
public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
public void bindSearchablesChanged();
public boolean isAllAppsButtonRank(int rank);
public void onPageBoundSynchronously(int page);
public void dumpLogsToLocalData();
}
Callbacks接口提供了很多接口,用于返回相关的数据给Launcher模块,下面我们对每个接口作用做个阐释。
setLoadOnResume() :当Launcher.java类的Activity处于onPause的时候,如果重新恢复,需要调用onResume,此时需要在onResume调用这个接口,恢复Launcher数据。
getCurrentWorkspace():获取屏幕序号(0~4)
startBinding():通知Launcher开始加载数据。清空容器数据,重新加载
bindItems(ArrayList<ItemInfo> shortcuts, int start, int end):加载App shortcut、Live Folder、widget到Launcher相关容器。
bindFolders(HashMap<Long, FolderInfo> folders):加载folder的内容
finishBindingItems():数据加载完成。
bindAppWidget(LauncherAppWidgetInfo item):workspace加载APP 快捷方式
bindAllApplications(final ArrayList<ApplicationInfo> apps):所有应用列表接着APP图标数据
bindAppsAdded(ArrayList<ApplicationInfo> apps):通知Launcher新安装了一个APP,更新数据。
bindAppsUpdated(ArrayList<ApplicationInfo> apps):通知Launcher一个APP更新了。(覆盖安装)
bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent):通知Launcher,应用被删除
bindPackagesUpdated():多个应用更新。
isAllAppsVisible():返回所有应用列表是否可见状态。
bindSearchablesChanged():Google搜索栏或者删除区域发生变化时通知Launcher
2、数据加载流程
Launcher.java类继承了Callbacks接口,并实现了该接口。LauncherModel里面会调用这些接口,反馈数据和状态给Launcher。数据加载总体分为两部分,一部分是加载workspace的数据,另一部分是加载All APP界面的数据。
3、startLoader()
下面我们先分析startLoader()接口,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.getContext(), isLaunching);
if (synchronousBindPage > -1 && mAllAppsLoaded && mWorkspaceLoaded) {
mLoaderTask.runBindSynchronousPage(synchronousBindPage);
} else {
sWorkerThread.setPriority(Thread.NORM_PRIORITY);
sWorker.post(mLoaderTask);
}
}
}
}
4、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);
}
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(); // 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 (AppsCustomizePagedView.DISABLE_ALL_APPS) {
// 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;
}
}
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(); // second step
if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
loadAndBindAllApps();
5、workspace加载数据
loadAndBindWorkspace()方法主要就是执行loadWorkspace()和 bindWorkspace()方法。
下面分别对这两个方法进行分析。
/** 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;
}
workspace的数据加载总的来说也是按照元素属性来区分加载,分为App快捷方式、Widget、Folder元素。
这几个元素分别加载到不同的容器里面。其中sItemsIdMap保存所有元素的id和ItemInfo组成的映射。其他
元素分别加载到3个不同的容器里面,用于后面绑定数据用。
/** Returns whether this is an upgradge path */
private boolean loadWorkspace() {
final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; final Context context = mContext;
final ContentResolver contentResolver = context.getContentResolver();
final PackageManager manager = context.getPackageManager();
final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
final boolean isSafeMode = manager.isSafeMode(); LauncherAppState app = LauncherAppState.getInstance();
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
int countX = (int) grid.numColumns;
int countY = (int) grid.numRows; // Make sure the default workspace is loaded, if needed
LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(0); // Check if we need to do any upgrade-path logic
boolean loadedOldDb = LauncherAppState.getLauncherProvider().justLoadedOldDb(); synchronized (sBgLock) {
clearSBgDataStructures(); final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
final Cursor c = contentResolver.query(contentUri, null, null, null, null); // +1 for the hotseat (it can be larger than the workspace)
// Load workspace in reverse order to ensure that latest items are loaded first (and
// before any earlier duplicates)
final HashMap<Long, ItemInfo[][]> occupied = new HashMap<Long, ItemInfo[][]>(); try {
final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
final int intentIndex = c.getColumnIndexOrThrow
(LauncherSettings.Favorites.INTENT);
final int titleIndex = c.getColumnIndexOrThrow
(LauncherSettings.Favorites.TITLE);
final int iconTypeIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.ICON_TYPE);
final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
final int iconPackageIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.ICON_PACKAGE);
final int iconResourceIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.ICON_RESOURCE);
final int containerIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.CONTAINER);
final int itemTypeIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.ITEM_TYPE);
final int appWidgetIdIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.APPWIDGET_ID);
final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.APPWIDGET_PROVIDER);
final int screenIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.SCREEN);
final int cellXIndex = c.getColumnIndexOrThrow
(LauncherSettings.Favorites.CELLX);
final int cellYIndex = c.getColumnIndexOrThrow
(LauncherSettings.Favorites.CELLY);
final int spanXIndex = c.getColumnIndexOrThrow
(LauncherSettings.Favorites.SPANX);
final int spanYIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.SPANY);
//final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
//final int displayModeIndex = c.getColumnIndexOrThrow(
// LauncherSettings.Favorites.DISPLAY_MODE); ShortcutInfo info;
String intentDescription;
LauncherAppWidgetInfo appWidgetInfo;
int container;
long id;
Intent intent; while (!mStopped && c.moveToNext()) {
AtomicBoolean deleteOnItemOverlap = new AtomicBoolean(false);
try {
int itemType = c.getInt(itemTypeIndex); switch (itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
id = c.getLong(idIndex);
intentDescription = c.getString(intentIndex);
try {
intent = Intent.parseUri(intentDescription, 0);
ComponentName cn = intent.getComponent();
if (cn != null && !isValidPackageComponent(manager, cn)) {
if (!mAppsCanBeOnRemoveableStorage) {
// Log the invalid package, and remove it from the db
Launcher.addDumpLog(TAG, "Invalid package removed: " + cn, true);
itemsToRemove.add(id);
} else {
// If apps can be on external storage, then we just
// leave them for the user to remove (maybe add
// visual treatment to it)
Launcher.addDumpLog(TAG, "Invalid package found: " + cn, true);
}
continue;
}
} catch (URISyntaxException e) {
Launcher.addDumpLog(TAG, "Invalid uri: " + intentDescription, true);
continue;
} if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
info = getShortcutInfo(manager, intent, context, c, iconIndex,
titleIndex, mLabelCache);
} else {
info = getShortcutInfo(c, context, iconTypeIndex,
iconPackageIndex, iconResourceIndex, iconIndex,
titleIndex); // App shortcuts that used to be automatically added to Launcher
// didn't always have the correct intent flags set, so do that
// here
if (intent.getAction() != null &&
intent.getCategories() != null &&
intent.getAction().equals(Intent.ACTION_MAIN) &&
intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
}
} if (info != null) {
info.id = id;
info.intent = intent;
container = c.getInt(containerIndex);
info.container = container;
info.screenId = c.getInt(screenIndex);
info.cellX = c.getInt(cellXIndex);
info.cellY = c.getInt(cellYIndex);
info.spanX = 1;
info.spanY = 1;
// Skip loading items that are out of bounds
if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
if (checkItemDimensions(info)) {
Launcher.addDumpLog(TAG, "Skipped loading out of bounds shortcut: "
+ info + ", " + grid.numColumns + "x" + grid.numRows, true);
continue;
}
}
// check & update map of what's occupied
deleteOnItemOverlap.set(false);
if (!checkItemPlacement(occupied, info, deleteOnItemOverlap)) {
if (deleteOnItemOverlap.get()) {
itemsToRemove.add(id);
}
break;
} switch (container) {
case LauncherSettings.Favorites.CONTAINER_DESKTOP:
case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
sBgWorkspaceItems.add(info);
break;
default:
// Item is in a user folder
FolderInfo folderInfo =
findOrMakeFolder(sBgFolders, container);
folderInfo.add(info);
break;
}
sBgItemsIdMap.put(info.id, info); // now that we've loaded everthing re-save it with the
// icon in case it disappears somehow.
queueIconToBeChecked(sBgDbIconCache, info, c, iconIndex);
} else {
throw new RuntimeException("Unexpected null ShortcutInfo");
}
break; case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
id = c.getLong(idIndex);
FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id); folderInfo.title = c.getString(titleIndex);
folderInfo.id = id;
container = c.getInt(containerIndex);
folderInfo.container = container;
folderInfo.screenId = c.getInt(screenIndex);
folderInfo.cellX = c.getInt(cellXIndex);
folderInfo.cellY = c.getInt(cellYIndex);
folderInfo.spanX = 1;
folderInfo.spanY = 1; // Skip loading items that are out of bounds
if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
if (checkItemDimensions(folderInfo)) {
Log.d(TAG, "Skipped loading out of bounds folder");
continue;
}
}
// check & update map of what's occupied
deleteOnItemOverlap.set(false);
if (!checkItemPlacement(occupied, folderInfo,
deleteOnItemOverlap)) {
if (deleteOnItemOverlap.get()) {
itemsToRemove.add(id);
}
break;
} switch (container) {
case LauncherSettings.Favorites.CONTAINER_DESKTOP:
case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
sBgWorkspaceItems.add(folderInfo);
break;
} sBgItemsIdMap.put(folderInfo.id, folderInfo);
sBgFolders.put(folderInfo.id, folderInfo);
break; case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
// Read all Launcher-specific widget details
int appWidgetId = c.getInt(appWidgetIdIndex);
String savedProvider = c.getString(appWidgetProviderIndex); id = c.getLong(idIndex); final AppWidgetProviderInfo provider =
widgets.getAppWidgetInfo(appWidgetId); if (!isSafeMode && (provider == null || provider.provider == null ||
provider.provider.getPackageName() == null)) {
String log = "Deleting widget that isn't installed anymore: id="
+ id + " appWidgetId=" + appWidgetId;
Log.e(TAG, log);
Launcher.addDumpLog(TAG, log, false);
itemsToRemove.add(id);
} else {
appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
provider.provider);
appWidgetInfo.id = id;
appWidgetInfo.screenId = 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]; container = c.getInt(containerIndex);
if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
Log.e(TAG, "Widget found where container != " +
"CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
continue;
} appWidgetInfo.container = c.getInt(containerIndex);
// Skip loading items that are out of bounds
if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
if (checkItemDimensions(appWidgetInfo)) {
Log.d(TAG, "Skipped loading out of bounds app widget");
continue;
}
}
// check & update map of what's occupied
deleteOnItemOverlap.set(false);
if (!checkItemPlacement(occupied, appWidgetInfo,
deleteOnItemOverlap)) {
if (deleteOnItemOverlap.get()) {
itemsToRemove.add(id);
}
break;
}
String providerName = provider.provider.flattenToString();
if (!providerName.equals(savedProvider)) {
ContentValues values = new ContentValues();
values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER,
providerName);
String where = BaseColumns._ID + "= ?";
String[] args = {Integer.toString(c.getInt(idIndex))};
contentResolver.update(contentUri, values, where, args);
}
sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
sBgAppWidgets.add(appWidgetInfo);
}
break;
}
} catch (Exception e) {
Launcher.addDumpLog(TAG, "Desktop items loading interrupted: " + e, true);
}
}
} finally {
if (c != null) {
c.close();
}
} // Break early if we've stopped loading
if (mStopped) {
clearSBgDataStructures();
return false;
} if (itemsToRemove.size() > 0) {
ContentProviderClient client = contentResolver.acquireContentProviderClient(
LauncherSettings.Favorites.CONTENT_URI);
// Remove dead items
for (long id : itemsToRemove) {
if (DEBUG_LOADERS) {
Log.d(TAG, "Removed id = " + id);
}
// Don't notify content observers
try {
client.delete(LauncherSettings.Favorites.getContentUri(id, false),
null, null);
} catch (RemoteException e) {
Log.w(TAG, "Could not remove id = " + id);
}
}
} if (loadedOldDb) {
long maxScreenId = 0;
// If we're importing we use the old screen order.
for (ItemInfo item: sBgItemsIdMap.values()) {
long screenId = item.screenId;
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
!sBgWorkspaceScreens.contains(screenId)) {
sBgWorkspaceScreens.add(screenId);
if (screenId > maxScreenId) {
maxScreenId = screenId;
}
}
}
Collections.sort(sBgWorkspaceScreens); LauncherAppState.getLauncherProvider().updateMaxScreenId(maxScreenId);
updateWorkspaceScreenOrder(context, sBgWorkspaceScreens); // Update the max item id after we load an old db
long maxItemId = 0;
// If we're importing we use the old screen order.
for (ItemInfo item: sBgItemsIdMap.values()) {
maxItemId = Math.max(maxItemId, item.id);
}
LauncherAppState.getLauncherProvider().updateMaxItemId(maxItemId);
} else {
TreeMap<Integer, Long> orderedScreens = loadWorkspaceScreensDb(mContext);
for (Integer i : orderedScreens.keySet()) {
sBgWorkspaceScreens.add(orderedScreens.get(i));
} // Remove any empty screens
ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
for (ItemInfo item: sBgItemsIdMap.values()) {
long screenId = item.screenId;
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
unusedScreens.contains(screenId)) {
unusedScreens.remove(screenId);
}
} // If there are any empty screens remove them, and update.
if (unusedScreens.size() != 0) {
sBgWorkspaceScreens.removeAll(unusedScreens);
updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
}
} if (DEBUG_LOADERS) {
Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
Log.d(TAG, "workspace layout: ");
int nScreens = occupied.size();
for (int y = 0; y < countY; y++) {
String line = ""; Iterator<Long> iter = occupied.keySet().iterator();
while (iter.hasNext()) {
long screenId = iter.next();
if (screenId > 0) {
line += " | ";
}
for (int x = 0; x < countX; x++) {
line += ((occupied.get(screenId)[x][y] != null) ? "#" : ".");
}
}
Log.d(TAG, "[ " + line + " ]");
}
}
}
return loadedOldDb;
}
6、workspace绑定数据
Launcher的内容绑定分为五步:分别对应着startBinding()、bindItems()、bindFolders()、 bindAppWidgets()、
finishBindingItems()的调用。下面针对bindWorkspace做个简单的流程分析。
/**
* 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;
} 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<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);
} 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>(); // 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();
}
}
};
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) {
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)); // 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) {
mDeferredBindRunnables.add(r);
} else {
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
}
上面就是Launcher的workspace绑定数据的过程,跟加载数据过程很相似,也是区分3中类型的元素进行加载。
下面我们总结一下,workspace的加载和绑定数据的过程。我们现在回头看,可以发现,其实workspace里面就是
存放了3中数据ItemInfo、FolderInfo、LauncherAppWidgetInfo。分别对应我们的APP快捷方式、文件夹、Widget
数据。其中FolderInfo、LauncherAppWidgetInfo都是继承了ItemInfo。数据加载过程,就是从Launcher的数据库
读取数据然后按元素属性分别放到3个ArrayList里面。绑定数据过程就是把3个ArrayList的队列关联到Launcher界面里面。
7、ALL 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 PackageManager packageManager = mContext.getPackageManager();
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); // Clear the list of apps
mBgAllAppsList.clear(); // Query for the set of apps
final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
if (DEBUG_LOADERS) {
Log.d(TAG, "queryIntentActivities took "
+ (SystemClock.uptimeMillis()-qiaTime) + "ms");
Log.d(TAG, "queryIntentActivities got " + apps.size() + " apps");
}
// Fail if we don't have any apps
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(packageManager, mLabelCache));
if (DEBUG_LOADERS) {
Log.d(TAG, "sort took "
+ (SystemClock.uptimeMillis()-sortTime) + "ms");
} // Create the ApplicationInfos
for (int i = 0; i < apps.size(); i++) {
ResolveInfo app = apps.get(i);
// This builds the icon bitmaps.
mBgAllAppsList.add(new AppInfo(packageManager, app,
mIconCache, mLabelCache));
} // Huh? Shouldn't this be inside the Runnable below?
final ArrayList<AppInfo> added = mBgAllAppsList.added;
mBgAllAppsList.added = new ArrayList<AppInfo>(); // 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");
}
}
// Post callback on main thread 很重要
AppInfo由四部分组成
List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
// Create the ApplicationInfos
for (int i = 0; i < apps.size(); i++) {
ResolveInfo app = apps.get(i);
// This builds the icon bitmaps.
mBgAllAppsList.add(new AppInfo(packageManager, app,
mIconCache, mLabelCache));
}
AllAPP的数据加载和绑定跟workspace的差不多,也是先加载数据然后绑定数据,通知Launcher。加载数据的时候
从PackageManager获取所有已经安装的APK包信息,然后过滤只包含需要显示在所有应用列表的应用,需要包含
ACTION_MAIN和CATEGORY_LAUNCHER两个属性。这个我们在编写应用程序的时候都应该知道。
AllAPP加载跟workspace不同的地方是加载的同时,完成数据绑定的操作,也就是说第一次加载AllAPP页面的数据,
会同时绑定数据到Launcher。第二次需要加载的时候,只会把数据直接绑定到Launcher,而不会重新搜索加载数据。
Launcher启动加载和绑定数据就是这样完成。绑定完数据,Launcher就可以运行。
绑定数据,回调接口
/**
* Add the icons for all apps.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindAllApplications(final ArrayList<AppInfo> apps) {
if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
if (mIntentsOnWorkspaceFromUpgradePath != null) {
if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) {
getHotseat().addAllAppsFolder(mIconCache, apps,
mIntentsOnWorkspaceFromUpgradePath, Launcher.this, mWorkspace);
}
mIntentsOnWorkspaceFromUpgradePath = null;
}
} else {
if (mAppsCustomizeContent != null) {
mAppsCustomizeContent.setApps(apps);
}
}
}
8、ALL APP显示:
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;
int startIndex = page * numCells;
int endIndex = Math.min(startIndex + numCells, mApps.size());
AppsCustomizeCellLayout layout = (AppsCustomizeCellLayout) getPageAt(page); layout.removeAllViewsOnPage();
ArrayList<Object> items = new ArrayList<Object>();
ArrayList<Bitmap> images = new ArrayList<Bitmap>();
for (int i = startIndex; i < endIndex; ++i) {
AppInfo info = mApps.get(i);
PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate(
R.layout.apps_customize_application, layout, false);
icon.applyFromApplicationInfo(info, true, this);
icon.setOnClickListener(this);
icon.setOnLongClickListener(this);
icon.setOnTouchListener(this);
icon.setOnKeyListener(this); int index = i - startIndex;
int x = index % mCellCountX;
int y = index / mCellCountX;
if (isRtl) {
x = mCellCountX - x - 1;
}
layout.addViewToCellLayout(icon, -1, i, new CellLayout.LayoutParams(x,y, 1,1), false); items.add(info);
images.add(info.iconBitmap);
} enableHwLayersOnVisiblePages();
}
public void applyFromApplicationInfo(AppInfo info, boolean scaleUp,
PagedViewIcon.PressedCallback cb) {
mIcon = info.iconBitmap;
mPressedCallback = cb;
setCompoundDrawables(null, Utilities.createIconDrawable(mIcon),
null, null);
setText(info.title);
setTag(info);
}
将图标和title加载到控件上
启动应用程序:
@Override
public void onClick(View v) {
// When we have exited all apps or are in transition, disregard clicks
if (!mLauncher.isAllAppsVisible() ||
mLauncher.getWorkspace().isSwitchingState()) return; if (v instanceof PagedViewIcon) {
// Animate some feedback to the click
final AppInfo appInfo = (AppInfo) v.getTag(); // Lock the drawable state to pressed until we return to Launcher
if (mPressedIcon != null) {
mPressedIcon.lockDrawableState();
}
mLauncher.startActivitySafely(v, appInfo.intent, appInfo);
mLauncher.getStats().recordLaunch(appInfo.intent);
} else if (v instanceof PagedViewWidget) {
// Let the user know that they have to long press to add a widget
if (mWidgetInstructionToast != null) {
mWidgetInstructionToast.cancel();
}
mWidgetInstructionToast = Toast.makeText(getContext(),R.string.long_press_widget_to_add,
Toast.LENGTH_SHORT);
mWidgetInstructionToast.show(); // Create a little animation to show that the widget can move
float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
final ImageView p = (ImageView) v.findViewById(R.id.widget_preview);
AnimatorSet bounce = LauncherAnimUtils.createAnimatorSet();
ValueAnimator tyuAnim = LauncherAnimUtils.ofFloat(p, "translationY", offsetY);
tyuAnim.setDuration(125);
ValueAnimator tydAnim = LauncherAnimUtils.ofFloat(p, "translationY", 0f);
tydAnim.setDuration(100);
bounce.play(tyuAnim).before(tydAnim);
bounce.setInterpolator(new AccelerateInterpolator());
bounce.start();
}
}
卸载程序后,如何更新页面,探究:
protected void invalidatePageData(int currentPage, boolean immediateAndOnly) {
if (!mIsDataReady) {
return;
} if (mContentIsRefreshable) {
// Force all scrolling-related behavior to end
mScroller.forceFinished(true);
mNextPage = INVALID_PAGE; // 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();
}
}
查询loadAssociatedPages
安装程序后桌面出错:
《Android进阶》之第二篇 launcher的更多相关文章
- [转]Android开源项目第二篇——工具库篇
本文为那些不错的Android开源项目第二篇--开发工具库篇,主要介绍常用的开发库,包括依赖注入框架.图片缓存.网络相关.数据库ORM建模.Android公共库.Android 高版本向低版本兼容.多 ...
- Android开源项目第二篇——工具库篇
本文为那些不错的Android开源项目第二篇——开发工具库篇,**主要介绍常用的开发库,包括依赖注入框架.图片缓存.网络相关.数据库ORM建模.Android公共库.Android 高版本向低版本兼容 ...
- android 串口开发第二篇:利用jni实现android和串口通信
一:串口通信简介 由于串口开发涉及到jni,所以开发环境需要支持ndk开发,如果未配置ndk配置的朋友,或者对jni不熟悉的朋友,请查看上一篇文章,android 串口开发第一篇:搭建ndk开发环境以 ...
- Android JNI入门第二篇——Java参数类型与本地参数类型对照
前面一篇通过简单的例子介绍了android中JNI的使用.这一篇从基础上了解一些Java参数类型与本地参数类型区别. 1) java中的返回值void和JNI中的void是完全对应的哦! ...
- Android基础学习第二篇—Activity
写在前面的话: 1. 最近在自学Android,也是边看书边写一些Demo,由于知识点越来越多,脑子越来越记不清楚,所以打算写成读书笔记,供以后查看,也算是把自己学到所理解的东西写出来,献丑,如有不对 ...
- Android学习笔记(第二篇)View中的五大布局
PS:人不要低估自己的实力,但是也不能高估自己的能力.凡事谦为本... 学习内容: 1.用户界面View中的五大布局... i.首先介绍一下view的概念 view是什么呢?我们已经知道一个Act ...
- Android窗口系统第二篇---Window的添加过程
以前写过客户端Window的创建过程,大概是这样子的.我们一开始从Thread中的handleLaunchActivity方法开始分析,首先加载Activity的字节码文件,利用反射的方式创建一个Ac ...
- Python进阶【第二篇】多线程、消息队列queue
1.Python多线程.多进程 目的提高并发 1.一个应用程序,可以有多进程和多线程 2.默认:单进程,单线程 3.单进程,多线程 IO操作,不占用CPU python的多线程:IO操作,多线程提供并 ...
- 深入理解javascript函数进阶系列第二篇——函数柯里化
前面的话 函数柯里化currying的概念最早由俄国数学家Moses Schönfinkel发明,而后由著名的数理逻辑学家Haskell Curry将其丰富和发展,currying由此得名.本文将详细 ...
随机推荐
- Redis基础学习(四)—Redis的持久化
一.概述 Redis的强大性能很大程度上都是因为数据时存在内存中的,然而当Redis重启时,所有存储在内存中的数据将会丢失,所以我们要将内存中的数据持久化. Redis支持两种数据持久化的方 ...
- java中GUI的awt和Swing的知识点
刚刚学习了java的GUI,写了几个程序,基本熟悉了awt和Swing,下面和大家分享一下知识点 1.JFrame的层次结构 参考:http://tieba.baidu.com/p/200421612 ...
- File类遍历目录及文件
1. 构造函数 File(String args0)//使用一个表示文件或目录的路径的字符串创建一个File对象 File(URL args0)//使用一个URL对象创建File对象 File(Fil ...
- 【one day one linux】find 用法详解小记
find命令的功能很强大,查找文件的选项很多,所以这是一个很实用并且很常用的linux命令.但是他有个缺点就是搜索的时候比较慢的.而与之相对的有一个locate命令. find的命令格式 find ...
- Java 工具类—日期获得,随机数,系统命令,数据类型转换
package tems; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; ...
- xmlplus 组件设计系列之零 - xmlplus 简介
xmlplus 是什么 xmlplus 是博主写的一个 JavaScript 框架,用于快速开发前后端项目. xmlplus 基于组件设计,组件是基本的构造块.评价组件设计好坏的一个重要标准是封装度. ...
- Linux系统操作指令汇总
1.系统配置 arch 显示机器的处理器架构(1) uname -m 显示机器的处理器架构(2) uname -r 显示正在使用的内核版本 dmidecode -q 显示硬件系统部件 - (SMBIO ...
- php函数每日学习二十个
数学函数 1,abs() 求绝对值 2,ceil() 进一法取整 3,floor() 舍去法取整 4,fmod()对浮点数进行取余 例如fmod(5.7,1.3) 5,pow() 返回数的n次方 po ...
- 关于jstl.jar引用问题及解决方法
在前文SSM说到因为从MyEclipse换成了Eclipse.有些架包自动缺失. 造成:"org.apache.jasper.JasperException: This absolute u ...
- nodejs版本管理工具NVM(Node Version Mene)
最近打算用心学习nodejs,所以在学习中了解到NVM-nodejs的版本管理工具,下面我就记录下我学习并且安装的详细过程,请大神们放过~~第一步.你要先把你本机上安装的nodejs以及npm相关的东 ...