一般我们都会在网络上传输一些格式化后的数据,这种数据具有一定的结构规格与语义,当另一方收到数据消息之后就可以按照相同的结构规格进行解析,从而取出需要的内容。
搭建简单的Web服务器
1、下载Apache服务器并安装。
2、在Apache\htdocs目录下,新建get_data.xml文件,并加入如下内容:
<apps>
<app>
<id>1</id>
<name>Google Maps</name>
<version>1.0</version>
</app>
<app>
<id>2</id>
<name>Chrome</name>
<version>2.1</version>
</app>
<app>
<id>3</id>
<name>Google Play</name>
<version>2.3</version>
</app>
</apps>
3、电脑端建立Wifi,并用手机连接,此时访问本机IP+get_data.xml便可访问该数据。
一、解析XML格式数据
(一)Pull解析方式
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
…….
private void sendRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("http://10.192.41.164/get_data.xml").build();
Response response =client.newCall(request).execute();
String responseData = response.body().string();
parseXMLWithPull(responseData);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
……
//解析服务器返回的数据
private void parseXMLWithPull(String xmlData) {
try {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser xmlPullParser = factory.newPullParser();
//将服务器返回的数据导入后开始解析
xmlPullParser.setInput(new StringReader(xmlData));
//当前解析的数据
int eventType = xmlPullParser.getEventType();
String id = "";
String name = "";
String version = "";
//当当前事件不等于XmlPullParser.END_DOCUMENT说明解析工作还没完成
while(eventType != XmlPullParser.END_DOCUMENT){
//d当前节点名
String nodeName = xmlPullParser.getName();
switch (eventType){
case XmlPullParser.START_TAG:{
//获取节点内具体内容
if("id".equals(nodeName)){
id = xmlPullParser.nextText();
}else if("name".equals(nodeName)){
name = xmlPullParser.nextText();
}else if("version".equals(nodeName)){
version = xmlPullParser.nextText();
}
break;
}
case XmlPullParser.END_TAG:{
if("app".equals(nodeName)){
Log.d("MainActivity", "id is " + id);
Log.d("MainActivity", "name is " + name);
Log.d("MainActivity", "version is " + version);
}
}
default:
break;
}
//获取下一个节点事件
eventType = xmlPullParser.next();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
(二)SAX解析方式
通常会新建一个类继承自DefaultHandler,并重写以下五个方法:
1)startDocument()方法在开始XML解析时调用。
2)startElement()方法在开始解析某个节点时调用。
3)characters()方法在获取节点中内容时调用,通常该方法可能被调用多次,一些换行符也会被当作内容解析出来。
4)endElement()方法在完成解析某个节点后调用。
5)endDocument()方法在完成XML解析时调用。
1、新建ContentHandler类继承自DefaultHandler,并重写父类的五个方法。
public class ContentHandler extends DefaultHandler {
//当前节点名
private String nodeName;
private StringBuilder id;
private StringBuilder name;
private StringBuilder version;
@Override
public void startDocument() throws SAXException {
//初始化
id = new StringBuilder();
name = new StringBuilder();
version = new StringBuilder();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
//记录当前节点,用于之后的characters()方法
nodeName = localName;
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
//开始解析
if("id".equals(nodeName)){
id.append(ch, start, length);
}else if("name".equals(nodeName)){
name.append(ch, start, length);
}else if("version".equals(nodeName)){
version.append(ch, start, length);
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if("app".equals(localName)){
Log.d("ContentHandler", "id is " + id.toString().trim());
Log.d("ContentHandler", "name is " + name.toString().trim());
Log.d("ContentHandler", "version is " + version.toString().trim());
//打印完成后清空内容
id.setLength(0);
name.setLength(0);
version.setLength(0);
}
}
@Override
public void endDocument() throws SAXException {
super.endDocument();
}
}
2、修改MainActivity中代码。
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
……
private void sendRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("http://10.192.41.164/get_data.xml").build();
Response response =client.newCall(request).execute();
String responseData = response.body().string();
parseXMLWithSAX(responseData);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
private void parseXMLWithSAX(String xmlData) {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
XMLReader xmlReader = factory.newSAXParser().getXMLReader();
ContentHandler handler = new ContentHandler();
//将ContentHandler实例设置到XMLReader中。
xmlReader.setContentHandler(handler);
//调用parse()方法开始执行解析
xmlReader.parse(new InputSource(new StringReader(xmlData)));
} catch (Exception e) {
e.printStackTrace();
}
}
}
二、解析JSON格式数据
与XML格式比较,JSON格式数据体积更小,在网络上传输会更省流量,但缺点在于语义性较差,不如XML直观。
在Apache\htdocs目录中新建get_data.json文件,加入如下内容:
[{"id":"5","version":"5.5","name":"Clash of Clans"},
{"id":"6","version":"7.0","name":"Boom Beach"},
{"id":"7","version":"3.5","name":"Clash Royale"}]
(一)使用JSONObject
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
……
private void sendRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("http://10.192.41.164/get_data.json").build();
Response response =client.newCall(request).execute();
String responseData = response.body().string();
parseJSONWithJSONObject(responseData);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
……
private void parseJSONWithJSONObject(String jsonData) {
try {
//JSON数组
JSONArray jsonArray = new JSONArray(jsonData);
//循环遍历JSONArray从中取出的每一个元素都是一个JSONObject对象,会包含id、name与version属性
for(int i=0; i<jsonArray.length(); i++){
JSONObject jsonObject = jsonArray.getJSONObject(i);
String id = jsonObject.getString("id");
String name = jsonObject.getString("name");
String version = jsonObject.getString("version");
Log.d("MainActivity", "id is " + id);
Log.d("MainActivity", "name is " + name);
Log.d("MainActivity", "version is " + version);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
(二)使用GSON
GSON可以将一段JSON格式的字符串自动映射成一个对象,从而不需要手动编写代码解析。
对于一段JSON格式的数据:
{" name ":"Tom", "age":20}
1)普通JSON对象,定义一个Person类,并加入name和age字段,然后调用以下代码便可将JSON数据解析为Person对象。
Gson gson = new Gson();
Person person = gson.fromJson(jsonData, Person.class);
2)JSON数组,需要借助TypeToken将期望解析成的数据类型传入到fromJson()方法。
Gson gson = new Gson();
List<Person> appList = gson.fromJson(jsonData,new TypeToken<List<Person>>(){}.getType());
1、在app/build.gradle文件,dependencies闭包中加入依赖。
compile 'com.google.code.gson:gson:2.7'
2、新增App类,加入id、name与version字段。
public class App {
private String id;
private String name;
private String version;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
3、修改MainActivity中代码。
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
……
private void sendRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("http://10.192.41.164/get_data.json").build();
Response response =client.newCall(request).execute();
String responseData = response.body().string();
parseJSONWithGSON(responseData);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
…….
private void parseJSONWithGSON(String jsonData) {
Gson gson = new Gson();
//开始解析
List<App> appList = gson.fromJson(jsonData,new TypeToken<List<App>>(){}.getType());
for(App app : appList){
Log.d("MainActivity", "id is " + app.getId());
Log.d("MainActivity", "name is " + app.getName());
Log.d("MainActivity", "version is " + app.getVersion());
}
}
}
三、Android网络编程实践
1、通常情况下应该将通用的网络操作提取到一个公共的类里,并提供一个静态方法,此时想要发起网络请求只需要简单调用该方法即可。
2、该方法不能开启子线程进行HTTP请求,此时服务器响应的数据将无法返回,所有耗时逻辑都是在子线程进行的,方法会在服务器还没来记得响应的时候就执行结束。
3、此时需要利用Java的回调机制,定义一个接口并定义一些方法使得服务器成功响应或网络操作错误时调用,并在上述方法内部加入回调参数并开启子线程,此时服务器成功响应后就可以在接口定义的响应方法中对响应的数据进行调用。
4、OkHttp存在自带回调接口okthhp3.Callback,同时调用enqueue()方法可以直接完成上述功能。