====================================
====== 第九章:看看精彩的世界 — 使用网络技术 ======
====================================
9.1 WebView的使用
新建一个WebViewTest项目
1、修改activity_main.xml的代码
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent” >
<WebView
android:id=“@+id/web_view”
android:layout_width=“match_parent”
android:layout_height=“match_parent” />
</LinearLayout>
2、修改MainActivity的代码
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WebView webView = (WebView) findViewById(R.id.web_view);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient());
webView.loadUrl(“http://www.baidu.com”);
}
}
getSettings()方法可以去设置一些浏览器的属性
setJavaScriptEnabled()方法来让WebView支持JavaScript脚本
setWebViewClient()方法,并传入一个WebVIewClient的实例,作用是,当需要从一个网页跳转到另一个网页时,我们希望目标网页仍然是当前WebView中显示,而不是打开系统浏览器。
3、由于本程序使用了网络功能,而访问网络是需要声明权限的。我们需要在AndroidMainifest.xml中声明
<manifest xmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.webviewtest” >
<uses-permission android:name=“android.permission.INTERNET” />
…
</manifest>
我们不是做网站开发的,对于HTTP协议,我们只需要稍微了解一些就足够了。上面的WebVIew加载百度网页,其实就是我们向百度的服务器发起了一条HTTP请求,接着服务器分析出我们想要访问的是百度的首页,于是会把该网页的HTML代码进行返回,然后WebView再调用手机浏览器的内核对返回的HTML代码进行解析,最终将页面展示出来。
简单来说,WebView已经在后台帮我们处理好了发送HTTP请求、接收服务器响应、解析发挥数据、以及最终的页面展示这几步工作。
9.2.1 使用HttpURLConnection
在过去,Android上发送HTTP请求有两种方式:HttpURLConnection和HttpClient。
不过HttpClient已经在Android6.0系统中被完全移除。
URL url = new URL(“http://www.baidu.com”);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod(“GET”);
connection.setConnectionTImeout(8000);
connection.setReadTimeout(8000);
// 之后再调用getInputStream()方法就可以获取服务器返回的输入流了。
InputStream in = connection.getInputStream();
connection.disconnect();
现在用一个NetworkTest项目
1、修改activity_main.xml
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:orientation=“vertical”
android:layout_width=“match_parent”
androdi:layout_height=“match_parent” >
<Button
android:id=“@+id/send_request”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:text=“Send Request” />
<ScrollView
android:layout_width=“match_parent”
androdi:layout_height=“match_parent” >
<TextView
android:id=“@+id/response_text”
android:layout_width=“match_parent”
android:layout_height=“wrap_content” />
</ScrollView>
</LinearLayout>
2、修改MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickLiseter {
TextView responseText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentVIew(R.layout.activity_main);
Button sendRequest = (Button) findViewById(R.id.send_request);
responseText = (TextView)findViewById(R.id.response_text);
sendRequest.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.send_request) {
// 自定义的发送Http请求的方法
sendRequsetWithHttpURLConnection();
}
}
private void sendRequestWithHttpURLConnection() {
// 开启线程来发起网络请求
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
BufferReader reader = null;
try {
URL url = new URL(“http://www.baidu.com”);
connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod(“GET”);
connection.setConnectionTimeout(8000);
connection.setReadTImeout(8000);
InputStream in = connection.getInputStream();
// 下面对获取到的输入流进行读取
reader = new BufferReader(new InputSteramReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine() != null) {
response.append(line);
}
// showResponse又是一个私有方方法
showResponse(response.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStactTrace();
}
}
if (connection != null) {
connection.disconnect();
}
}
}).start();
}
private void showResponse(final String response) {
runOnUiThread(new Runnable() {
@Override
public void run() {
// 在这里进行UI操作,将结果显示在界面上
responseText.setText(response);
}
});
}
}
3、修改AndroidManifest.xml文件
<manifest xmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.networktest” >
<uses-permission android:name=“android.permission.INTERNET” />
…
</manifest>
如果想提交数据给服务器怎么办。只需要将HTTP请求的方法改成POST,并在获取输入流之前将要提交的数据写出即可。注意每条数据都要以键值对的形式存在。数据与数据之间要用&符号隔开。
如
connection.setRequestMethod(“POST”);
DataOutputStream out = new DataOutPutStream(connection.getOutputStream());
out.writeBytes(“username=admin&password=123456”);
9.2.2 使用OkHttp
在开源盛行的今天,有许多出色的网络通讯库可以替代原生的HttpURLConnection,其中OkHttp是最出色的。Android首选的网络开源库。地址是https://github.com/square/okhttp
1、编辑app/build.gradle文件,在dependencies闭包中添加OkHttp库的依赖:
dependencies {
compile fileTree(dir: ‘libs’, include: [‘*.jar’]
compile ‘com.android.support:appcompat-v7:24.2.1’
testCompile ‘junit:junit:4.12’
compile ‘com.squareup.okhttp3:okhttp3:3.4.1’
}
添加上述依赖,会自动下载两个库,一个是OkHttp库,一个是Okio库,后者是前者的通信基础
具体OkHttp的用法:
OkHttpClient client = new OkHttpClient();
接下来如果想发起一条HTTP请求,就需要创建一个Request对象:
Request request = new Request.Builder().build();
但是上诉代码只是创建了一个空的Request对象,并没有什么实际作用,需要在build()方法之前连缀很多其他方法来丰富这个reqeust:
Request request = new Request.Builder().url(“http://www.baidu.com”).build();
之后调用OkHttpClient的newCall()方法来创建一个Call对象,并调用它的execute()方法来发送请求获取服务器返回的数据:
Response response = client.newCall(request).execute();
Response对象就是服务器返回的数据了。
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();
现在修改NetworkTest项目的MainActivity代码:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
…
@Override
public void onClick(View v) {
if (v.getId() == R.id.send_request) {
sendRequestWithOkHttp();
}
}
private void sendRequestWithOkHttp() {
// 开启一个子线程
new Thread(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(“http://www.baidu.com”).build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
showResponse(responseData);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
…
}
9.3 解析XML格式数据
我们先下载一个Apache服务器的安装包 http://httpd.apache.org/dowmload.cgi
XML的解析方式有很多种,我们现在学习Pull解析和SAX解析。
private void parseXMLWithPull(String xmlData) {
try {
XmlPullParseFactory factory = XmlPullParserFactotry.newInstance();
XmlPullParser xmlPullParser = factory.newPullParser();
// 接收数据并开始解析
xmlPullparser.setInput(new StringReader(xmlData));
int eventType = xmlPullParser.getEventType(0;
String id = “”;
String name = “”;
String version = “”;
while (eventTYpe != XmlPullParser.END_DOCUMENT) {
String nodeName = xmlPullParser.getName();
switch( eventType) {
// 开始解释节点
case XmlPullParser.START_TAG: {
if (“id”.equal(nodeName)) {
id = xmlPullParser.nextText();
} else if (“name”.equals(nodeName) {
name = xmlPullParser.nextText();
} else if (“version”.equals(nodeName) {
version = xmlPullPaser.nextText();
}
break;
}
// 完成解释节点
case XmlPullParser.END_TAG: {
if (“app”.equals(nodeName)) {
Log.d(“MainActivity”, “id is ” + id );
…
}
break;
}
default:
break;
}
eventType = xmlPullParser.next();
}
} catch (Exception e) {
e.printStackTrace();
}
}
9.3.2 SAX方式解析:
新建一个类,继承自DefaultHandler,并重写父类的5个方法,
public class MyHandler extends DefaultHandler {
@Override
public void startDocument() throws SAXException {
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
// 获取节点中内容的时候调用,会被调用多次
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
}
@Override
public void endDocument() throws SAXException {
}
}
新建一个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();
}
@Overrdie
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 SAXExcepiton {
// 根据当前的节点名判断将内容添加到哪一个StringBuilder对象中
if (“id”.equals(nodeName)) {
id.append(ch, start, length);
} else if (“name”.eqauls(nodeName)) {
version.append(ch, start, length);
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if(“app”.equals(localName)) {
Log.d(“ContentHandle”, “id is” + id.toString(0.trim());
…
id.setLength(0);
}
}
@Override
public void endDocument() throws SAXException {
super.endDocument();
}
}
接下来修改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.0.2.2/get_data.xml”).build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
parseXMLWithSAX(responseData);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
…
private void parseXMLWithSAX(String xmlData) {
try {
SAXParseFactory factory = SAXParserFactory.newInstance();
XMLReader xmlReader = factory.newSAXParser().getXMLReader();
// 这是我们自己创建的Handle
ContentHandle handler = new ContentHandler();
// 将ContentHandler的实例设置到XMLReader中
xmlReader.setContentHandler(handler);
// 开始进行解析
xmlReader.parse(new InputSource(new StringReader(xmlData)));
} catch (Exception e) {
e.printStackTrace();
}
}
}
当然,还有Dom方式来解析xml。这里就不介绍了。
9.4 解析JSON格式数据
相对于XML,JSON格式的优势在于体积更小,网络上传输更省流量。
9.4.1 使用JSONObject
解析JSON,可以使用官方的JSONObject,也可以使用谷歌开源的GSON,另外一些第三方的Jackson、FastJSON也不错。
使用JSONObject来解析:修改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.0.2.2/get_data.json”).build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
parseJSONWithJSONObject(responseData);
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
…
private void parseJSONWithJSONObject(String jsonData) {
try {
JSONArray jsonArray = new JSONArray(jsonData);
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”);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
9.4.2 使用GSON
先在项目中添加GSON库的依赖,编辑app/build.gradle文件
dependencies {
compile ‘com.google.code.gson:gson:2.7’
…
}
GSON库神奇的地方在于可以将一段JSON格式的字符串自动映射成一个对象:
比如{“name”:”Tom”, “age”:20}
我们自定义一个Person类,并加入name和age这两个字段
Gson gson = new Gson();
Person person = gson.fromJson(jsonData, Person.class);
如果需要解析的是一段JSON数组,则需要借助TypeToken将期望解析成的数据类型传入fromJson()方法中:
List<Person> person = gson.fromJson(jsonData, new TypeToken<List<Person>> (){}.getType();
现在我们来尝试一下。新增一个App类:
public class App {
private String id;
private String name;
private String version;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
修改MainActivity的代码:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
…
private void sendRequestWithOkHttp() {
new Thread(new Runnable() {
@Overrde
public void run() {
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(“http://10.0.2.2/get_data.json”).build();
Response response = client.newCall(reqeust).execute();
String responseData = response.body().string();
parseJSNOWithGSON(responseData);
} catch (Exception 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(xxx);
}
}
}
9.5 网络编程的最佳实践
如果我们每次都去编写一遍发送HTTP请求的代码,这显然是非常差劲的做法。
所以。我们应该将这些通用的网络操作提取到一个公共的类里,并提供一个静态方法,当我们想要发起网络请求的时候,只需要简单的调用一个方法即可:
public class HttpUtil {
public static String sendHttpReqeust(String address) {
HttpURLConnection connection = null;
try {
URL url = new URL(address);
connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod(“GET”);
connection.setConnecTimeout(8000);
connection.setReadTimeout(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();
returen e.getMessage();
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}
之后,每次需要发起一条HTTP请求的时候,只需要这样写:
String address = “http://ww.baidu.com”;
String response = HttpUtil.sendHttpRequest(address);
但是有个问题,sendHttpRequest方法里面没有开启新线程,所以是同步的,会阻塞当前线程。
这时候需要用到Java的回调机制:
1、定义一个接口
public interface HttpCallbackListener {
void onFinish(String response);
void onError(Exception e);
}
2、修改HttpUtil中的代码;
public class HttpUtil {
public static void sendHttpReqeust(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.setDoInput(true);
connection.setDoOutput(true);
InputStream in = connection.getInputStream();
BufferReader reader = new BufferdReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
if (listenr != null) {
// 回调onFinish()方法
listener.onFinish(response.toString());
}
} catch (Exception e) {
if (listener != null) {
// 回调onError()方法
listener.onError(e);
}
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}).start();
}
}
子线程主功能是无法通过return语句来返回数据的,因此我们将服务器响应的数据传入到HttpCallbackListener的onFinish()方法中,如果出现了异常则传入到onError()方法中。
现在我们调用的时候:
HttpUtil.sendHttpReqeust(address, new HttpCallbackListener() {
@Override
public void onFinish(String response) {
// 在这里根据返回内容执行具体的逻辑
}
@Override
public void onError(Exception e) {
// 在这里对异常情况进行处理
}
});
修改HttpUtil类:
public class HttpUtil {
…
public static void sendOkHttpRequest(String address, okhttp3.Callback callback) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(address).build();
client.newCall(request).enqueue(callback);
}
}
okhttp3.Callback这个参数,是OkHttp库中自带的一个回调接口,类似于我们刚才自己编写的HttpCallbackListener,
然后在client.newCall()之后不再调用execute()方法,而是调用enqueue()方法,并把okhttp3.Callback参数传入。OkHttp在enqueue()方法内部已经帮我们开好了子线程了。然后会在子线程中去执行HTTP请求,并将最终的请求结构会调到okhttp3.Callback中。
我们在调用sendOkHttpRequest()方法中这么写:
HttpUtil.sendOkHttpRequest(“http://www.baidu.com”, new okhttp3.Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
// 得到服务器返回的具体内容
String responseData = response.body().string();
}
@Override
public void onFailure(Call call, IOException e) {
// 这里对异常情况处理
}
});
所以,不管使用HttpURLConnection还是OkHttp,最终的回调接口还是在子线程中运行的,因此我们不可以砸这里执行任何的UI操作,除非借助runOnUiThread()方法来进行线程转换
网友评论