引入
上一篇介绍了Workspace数据库和表的创建过程,本篇接着上一篇介绍下,workspace首次加载默认配置到数据库的过程。
这个默认配置是什么呢?就是我们首次使用launcher的时候,桌面上默认显示应用图标、文件夹、小部件等元素的配置。在实际的项目中,需要按照需求去配置哪些应用图标需要显示,哪些需要隐藏,就是在这个默认配置文件中修改的,这个文件就是default_workspace.xml。
这里有个知识点需要复习一下,使用XmlPullParser解析XML文件,下面先简单介绍下pull解析。
XmlPullParser介绍
Pull解析是一个开源项目,用于解析xml格式的数据,Android中直接内置了pull解析库,基本所有需要解析xml的地方都是通过pull解析完成的,所以在Android项目中解析xml首选pull解析。launcher也不例外,在加载default_workspace.xml时,也用到了pull解析。下面就跟着我,一起去看看pull解析在launcher中的应用。
XmlPullParser解析default_workspace.xml过程分析
默认配置方案选择
首先看下加载默认配置开始的地方,即LauncherProvider的loadDefaultFavoritesIfNecessary方法。
/**
* Loads the default workspace based on the following priority scheme:
* 1) From the app restrictions
* 2) From a package provided by play store
* 3) From a partner configuration APK, already in the system image
* 4) The default configuration for the particular device
*/
synchronized private void loadDefaultFavoritesIfNecessary() {
SharedPreferences sp = Utilities.getPrefs(getContext());
if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
Log.d(TAG, "loading default workspace");
AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
AutoInstallsLayout loader = null;
......
......
final boolean usingExternallyProvidedLayout = loader != null;
if (loader == null) {
loader = getDefaultLayoutParser(widgetHost);
}
......
if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
&& usingExternallyProvidedLayout) {
......
}
clearFlagEmptyDbCreated();
}
}
从源码的注释可以略窥一二,有几种方案实现这个默认配置,因为本人经验有限,这里不作一一讲解了,只以自己在项目中遇到的方案,去跟大家分析一下。因为公司项目是整机项目,可以在项目中添加配置文件,集成自研的launcher后,就可以根据文件在项目中的路径读取配置文件,当然这个文件就是xml配置文件,也就是前面提到的default_workspace.xml。
另外,如果开发的是第三方的launcher,可能我们只能把默认配置放在应用内了,当然实际的项目,apk内都会有一个默认配置文件,以防从其他地方没能获取到配置文件。
进一步会调用到DatabaseHelper的loadFavorites方法
@Thunk int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
ArrayList<Long> screenIds = new ArrayList<Long>();
// TODO: Use multiple loaders with fall-back and transaction.
int count = loader.loadLayout(db, screenIds);
......
return count;
}
该方法参数传入了一个AutoInstallsLayout对象,并调用了它的loadLayout方法,看样子所有的解析过程都是在这个类中完成的,有必要分析一下这个类。
Xml解析的封装:AutoInstallsLayout类
launcher在解析xml时,把所有解析过程都封装进了AutoInstallsLayout这个类,当然,可以根据需求进行扩展。下面就顺着它的实现分析一下
首先看下构造函数,这里主要看下参数,具体实现就不贴了:
public AutoInstallsLayout(Context context, AppWidgetHost appWidgetHost,
LayoutParserCallback callback, Resources res,
int layoutId, String rootTag) {
......
}
这边说下layoutId和rootTag参数,layoutId就是对应配置文件的资源id,正常会放在res/xml目录下,rootTag是配置文件根节点标记的名字,所以代码和配置文件中要匹配起来,不然会解析异常。
AutoInstallsLayout解析类供外部调用的接口方法loadLayout,实现如下:
/**
* Loads the layout in the db and returns the number of entries added on the desktop.
*/
public int loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds) {
mDb = db;
try {
return parseLayout(mLayoutId, screenIds);
} catch (Exception e) {
Log.e(TAG, "Error parsing layout: " + e);
return -1;
}
}
该方法只有一个parseLayout方法调用,进入parseLayout看下实现:
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;
ArrayMap<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;
}
从这个方法中可以看到,通过Resources.getXml方法返回了一个XmlResourceParser对象,这个对象就是实际的pull解析器,它继承了XmlPullParser接口,然后在while循环中对各个节点进行解析。这个方法是真正实现xml解析的入口点。
不同类型数据的解析器:TagParser
getLayoutElementsMap这个方法是干什么的呢?看下getLayoutElementsMap方法就一目了然了:
protected ArrayMap<String, TagParser> getLayoutElementsMap() {
ArrayMap<String, TagParser> parsers = new ArrayMap<>();
parsers.put(TAG_APP_ICON, new AppShortcutParser());
parsers.put(TAG_AUTO_INSTALL, new AutoInstallParser());
parsers.put(TAG_FOLDER, new FolderParser());
parsers.put(TAG_APPWIDGET, new PendingWidgetParser());
parsers.put(TAG_SHORTCUT, new ShortcutParser(mSourceRes));
return parsers;
}
原来launcher对各个类型的节点解析也进行了封装,每个解析器都实现了TagParser接口。
在上面的parseAndAddNode方法参数中传入了tagParserMap,以便获取对应类型节点的解析对象。接着看parseAndAddNode方法实现:
protected int parseAndAddNode(
XmlResourceParser parser,
ArrayMap<String, TagParser> tagParserMap,
ArrayList<Long> screenIds)
throws XmlPullParserException, IOException {
......
......
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;
}
tagParserMap根据xml中节点标签的名字,获取到对应节点的解析器,然后调用parseAndAdd方法进行解析,我们进入看下,这个以AppShortcutParser为例看下实现:
private class AppShortcutParser implements TagParser {
@Override
public long parseAndAdd(XmlResourceParser parser, Resources res) {
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) {
......
}
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.w(TAG, "Unable to add favorite: " + packageName + "/" + className, e);
}
return -1;
} else {
if (LOGD) Log.d(TAG, "Skipping invalid <favorite> with no component or uri");
return -1;
}
}
}
每个解析器都实现了TagParser接口,以便统一接口调用。这里通过getAttributeValue方法,解析出了应用的包名和类名,确定了组件,然后调用addShortcut方法,向数据库中增加记录。
持久化xml解析的数据到数据库
这里以addShortcut为例,看下代码:
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;
}
}
mCallback是DatabaseHelper的引用,调用insertAndCheck最终完成一条解析后的Favorites.ITEM_TYPE_APPLICATION类型数据的插入。
到这里,从解析xml数据到将数据插入数据库的一个流程就结束了,其他类型的数据,大家可以参考这个过程分析。
总结
以上粗略分析了一下,Workspace通过XmlPullParser解析默认配置文件的过程,有讲的不当的地方,希望各位大侠指教,不胜感激。