【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 {}


LauncherProvider.java 中定义数据库的类:

private static class DatabaseHelper extends SQLiteOpenHelper {}


其中读取文件的方法,其中用XmlPull解析相应的节点,这里还用到了Android本身读取节点属性的TypeArray来快速读取节点属性,该方法在LauncherMidel中的loadworkspace调用。如此以来,我们就可以在桌面上预制像shortcut一样的自定义图标了,铜鼓预制图标去完成一些功能。读完之后并写在了db中。

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的所有方法。在相应的方法中就可以取到数据了。



posted @ 2015-04-16 14:07  顾明伟  阅读(851)  评论(0编辑  收藏  举报