第九章主要讲了WebView、网络访问,解析xml 和 json 格式数据
一、WebView
WebView主要用来在应用程序里展示一些网页。
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent">
</WebView>
WebView webview = (WebView) findViewById(R.id.webview);
/*
通过 WebView 的 getSettings() 方法区设置一些浏览器属性
这里是设置让 Webview 支持 JavaScript 脚本
*/
webview.getSettings().setJavaScriptEnabled(true);
/*
当需要从一个网页跳转到另一个网页时,我们希望目标网页仍在当前 WebView 中显示,
而不是打开系统浏览器
*/
webview.setWebViewClient(new WebViewClient());
/*
加载网页
*/
webview.loadUrl("http://www.baidu.com");
不要忘了添加权限
<uses-permission android:name="android.permission.INTERNET" />
二、使用 HttpURLConnection 访问网络
对于 HTTP协议,工作原理很简单,就是客户端向服务器发出一条 HTTP 请求,服务器收到请求之后会返回一些数据给客户端,然后客户端再对这些数据进行解析和处理就可以了。
以前,Android 上发送 HTTP 请求一般有两种方式:HttpURLConnection 和 HttpClient,在 Android6.0系统中,HttpClient 的功能被完全移除了,官方建议使用 HttpURLConnection。
/**
* 发送请求
*/
private void sendRequestWithHttpURLConnection() {
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
//获取 URL 对象并传入目标网络地址
URL url = new URL("http://www.baidu.com");
connection = (HttpURLConnection) url.openConnection();
//设置请求方法:GET 或者 POST
connection.setRequestMethod("GET");
//设置连接超时和读取超时
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
//获取服务器返回的流
InputStream inputStream = connection.getInputStream();
/*
POST请求的写法:
connection.setRequestMethod("POST");
DataOutputStream out = new DataOutputStream(connection.getOuputStream());
out.writeBytes(username=admin&password=123456);
*/
//获取输入流进行读取数据
reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
}
showResponse(builder.toString());
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
}
//将 HTTP 连接关闭掉
if(connection != null){
connection.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
private void showResponse(final String s) {
/*
Android 是不允许在子线程中进行 UI 操作的,通过
下面的方法切换到主线程,然后再更新UI元素
*/
runOnUiThread(new Runnable() {
@Override
public void run() {
TextView tv = (TextView) findViewById(R.id.tv_receive_response);
tv.setText(s);
}
});
}
三、使用 OkHttp
OkHttp 是由大名鼎鼎的 Square 公司开发的,该公司还开发了像 Picasso、Retrofit等著名的开源项目,项目主页地址是:
https://github.com/square/okhttp
记得添加依赖,一个是 OkHttp 库,一个是 Okio 库,后者是前者的通信基础,具体继承步骤看OkHttp 项目的主页。
private void sendRequestWithOkHttp(){
new Thread(new Runnable() {
@Override
public void run() {
try {
//获取 OkHttpClient 实例
OkHttpClient client = new OkHttpClient();
//构建 Request对象
Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
/*
OkHttpClient 的 newCall()方法获取一个 Call()对象,并调用 execute()
方法来发送请求兵器获取服务器返回的数据
*/
Response response = client.newCall(request).execute();
String responseData = response.body().string();
/*
发送 POST 请求:
RequestBody requestBody = new FormBody.Builder()
.add("username","admin")
.add("password","123456")
.build();
Request request = new Request.Builder()
.url("http://www.baidu.com")
.post(requestBody)
.build();
*/
showResponse(responseData);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
四、解析 XML 格式数据
一般我们会在网络上传输一些格式化后的数据,这种数据会有一定的结构规格和语义,当另一方收到数据消息之后就可以按照相同的结构规格进行解析,从而取出想要的内容。
在网络上传输数据时常用的格式有两种:XML和 JSON,我们新建一个 get_data.xml 文件
,内容如下
<apps>
<app>
<id>1</id>
<name>Goole 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>
解析 XML 格式的数据比较常见的有两种:Pull 解析和 SAX 解析
- Pull 解析
private void parseXMLWithPull(String xmlData){
try{
/*
首先获取 XmlPullParserFactory 实例对象,然后
再根据该对象获取 XmlPullParser 对象
*/
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser xmlPullParser = factory.newPullParser();
//将服务器返回的 xml 数据设置进去
xmlPullParser.setInput(new StringReader(xmlData));
//获取当前的解析事件
int eventType = xmlPullParser.getEventType();
String id = "";
String name = "";
String version = "";
//进入 while 说明解析工程还没结束
while(eventType != XmlPullParser.END_DOCUMENT){
/*
通过 getName() 获取当前节点的名字
通过 getText() 获取节点内具体的内容
*/
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(TAG, "parseXMLWithPull: id = " + id + "--- name = "
+ name + "--- version = " + version);
}
break;
}
//获取下一个解析事件
eventType = xmlPullParser.next();
}
}catch (Exception e){
e.printStackTrace();
}
}
- SAX 解析
SAX 解析虽然比 Pull 解析要复杂一些,但是在语义方面会更加清楚。首先新建一个类继承自 DefaultHandler,并重写方法。
public class MyHandler extends DefaultHandler {
private static final String TAG = "MyHandler";
private String nodename;
private StringBuilder id;
private StringBuilder name;
private StringBuilder version;
/**
* 开始解析 XML 的时候调用
*/
@Override
public void startDocument() throws SAXException {
id = new StringBuilder();
name = new StringBuilder();
version = new StringBuilder();
}
/**
* 开始解析某个节点的时候调用
* localName 参数记录当前节点的名字
*/
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
//记录当前节点名
nodename = localName;
}
/**
* 会在获取节点中内容的时候调用
* 该方法会被多次调用,一些换行符也被当做内容解析出来,
* 我们可能需要针对这种情况在代码中做好控制
*/
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
//根据当前的节点名判断将内容添加到哪一个 StringBuilder 对象中
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(TAG, "endElement: id is " + id.toString().trim()
+" name is " + name.toString().trim() + " version is " + version.toString().trim());
/*
最后别忘了要将 StringBuilder 清空掉,
不然会影响下一次内容的读取
*/
id.setLength(0);
name.setLength(0);
version.setLength(0);
}
}
/**
* 完成整个 XML 解析的时候调用
*/
@Override
public void endDocument() throws SAXException {
super.endDocument();
}
}
private void parseXMLWithSAX(String xmlData) {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
XMLReader xmlReader = factory.newSAXParser().getXMLReader();
ContentHandler handler = new MyHandler();
//将 ContentHandler 的实例设置到 XMLReader
xmlReader.setContentHandler(handler);
//开始执行解析
xmlReader.parse(new InputSource(new StringReader(xmlData)));
} catch (Exception e) {
e.printStackTrace();
}
}
五、解析 JSON 格式数据
比起 XML,JSON的优势在于它的体积更小,在网络上传输的时候可以更省流量,但是缺点是语义性差,没有 XML 直观。我们继续新建一个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
官方提供的 JSONObject 可以用于解析 json 字符串。
private void parseJSONWithJSONObject(String jsonData) {
try {
//因为是个 JSON 数组,所以用 JSONArray 来接收
JSONArray jsonArray = new JSONArray(jsonData);
for (int i = 0; i < jsonArray.length(); i++) {
//JSONArray 中每个元素都是一个 JSONObject 对象
JSONObject jsonObject = jsonArray.getJSONObject(i);
//调用 getString()方法将这些数据取出
String id = jsonObject.getString("id");
String name = jsonObject.getString("name");
String version = jsonObject.getString("version");
}
} catch (Exception e) {
e.printStackTrace();
}
}
- 使用 GSON
使用 GSON 要添加库依赖compile 'com.google.code.gson:gson:2.7'
,其优势是可以将 JSON 格式的字符串自动映射成一个对象,不用我们手动解析。
private void parseJSONWithGSON(String jsonData){
Gson gson = new Gson();
//解析数组,需要借助 TypeToken 将期望解析成的数据类型传入到 fromJson()方法中
List<Bean> beanList = gson.fromJson(jsonData,new TypeToken<List<Bean>>(){}.getType());
for (Bean bean : beanList){
Log.d(TAG, "parseJSONWithGSON: " + bean.getName());
}
/*
简单的解析一个对象
Gson gson = new Gson();
Bean bean = gson.fromJson(jsonData,Bean.class);
*/
}
六、网络编程优化
许多地方都使用网络功能,而发送 HTTP 请求的代码基本都是相同的。所以我们应该将这些通用的网络操作提取到一个公共的类里,并提供一个静态方法,要发起网络请求时,只需要简单调用一下该方法即可。
public class HttpUtil {
public static String sendHttpRequest(String address) {
HttpURLConnection connection = null;
try {
URL url = new URL(address);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setReadTimeout(8000);
connection.setConnectTimeout(8000);
connection.setDoInput(true);
connection.setDoOutput(true);
InputStream in = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while((line = reader.readLine()) != null){
response.append(line);
}
return response.toString();
} catch (Exception e) {
e.printStackTrace();
return e.getMessage();
} finally {
if(connection != null){
connection.disconnect();
}
}
}
}
/**
* 需要发起请求的时候调用
*/
String address = "http://www.baidu.com";
String response = HttpUtil.sendHttpRequest(address);
注意,网络请求通常都属于耗时操作,我们需要放到子线程中进行。但是如果我们在 sendHttpRequest()方法中开启了一个线程来发起 HTTP请求,那么服务器响应的数据是无法返回的。于是我们需要使用到 Java 的回调机制。
public interface HttpCallbackListener {
//服务器成功响应我们的请求
void onFinish(String response);
//网络操作出现错误的时候调用
void onError(Exception e);
}
修改 HttpUtil 里面的代码
public class HttpUtil {
//子线程是无法通过 return 语句来返回数据的
public static void sendHttpRequest(final String address, final HttpCallbackListener listener) {
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
try {
URL url = new URL(address);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setReadTimeout(8000);
connection.setConnectTimeout(8000);
connection.setDoInput(true);
connection.setDoOutput(true);
InputStream in = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while((line = reader.readLine()) != null){
response.append(line);
}
if(listener != null){
listener.onFinish(response.toString());
}
} catch (Exception e) {
e.printStackTrace();
if(listener != null){
listener.onError(e);
}
} finally {
if(connection != null){
connection.disconnect();
}
}
}
}).start();
}
}
/**
* 需要发起请求的时候调用
*/
String address = "http://www.baidu.com";
HttpUtil.sendHttpRequest(address, new HttpCallbackListener() {
@Override
public void onFinish(String response) {
//对返回数据内容执行具体的逻辑
}
@Override
public void onError(Exception e) {
//对异常情况进行处理
}
});
如果我们使用 OkHttp 来请求网络呢
public class HttpUtil {
// ...
//Callback 是 OkHttp 库中自带的一个回调接口
public static void sendOkHttpRequest(String address, Callback callback){
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(address)
.build();
/*
这里执行的是 enqueue 方法,是一个异步的请求,而 execute 是同步的请求
最终的请求结果回调到 Call 当中
*/
client.newCall(request).enqueue(callback);
}
}
/**
* 需要发起请求的时候调用
*/
String address = "http://www.baidu.com";
HttpUtil.sendOkHttpRequest(address, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//对异常情况进行处理
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//获取服务器返回的具体内容
String responseData = response.body().string();
}
});
注意:不管是 HttpURLConnection 还是 OkHttp,最终的回调接口都还是在子线程中运行的,因此我们不可以在这里执行任何 UI 操作,必须要切换到主线程才能进行 UI 操作
网友评论