Launcher8.0启动流程的第六步中loadworkspace的第1小步是获取数据库,在没有布局时,需要读取手机上的布局xml文件,通过第三个操作我们创建了新的数据库并选取了xml文件那么如何解析该xml文件呢?
答案是loadFavorites方法
该方法把loader对应的xml布局文件放到数据库中。
源码如下:
int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
ArrayList<Long> screenIds = new ArrayList<Long>();
int count = loader.loadLayout(db, screenIds);
Collections.sort(screenIds);
int rank = 0;
ContentValues values = new ContentValues();
for (Long id : screenIds) {
values.clear();
values.put(LauncherSettings.WorkspaceScreens._ID, id);
values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
if (dbInsertAndCheck(this, db, WorkspaceScreens.TABLE_NAME, null, values) < 0) {
throw new RuntimeException("Failed initialize screen table"
+ "from default layout");
}
rank++;
}
return count;
}
}
从代码直观看,获取了screen的id并通过dbInsertAndCheck()方法存入数据库。
在此之前,int count = loader.loadLayout(db, screenIds); 此方法完成了桌面图标的数据库写入,源码如下:
public int loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds) {
return parseLayout(mLayoutId, screenIds);
}
protected int parseLayout(int layoutId, ArrayList<Long> screenIds)
throws XmlPullParserException, IOException {
XmlResourceParser parser = mSourceRes.getXml(layoutId);
beginDocument(parser, mRootTag);
final int depth = parser.getDepth();
int type;
HashMap<String, TagParser> tagParserMap = getLayoutElementsMap();
int count = 0;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
count += parseAndAddNode(parser, tagParserMap, screenIds);
}
return count;
}
关键代码parseLayout。 先找loader中的xml获取mSourceRes.getXml(layoutId) 布局参数,实际将光标放到布局xml的开端。
随后while循环,根据关键词依次读取,读取的方法是是parseAndAddNode(parser, tagParserMap, screenIds);
protected int parseAndAddNode(
XmlResourceParser parser,
HashMap<String, TagParser> tagParserMap,
ArrayList<Long> screenIds)
throws XmlPullParserException, IOException {
if (TAG_INCLUDE.equals(parser.getName())) {
final int resId = getAttributeResourceValue(parser, ATTR_WORKSPACE, 0);
if (resId != 0) {
// recursively load some more favorites, why not?
return parseLayout(resId, screenIds);
} else {
return 0;
}
}
mValues.clear();
parseContainerAndScreen(parser, mTemp);
final long container = mTemp[0];
final long screenId = mTemp[1];
mValues.put(Favorites.CONTAINER, container);
mValues.put(Favorites.SCREEN, screenId);
mValues.put(Favorites.CELLX,
convertToDistanceFromEnd(getAttributeValue(parser, ATTR_X), mColumnCount));
mValues.put(Favorites.CELLY,
convertToDistanceFromEnd(getAttributeValue(parser, ATTR_Y), mRowCount));
TagParser tagParser = tagParserMap.get(parser.getName());
if (tagParser == null) {
if (LOGD) Log.d(TAG, "Ignoring unknown element tag: " + parser.getName());
return 0;
}
long newElementId = tagParser.parseAndAdd(parser);
if (newElementId >= 0) {
// Keep track of the set of screens which need to be added to the db.
if (!screenIds.contains(screenId) &&
container == Favorites.CONTAINER_DESKTOP) {
screenIds.add(screenId);
}
return 1;
}
return 0;
}
以上我们看到获取了CONTAINER SCREEN CELLX CELLY等通用关键词,随后调用方法
TagParser tagParser = tagParserMap.get(parser.getName());获取每个词条的图标类型,
类型有application shortcut widget folder等等。
long newElementId = tagParser.parseAndAdd(parser);
而后返回的结果是在数据库中生成的数据库词条的id号。Id号是依次列出的。
不同tagParser下的parseAndAdd方法:
appShortcut类型是图标,就是应用的图标最常用的。
而shortcut是快捷方式,通常是应用往桌面上放置的快速按钮,比如快速拨号,具体某个人的通讯录,直接打开某个网页的链接等等。
主要关键词有packagename,intent等,下面以appshortcut为例。
protected class AppShortcutParser implements TagParser {
@Override
public long parseAndAdd(XmlResourceParser parser) {
final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
ActivityInfo info;
try {
ComponentName cn;
try {
cn = new ComponentName(packageName, className);
info = mPackageManager.getActivityInfo(cn, 0);
} catch (PackageManager.NameNotFoundException nnfe) {
String[] packages = mPackageManager.currentToCanonicalPackageNames(
new String[] { packageName });
cn = new ComponentName(packages[0], className);
info = mPackageManager.getActivityInfo(cn, 0);
}
final Intent intent = new Intent(Intent.ACTION_MAIN, null)
.addCategory(Intent.CATEGORY_LAUNCHER)
.setComponent(cn)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
return addShortcut(info.loadLabel(mPackageManager).toString(),
intent, Favorites.ITEM_TYPE_APPLICATION);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Favorite not found: " + packageName + "/" + className);
}
return -1;
} else {
return invalidPackageOrClass(parser);
}
}
最后会return addShortcut(info.loadLabel(mPackageManager).toString(),
intent, Favorites.ITEM_TYPE_APPLICATION); 把数据统一放到mValues而后insert到数据库里面。
protected long addShortcut(String title, Intent intent, int type) {
long id = mCallback.generateNewItemId();
mValues.put(Favorites.INTENT, intent.toUri(0));
mValues.put(Favorites.TITLE, title);
mValues.put(Favorites.ITEM_TYPE, type);
mValues.put(Favorites.SPANX, 1);
mValues.put(Favorites.SPANY, 1);
mValues.put(Favorites._ID, id);
if (mCallback.insertAndCheck(mDb, mValues) < 0) {
return -1;
} else {
return id;
}
}
再比如widget 增加了SPANX和SPANY 。 应用图标的话只占一格,而widget往往占多个格子。
protected class PendingWidgetParser implements TagParser {
@Override
public long parseAndAdd(XmlResourceParser parser)
throws XmlPullParserException, IOException {
final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
mValues.put(Favorites.SPANX, getAttributeValue(parser, ATTR_SPAN_X));
mValues.put(Favorites.SPANY, getAttributeValue(parser, ATTR_SPAN_Y));
mValues.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
Bundle extras = new Bundle();
int widgetDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > widgetDepth) {
if (type != XmlPullParser.START_TAG) {
continue;
}
}
return verifyAndInsert(new ComponentName(packageName, className), extras);
}
至此,完成了loadWorkspace()的第一小步:第一个操作call LauncherProvider的方法。 call方法进行判断并记录下数据库是否为空。接着调用loadDefaultFavoritesIfNecessary方法,该方法在数据库为空时才会运行。 第二个操作读取布局的xml文件,按照特殊字符,特殊广播,特殊程序,本地布局。接着第三个操作,获取能读取到的第一个布局。最后第四个操作依次读取布局里面的内容存储到数据库里面。
如果不是第一次开机,那么数据库里面存储着布局的信息,第一小步是不做的,直接做第二小步获取数据库的信息。