Android之pull生成XML及XmlSerializer详解
文章链接:http://blog.csdn.net/qq_16628781/article/details/70161601
知识点
- XmlSerializer实例的源码解析;
- XmlSerializer类方法详解;
- pull生成XML的实例;
- 新名词记录{XmlSerializer;XmlSerializerFactory;StringWriter}
概述
前面的文章讲了3中常用的解析XML的方式,详情请看3种解析XML的方法。
下面我们就要来看下,如何生成XML文档,以便在内存中的对象进行序列化保存起来,那么就可以进行数据保存和共享了。
XmlSerializer实例
获取XML序列化XmlSerializer类实例:
XmlSerializer xmlSerializer = Xml.newSerializer();
//或者
try {
//还可以这样获取序列化实例
XmlPullParserFactory xmlPullParserFactory = XmlPullParserFactory.newInstance();
XmlSerializer xmlSerializer = xmlPullParserFactory.newSerializer();
} catch (XmlPullParserException e) {
e.printStackTrace();
}
首先我们查看到newSerializer()方法实现,如下:
此方法在Xml.java类下面。
public static XmlSerializer newSerializer() {
try {
return XmlSerializerFactory.instance.newSerializer();
} catch (XmlPullParserException e) {
throw new AssertionError(e);
}
}
由上面可以看到,系统建立一个内部静态类,静态类是用了一个工厂模式来管理XmlSerializer实例,代码如下:
代码在Xml.java文件下面。
static class XmlSerializerFactory {
static final String TYPE
= "org.kxml2.io.KXmlParser,org.kxml2.io.KXmlSerializer";
static final XmlPullParserFactory instance;
static {
try {
instance = XmlPullParserFactory.newInstance(TYPE, null);
} catch (XmlPullParserException e) {
throw new AssertionError(e);
}
}
}
我们知道,静态对象/静态代码块一开始就会被JVM加载到内存。可以看到上面的instance实例,是一个静态的final对象,最开始的时候就被实例化了。然后我们看到TYPE,这分明是KXmlSerializer类的全限定名,这是不是要放大招了–使用反射创建实例?
为了一探究竟,我们到XmlPullParserFactory.newInstance(TYPE, null)方法实现里面瞧一瞧。
很奇怪的是,传入的两个参数,居然都没有使用,Google的解释是,不允许此工厂随意的创建paser和serializer。
跟踪着源码看到下面的代码:
此代码在XmlPullParserFactory.java类里面
protected XmlPullParserFactory() {
parserClasses = new ArrayList<String>();
serializerClasses = new ArrayList<String>();
try {
parserClasses.add(Class.forName("org.kxml2.io.KXmlParser"));
serializerClasses.add(Class.forName("org.kxml2.io.KXmlSerializer"));
} catch (ClassNotFoundException e) {
throw new AssertionError();
}
}
来到这里,终于真相了。原来Google真的使用了反射来获取解析XML和生成XML的两个类,KXmlParser和KXmlSerializer。可以看到,这里使用两个数组将这两个类装载起来,需要的时候可以getParserInstance()和getSerializerInstance()分别获得解析XML实例和序列化XML实例。
但是很奇怪的是,在获取XmlPullParser实例的时候,可以使用使用反射获取到的解析XML实例,也可以使用Xml类的静态方法:newPullParser()方法获取到XmlPullParser实例。代码如下:
此代码在Xml.java文件下。
public static XmlPullParser newPullParser() {
try {
KXmlParser parser = new KXmlParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
return parser;
} catch (XmlPullParserException e) {
throw new AssertionError();
}
}
可以看到使用的new了一个KXmlParser对象,却不是利用上面反射得到的对象。更奇怪的是,同样的道理在获取序列化KXmlSerializer时,却不是这样子了,使用Xml.java类的newSerializer()方法和XmlPullParserFactory.java类的newSerializer()方法获得的实例,都是通过反射获得。这里就有点想不明白,为毛Google要这样做?
下面给出获取解析和序列化的全部方式:
try {
XmlPullParserFactory xmlPullParserFactory = XmlPullParserFactory.newInstance();
//解析实例
XmlPullParser xmlPullParser = xmlPullParserFactory.newPullParser();
XmlPullParser pullParser = newPullParser();
//序列化实例
XmlSerializer xmlSerializer = xmlPullParserFactory.newSerializer();
XmlSerializer xmlSerializer1 = newSerializer();
} catch (XmlPullParserException e) {
e.printStackTrace();
}
XmlSerializer类
因为XmlSerializer类是一个接口类,而KXmlSerializer是其子类。KXmlSerializer不能new出来,必须要利用反射的方式调用。我们这里看到XmlSerializer类的各个方法作用和使用。
1、void setFeature(String name, boolean state)
设置解析器的行为。例如是否打开命名空间处理功能等等。
参数1:设置feature,可以控制解析器的行为。例如可以设置”http://xml.org/sax/features/namespaces“:打开、关闭名空间处理功能、”http://xml.org/sax/features/validation“:是否打开校验。
当关闭校验的时候可以大大节约内存空间并且大大提高解析速度。因此如果使用的XML文档是可靠的,例如程序生成的,最好关闭校验。参数2:表明该行为是否打开。
对应获取的方法:
boolean getFeature(String name)
2、 void setProperty(String name,Object value)
设置属性的值。参数name最好是唯一的URI。
对应的根据name获取属性的方法为:Object getProperty(String name);未知的属性名则会返回null。
3、void setOutput (OutputStream os, String encoding)
根据给定的编码方式,对序列化后的内容通过OutputStream进行输出。
注意:此方法必须要在startDocument()之前调用。
4、void setOutput (Writer writer)
序列化后的内容,写入到writer中,然后可以对writer进行操作。唯一的问题就是:这里并没有对序列化过程中,指定编码方式。
注意:此方法必须要在startDocument()之前调用。
5、void startDocument (String encoding, Boolean standalone)
开始文档节点,并写入XML文件的声明,例如,如果有指定encoding=utf-8,那么就在XML文档第一句声明为:
xmlSerializer.setPrefix("yaojt", "http://com.yaojt.banana");
那么在生成的文件中,会在根节点设置命名空间。如下:
<users xmlns:yaojt="http://com.yaojt.banana">
对应的获取命名空间的方法:String getPrefix (String namespace, boolean generatePrefix)。由上面可知,获取到的密码空间就是设置的命名空间。如果没有设置命名空间,那么返回null。
7、int getDepth()
获取当前element的深度。调用startTag()方法,深度加1,反之,调动endTag()方法,深度减1;对应的值:0–在文档外部;1–根布局或者节点;2–foobar(Foobar?播放器,难道是看得见的char?即是节点对应的value);
8、String getNamespace ()
获取当前节点的命名空间,当前节点的命名空间是由startTag()方法的第一个参数决定的。如果startTag(“”, …),那么返回”“;如果startTag(null, …),那么返回null;
9、String getName()
返回当前节点由startTag()参数2决定的name。在调用startTag()之前,返回的都是为null。
10、XmlSerializer startTag (String namespace, String name)
参数1:当前节点命名空间。如果没有prefix,那么系统会自动创建一个prefix,需要设置,则可以在此方法之前调用setPrefix()设置命名空间。如果没有命名空间,那么对应的XML节点下,只会打印节点的名字。如果传入的是”“,那么在XML下会打印成:xmlns=”。如果已经定义的命名空间,那么会抛出不合法状态异常。
参数2:节点的名字。
对应的关闭tag的方法:XmlSerializer endTag (String namespace, String name)
注意:startTag()和endTag()必须成对出现,而且命名空间和节点名称必须完全相同。否则会报错误java.lang.IllegalArgumentException:
public class UserBean implements Serializable {
//串行化版本统一标识符
private static final long serialVersionUID = 1L;
private int id;
private String userName;
private String password;
private int age;
//省略setter和getter方法
}
/**
* Pull生成XML
*
* @param userBeanList 实体类集合
* @param outputStream 输出流
*/
public static String buildXmlByPull(List<UserBean> userBeanList, OutputStream outputStream) {
XmlSerializer xmlSerializer = newSerializer();
/*
try {
//还可以这样获取序列化实例
XmlPullParserFactory xmlPullParserFactory = XmlPullParserFactory.newInstance();
XmlSerializer xmlSerializer = xmlPullParserFactory.newSerializer();
} catch (XmlPullParserException e) {
e.printStackTrace();
}
*/
String result = null;
try {
xmlSerializer.setOutput(outputStream, "utf-8");
StringWriter stringWriter = new StringWriter();
// xmlSerializer.setOutput(stringWriter);
String np = xmlSerializer.getNamespace();
xmlSerializer.startDocument("utf-8", true);
xmlSerializer.setPrefix("yaojt", "http://com.yaojt.banana");
xmlSerializer.startTag(null, "users");
for (UserBean userBean : userBeanList) {
xmlSerializer.startTag(null, "user");
xmlSerializer.attribute(null, "id", String.valueOf(userBean.getId()));
xmlSerializer.startTag(null, "userName");
xmlSerializer.text(userBean.getUserName());
xmlSerializer.endTag(null, "userName");
xmlSerializer.startTag(null, "password");
xmlSerializer.text(userBean.getPassword());
xmlSerializer.endTag(null, "password");
xmlSerializer.startTag(null, "age");
xmlSerializer.text(String.valueOf(userBean.getId()));
xmlSerializer.cdsect("cdsect");
xmlSerializer.comment("comment");
xmlSerializer.ignorableWhitespace("ignorable White space");
xmlSerializer.endTag(null, "age");
xmlSerializer.endTag(null, "user");
}
xmlSerializer.endTag(null, "users");
xmlSerializer.endDocument();
xmlSerializer.flush();
outputStream.close();
result = stringWriter.toString();
CommonLog.logInfo("result:" + result);
return result;
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
方法调用
File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
InputStream inputStream = getResources().openRawResource(R.raw.users);
List<UserBean> userBeanList = XmlUtil.parseXmlByPull(inputStream);
try {
String outPutPath = file.getAbsolutePath() + "allusersbypull.xml";
File outFile = new File(outPutPath);
if (outFile.exists()){
outFile.delete();
outFile.createNewFile();
}else {
outFile.createNewFile();
}
CommonLog.logInfo("path:" + outFile.getAbsolutePath());
FileOutputStream fileOutputStream = new FileOutputStream(outFile);
XmlUtil.buildXmlByPull(userBeanList, fileOutputStream);
} catch (IOException e) {
e.printStackTrace();
}
parseXmlByPull(inputStream) 方法。以上方法对着看就可以了。再次提醒,关于startTag()和endTag()必须要成对出现,否则会报错。
下面是运行的效果截图。
因为我是将文件放在了内部公共目录根目录的DCIM文件夹下(这个是专门放置图片和视频的目录),所以会在前面加上了DCIM前缀。
如图:
得到的XML效果图:
总结
主要讲解了XmlSerializer实例的获取源码,以及XmlSerializer类的所有方法。以及最后的如何生成XML的实例。
关于实体类序列化成XML文件是很有用的,实体类只能在程序运行时存在内存中,如果需要持久化和分享数据,可以转成XML格式,就可以传输共享了。
下一篇,要讲下使用sax和dom生成XML。
以上就是所有内容,如果任何问题,请及时与我联系,谢谢!