美文网首页
Android第一行代码读书笔记 - 第九章

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

作者: 武当霍元甲 | 来源:发表于2019-11-20 13:49 被阅读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()方法来进行线程转换

    相关文章

      网友评论

          本文标题:Android第一行代码读书笔记 - 第九章

          本文链接:https://www.haomeiwen.com/subject/todoictx.html