【WeWeather成长记】重做城市列表————解析篇

/ 0评 / 0

具体上一次更新WeWeather已经过去近三个月了,为什么很久没有更新呢?放弃了?当然没有放弃。因为当时做城市列表的时候,不满意做出来的效果。当时做出来的效果是这样的,除去背景带来的效果单独看的话,还是很难看的。

原列表1

点击查看大图

 

而且遇到了及其困难的“Bug",为什么要打双引号呢。因为这个”Bug“如果是利用线程的话很容易就解决掉了。无奈自己学的太浅太少,当时没法解决,所以一怒之下,重新系统的学习Java。

俗话说得好,现学现卖。重做后的效果如下:

点击可看大图

点击查看大图

 

点击查看大图

点击查看大图

 

怎么样,是不是清爽很多。虽然有不少MD风格的功劳,但是想来想去,在追求炫酷还是极简的时候,还是选择做个追求极简的人。

 

好了废话不多说,接下来将从三个方面讲述这个列表的实现方式:

 

 

今天讲解解析篇。在前面的文章中说到了Android中XML文件的解析方式:Android : XML文件的生成与解析没错,在Android中XML文件的解析一般使用的是pull解析方式。因为这种方式简单、轻巧、速度快。包括这里的解析城市列表,在真机上测试大概6-10s。当然这也要看手机的性能的。

 

或许你会问,为什么不选择利用在线解析Json数据格式的城市列表呢?因为我们知道,在天气APP中,需要刷新或者重新载入城市列表可能就一次。如果采用在线解析的话,可能受网络影响,出现解析异常的情况。这个时候再去请求网络解析显得相当麻烦且没必要。

而在这里,我将城市列表的数据封装到离线XML文件中,只需解析一次即可。由于是离线方式,不会出现网络请求失败的情况。更不会出现其他千奇百怪的错误(额。。。。其实是出错的情况已经考虑到并提供了解决方案)。

离线XML城市文件在Github上,这里不详细说了。下面就重点讲一下解析的过程。

 

1、解析开始

我们知道,不论在Java还是Android,只要是耗时操作就应该在子线程中完成。所以解析开始的时候一定要开启一个子线程。

[code lang="Java"]

//开启子线程解析XML
new Thread(new Runnable() {
@Override
public void run() {
//解析操作

}
}).start();

[/code]

 

在这个地方,我将解析XML的工具类抽取成了一个方法,所以子线程中只需要调用一个方法即可。

 

public static int XMLAnalysisUtil(Context context, ProgressDialog progressBar) {

     return int ;

}

 

方法的返回值是一个int类型的数,用来判断解析结束标记。传递的参数是一个Context对象和一个ProgressDialog对象。至于为什么要传递ProgressDialog对象,在界面篇再具体说明。

而子线程run方法中,主要是要获取解析XML工具类的返回值,以此判断解析是否完成。我们知道,子线程中不能直接更新主线程UI,所以我们需要一个Handler来传递携带返回值的Message。并在主线程中对这个返回值进行操作。

 

[code lang="Java"]

//带有返回值的解析工具类
int analysisEnd = AnalysisCityListUtil.XMLAnalysisUtil(thisContext, progressDialog);
Message msg = Message.obtain();
msg.obj = analysisEnd;
//将返回值传递
handler.sendMessage(msg);

[/code]

这样的话,整个解析前的工作就完成的差不多的,省去的界面部分,仍然在界面篇详解。再来看整个子线程中的代码:

 

[code lang="Java"]
//开启子线程解析XML
new Thread(new Runnable() {
@Override
public void run() {
analysisEnd = AnalysisCityListUtil.XMLAnalysisUtil(thisContext, progressDialog);
Message msg = Message.obtain();
msg.obj = analysisEnd;
handler.sendMessage(msg);
}
}).start();
[/code]

 

 

2、解析中

如何解析XML文件,已经在前面的文章说过了。现在权当复习一遍。先来看城市列表的XML文件:

 

<province name="北京" id="01">
              <city name="北京" id="0101">
                       <county name="北京" id="010101" weatherCode="101010100" />
                       <county name="海淀" id="010102" weatherCode="101010200" />
                       <county name="朝阳" id="010103" weatherCode="101010300" />
                        ...
               </city>
       </province>

 

从上面的XML文件中可以看出,这个城市列表的结构就是先是省级,再市级,最后是县区级。这样就很方便了。只需要判断当前文件节点是哪一级别即可。比如如果是省级,可以实例化再存入相应的数据库。

当然在此之前,我们需要构建解析类工厂,并得到解析类实例,再将这个XML文件与解析实例绑定,再开始解析:

 

 

[code lang="Java"]
//构建XmlPullParserFactory解析XML
XmlPullParserFactory pullParserFactory = XmlPullParserFactory.newInstance();
//获取XmlPullParser的实例
XmlPullParser xmlPullParser = pullParserFactory.newPullParser();
//设置输入流 XML文件 文件在raw目录下
xmlPullParser.setInput(context.getResources().openRawResource(R.raw.city_id_list), "UTF-8");
[/code]

 

这个地方要注意,一定要传递一个Context对象,不然获取不到XML文件资源。接下来现在就要开始解析了 ,这里就不赘述具体过程了 ,应该能看懂 :

 

[code lang="Java"]
//得到当前节点的类型
int eventType = xmlPullParser.getEventType();
try {
while (eventType != XmlPullParser.END_DOCUMENT) {
//得到当前节点的名称
String nodeName = xmlPullParser.getName();
switch (eventType) {
//文档开始
case XmlPullParser.START_DOCUMENT:
break;
//开始节点
case XmlPullParser.START_TAG:
//如果开始节点是省,实例化省对象,并添加属性
if ("province".equals(nodeName)) {
...
}
//如果开始节点是市,实例化省对象,并添加属性
else if ("city".equals(nodeName)) {
...
}
//如果开始节点是县,实例化省对象,并添加属性
else if ("county".equals(nodeName)) {
...
}
break;
//结束节点
case XmlPullParser.END_TAG:
//如果结束节点是省,保存省对象到数据库
if ("province".equals(nodeName)) {
...
}
//如果结束节点是市,保存市对象到数据库
else if ("city".equals(nodeName)) {
...
}
//如果结束节点是县,保存县对象到数据库
else if ("county".equals(nodeName)) {
...
}
break;
default:
break;
}
eventType = xmlPullParser.next();
//循环之前判断是否是文档结束节点,如果是,返回解析完成标记
if (eventType == XmlPullParser.END_DOCUMENT) {
return FixedConstants.XML_END;
}
}
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
[/code]

 

从代码中可以看到,在解析结束的时候,返回了一个结束标记,这样在主线程中就可以判断这个标记了。

至此,我们就完成了解析XML功能。当然在解析过程中,没有暂时没有对数据进行存储,而且还有一个ProgressDialog对象没有用。在后面的几篇中会讲到。

 

3、解析完成

 

在解析过程中,可以看到返回了一个结束标记。并通过Handler传递给主线程。那么在主线程中就需要接收标记。

 

[code lang="Java"]
//接收子线程传递的值
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
analysisEnd = (int) msg.obj;
//如果解析结束标记等于固定常量,表示解析结束
if (analysisEnd == FixedConstants.XML_END) {
//去除详细处理代码,先用Toast表示。
Toast.makeText(thisContext, "初始化成功,谢谢等待", Toast.LENGTH_SHORT).show();
} else
Toast.makeText(thisContext, "初始化失败,请重试", Toast.LENGTH_SHORT).show();
}
};
[/code]

 

 

这样就完成了对整个离线XML文件的解析。整体来看,对离线XML文件的解析比在线解析Json数据少了对网络环境的判断且安全可靠。速度也很快,只需初始化一次即可,之后可以再第一次启动时初始化数据。就更加方便了。

现在来看一下,还有哪些问题没有解决:

 

 

这些问题将在之后的界面篇、数据库篇详细解释。

 

项目源码:https://github.com/IamXiaRui/Android_Weather

 

 

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注