Launcher3之XmlPullParser解析Workspace默认配置过程分析

引入

上一篇介绍了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解析默认配置文件的过程,有讲的不当的地方,希望各位大侠指教,不胜感激。

猜你喜欢

转载自blog.csdn.net/qinhai1989/article/details/84974572
今日推荐