Android第一行代码读书笔记 - 第九章

2019-11-20  本文已影响0人  武当霍元甲

====================================

====== 第九章:看看精彩的世界 — 使用网络技术 ======

====================================

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()方法来进行线程转换

上一篇下一篇

猜你喜欢

热点阅读