【Launcher2源码解读】Launcher启动和加载
Launcher是一个特殊的App,属于系统软件,在按home键时会启动的App,在你的Activity中加入如下intent-fliter 的category之后就会被系统当作Launcher应用。
<category android:name="android.intent.category.HOME" /> <category android:name="android.intent.category.DEFAULT" />
一般我们在给视图绑定数据的时候会把它写在主线程onCreate中,如果需要加载时间,我们会用线程去辅助加载数据,Launcher启动时需要加载好App,shortcut,Folder等一系列的item,所以Launcher采用了Runable来加载数据,并且定义了一个规范的LauncherModel.Callback接口来定义具体的加载过程,步骤。
Launcher本身就是一个框架,从首次启动读取自定义的配置文件开始,操作的过程不断的更新db中存储的信息。下面来简单介绍启动流程。
那么具体到代码是如何启动并绑定数据的呢?
首先定义了接口:LauncherModel.Callback 看名字就知道是什么意思了。
public interface Callbacks { public boolean setLoadOnResume(); public int getCurrentWorkspaceScreen(); public void startBinding(); public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end); public void bindFolders(HashMap<Long, FolderInfo> folders); public void finishBindingItems(); public void bindAppWidget(LauncherAppWidgetInfo info); public void bindAllApplications(ArrayList<ApplicationInfo> apps); public void bindAppsAdded(ArrayList<ApplicationInfo> apps); public void bindAppsUpdated(ArrayList<ApplicationInfo> apps); public void bindAppsRemoved(ArrayList<String> packageNames, boolean permanent); public void bindPackagesUpdated(); public boolean isAllAppsVisible(); public boolean isAllAppsButtonRank(int rank); public void bindSearchablesChanged(); public void onPageBoundSynchronously(int page); }
该类中还定义了一个线程:该类就负责读书default_workspace.xml文件,读取数据库等其他的耗时操作。
private class LoaderTask implements Runnable {}
private static class DatabaseHelper extends SQLiteOpenHelper {}
private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) { Intent intent = new Intent(Intent.ACTION_MAIN, null); intent.addCategory(Intent.CATEGORY_LAUNCHER); ContentValues values = new ContentValues(); PackageManager packageManager = mContext.getPackageManager(); int allAppsButtonRank = mContext.getResources().getInteger( R.integer.hotseat_all_apps_index); int i = 0; try { XmlResourceParser parser = mContext.getResources().getXml( workspaceResourceId); AttributeSet attrs = Xml.asAttributeSet(parser); beginDocument(parser, TAG_FAVORITES); final int depth = parser.getDepth(); int type; while (((type = parser.next()) != XmlPullParser.END_TAG || parser .getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } boolean added = false; final String name = parser.getName(); TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite); long container = LauncherSettings.Favorites.CONTAINER_DESKTOP; if (a.hasValue(R.styleable.Favorite_container)) { container = Long.valueOf(a .getString(R.styleable.Favorite_container)); } String screen = a.getString(R.styleable.Favorite_screen); String x = a.getString(R.styleable.Favorite_x); String y = a.getString(R.styleable.Favorite_y); // If we are adding to the hotseat, the screen is used as // the position in the // hotseat. This screen can't be at position 0 because // AllApps is in the // zeroth position. if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT && Integer.valueOf(screen) == allAppsButtonRank) { throw new RuntimeException( "Invalid screen position for hotseat item"); } values.clear(); values.put(LauncherSettings.Favorites.CONTAINER, container); values.put(LauncherSettings.Favorites.SCREEN, screen); values.put(LauncherSettings.Favorites.CELLX, x); values.put(LauncherSettings.Favorites.CELLY, y); if (TAG_FAVORITE.equals(name)) { long id = addAppShortcut(db, values, a, packageManager, intent); added = id >= 0; }else if(TAG_TINYACTION.equals(name)){ long id=addTinyAction(db, values, a, intent);//写入数据库 added = id >= 0; } else if (TAG_SEARCH.equals(name)) { added = addSearchWidget(db, values); } else if (TAG_CLOCK.equals(name)) { added = addClockWidget(db, values); } else if (TAG_APPWIDGET.equals(name)) { added = addAppWidget(parser, attrs, type, db, values, a, packageManager); } else if (TAG_SHORTCUT.equals(name)) { long id = addUriShortcut(db, values, a); added = id >= 0; } else if (TAG_FOLDER.equals(name)) { String title; int titleResId = a.getResourceId( R.styleable.Favorite_title, -1); if (titleResId != -1) { title = mContext.getResources().getString( titleResId); } else { title = mContext.getResources().getString( R.string.folder_name); } values.put(LauncherSettings.Favorites.TITLE, title); long folderId = addFolder(db, values); added = folderId >= 0; ArrayList<Long> folderItems = new ArrayList<Long>(); int folderDepth = parser.getDepth(); while ((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > folderDepth) { if (type != XmlPullParser.START_TAG) { continue; } final String folder_item_name = parser.getName(); TypedArray ar = mContext.obtainStyledAttributes( attrs, R.styleable.Favorite); values.clear(); values.put(LauncherSettings.Favorites.CONTAINER, folderId); if (TAG_FAVORITE.equals(folder_item_name) && folderId >= 0) { long id = addAppShortcut(db, values, ar, packageManager, intent); if (id >= 0) { folderItems.add(id); } } else if (TAG_SHORTCUT.equals(folder_item_name) && folderId >= 0) { long id = addUriShortcut(db, values, ar); if (id >= 0) { folderItems.add(id); } } else { throw new RuntimeException("Folders can " + "contain only shortcuts"); } ar.recycle(); } // We can only have folders with >= 2 items, so we need // to remove the // folder and clean up if less than 2 items were // included, or some // failed to add, and less than 2 were actually added if (folderItems.size() < 2 && folderId >= 0) { // We just delete the folder and any items that made // it deleteId(db, folderId); if (folderItems.size() > 0) { deleteId(db, folderItems.get(0)); } added = false; } } if (added) i++; a.recycle(); } } catch (XmlPullParserException e) { Log.w(TAG, "Got exception parsing favorites.", e); } catch (IOException e) { Log.w(TAG, "Got exception parsing favorites.", e); } catch (RuntimeException e) { Log.w(TAG, "Got exception parsing favorites.", e); } return i; }
写入之后就是再从表中读出来,封装成相应的数据类型,然后显示在界面上。
LauncherModel中的LoaderTask线程的loadFavorit()方法读取db中的信息:
private void loadWorkspace() {}
private void 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(); // Make sure the default workspace is loaded, if needed mApp.getLauncherProvider().loadDefaultFavoritesIfNecessary(0); synchronized (sBgLock) { sBgWorkspaceItems.clear(); sBgAppWidgets.clear(); sBgFolders.clear(); sBgItemsIdMap.clear(); sBgDbIconCache.clear(); final ArrayList<Long> itemsToRemove = new ArrayList<Long>(); final Cursor c = contentResolver.query(LauncherSettings.Favorites.CONTENT_URI, 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 ItemInfo occupied[][][] = new ItemInfo[Launcher.SCREEN_COUNT + 1][mCellCountX + 1][mCellCountY + 1]; 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 shortcutTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SHORTCUT_TYPE); final int appWidgetIdIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.APPWIDGET_ID); 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()) { try { int itemType = c.getInt(itemTypeIndex); switch (itemType) { case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: intentDescription = c.getString(intentIndex); try { intent = Intent.parseUri(intentDescription, 0); } catch (URISyntaxException e) { 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, shortcutTypeIndex); // 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.intent = intent; info.id = c.getLong(idIndex); container = c.getInt(containerIndex); info.container = container; info.screen = c.getInt(screenIndex); info.cellX = c.getInt(cellXIndex); info.cellY = c.getInt(cellYIndex); // check & update map of what's occupied if (!checkItemPlacement(occupied, info)) { 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 { // Failed to load the shortcut, probably // because the // activity manager couldn't resolve it // (maybe the app // was uninstalled), or the db row was // somehow screwed up. // Delete it. id = c.getLong(idIndex); Log.e(TAG, "Error loading shortcut " + id + ", removing it"); contentResolver.delete(LauncherSettings.Favorites.getContentUri(id, false), null, null); } 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.screen = c.getInt(screenIndex); folderInfo.cellX = c.getInt(cellXIndex); folderInfo.cellY = c.getInt(cellYIndex); // check & update map of what's occupied if (!checkItemPlacement(occupied, folderInfo)) { 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); 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.sDumpLogs.add(log); itemsToRemove.add(id); } else { 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]; 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); // check & update map of what's occupied if (!checkItemPlacement(occupied, appWidgetInfo)) { break; } sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo); sBgAppWidgets.add(appWidgetInfo); } break; } } catch (Exception e) { Log.w(TAG, "Desktop items loading interrupted:", e); } } } finally { c.close(); } 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 (DEBUG_LOADERS) { Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis() - t) + "ms"); Log.d(TAG, "workspace layout: "); for (int y = 0; y < mCellCountY; y++) { String line = ""; for (int s = 0; s < Launcher.SCREEN_COUNT; s++) { if (s > 0) { line += " | "; } for (int x = 0; x < mCellCountX; x++) { line += ((occupied[s][x][y] != null) ? "#" : "."); } } Log.d(TAG, "[ " + line + " ]"); } } } }
然后在执行的过程中,按照各自的时机去调用Callback接口的一系列方法
接下来就是Activity了
Launcher.java 实现了LauncherModel接口,并且重写的LauncherModel.Callback的所有方法。在相应的方法中就可以取到数据了。