《第一行代码》读书笔记(八)

Author Avatar
wshunli 6月 05, 2018
  • 在其它设备中阅读本文章

《第一行代码》读书笔记 — 网络编程

《第一行代码》读书笔记(一)— 平台架构 (第1章)
《第一行代码》读书笔记(二)— 应用组件之 Activity (第2、4章)
《第一行代码》读书笔记(三)— 应用组件之 Service (第10章)
《第一行代码》读书笔记(四)— 应用组件之 BroadcastReceiver (第5章)
《第一行代码》读书笔记(五)— 应用组件之 ContentProvider (第7章)
《第一行代码》读书笔记(六)— 数据存储方案 (第6章)
《第一行代码》读书笔记(七)— 多媒体资源 (第8章)
《第一行代码》读书笔记(八)— 网络编程 (第9章)

第8章 使用网络技术

WebView 的用法

WebView 也是 Android 的一个控件,用来显示一些网页,内容也比较多。

针对 WebView 的封装,可以使用 AgentWeb :

Justson/AgentWeb: AgentWeb is a powerful library based on Android WebView.
https://github.com/Justson/AgentWeb

优化首屏加载速度,可以使用 VasSonic :

Tencent/VasSonic: VasSonic is a lightweight and high-performance Hybrid framework developed by tencent VAS team, which is intended to speed up the first screen of websites working on Android and iOS platform.
https://github.com/Tencent/VasSonic

对于 WebView 有特殊要求的,可以使用一些浏览器内核:

Crosswalk - Embedding the Crosswalk Project
https://crosswalk-project.org/documentation/android/embedding_crosswalk.html

腾讯浏览服务:
http://x5.tencent.com/

本文只介绍 Android 系统 WebView 使用。

在 Android 4.4 以前使用基于 Android WebKit 的 WebView 实现;
在 Android 4.4 及以后使用基于 Chromium blink 的 WebView 实现;
从 Android 5.0 开始,Google 把 Chromium blink 内核作为 apk 单独从系统抽离出来。

WebView webView = findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient());
webView.loadUrl("https://html5test.com/");

这只是简单地显示网页,WebView 本身还有很多技巧。

使用 HTTP 协议访问网络

1、使用 HttpURLConnection

在 Android 上发送 HTTP 请求一般使用 HttpURLConnection 或者 HttpClient 。

不过 HttpClient 在 Android 6.0 已经废弃。

new Thread(new Runnable() {
    @Override
    public void run() {

        HttpURLConnection connection = null;
        BufferedReader reader = null;

        try {
            URL url = new URL("https://www.wshunli.com/");
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(8000);
            connection.setReadTimeout(8000);
            InputStream in = new BufferedInputStream(connection.getInputStream());
            reader = new BufferedReader(new InputStreamReader(in));
            String line;
            while ((line = reader.readLine()) != null) {
                Log.d(TAG, line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                connection.disconnect();
            }
        }
    }
}).start();

上面是发送 GET 请求,发送 POST 请求:

connection.setRequestMethod("POST");
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.writeBytes("username=wshunli&password=123456");

2、使用 OkHttp

在实际项目里面使用 HttpURLConnection 还是不太行的,往往使用一些开源的网络框架,比如 OkHttp 等等。

OkHttp:An HTTP & HTTP/2 client for Android and Java applications

https://github.com/square/okhttp

首先添加 OkHttp 依赖:

implementation 'com.squareup.okhttp3:okhttp:3.10.0'

使用 OkHttp 发送 GET 请求:

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
        .url("https://www.shunli.com")
        .build();
try {
    Response response = client.newCall(request).execute();
    String responseData = response.body().string();
    Log.d(TAG, "onCreate: " + responseData);
} catch (IOException e) {
    e.printStackTrace();
}

整个过程清楚简洁。

···
RequestBody requestBody = new FormBody.Builder()
        .add("username", "wshunli")
        .add("password", "123456")
        .build();
Request request = new Request.Builder()
        .url("https://www.shunli.com")
        .post(requestBody)
        .build();
···

发送 POST 请求有点不太一样,要使用 RequestBody 传递数据。

这里只是简单介绍了 OkHttp 的使用,还有很多东西,后面再介绍。

解析 XML 格式数据

在网络传递的数据主要有两种: XML 和 JSON 。

这里我们使用本站的 RSS 订阅源数据。

https://www.wshunli.com/atom.xml

1、Pull 解析方式

本方法是将请求的 XML 字符串传入 XmlPullParser 对象实例,然后根据节点名字遍历解析 XML 内容。

new Thread(new Runnable() {
    @Override
    public void run() {
        OkHttpClient client = new OkHttpClient();

        Request request = new Request.Builder()
                .url("https://www.wshunli.com/atom.xml")
                .build();
        try {
            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 title = "";
        String id = "";
        String published = "";
        String updated = "";

        while (eventType != XmlPullParser.END_DOCUMENT) {
            String nodeName = xmlPullParser.getName();
            switch (eventType) {
                case XmlPullParser.START_TAG:
                    if (nodeName.equals("title")) {
                        title = xmlPullParser.nextText();
                    } else if (nodeName.equals("id")) {
                        id = xmlPullParser.nextText();
                    } else if (nodeName.equals("published")) {
                        published = xmlPullParser.nextText();
                    } else if (nodeName.equals("updated")) {
                        updated = xmlPullParser.nextText();
                    }
                    break;
                case XmlPullParser.END_TAG:
                    if (nodeName.equals("entry")) {
                        Log.d(TAG, "文章标题: " + title);
                        Log.d(TAG, "文章链接: " + id);
                        Log.d(TAG, "发布时间: " + published);
                        Log.d(TAG, "更新时间: " + updated);
                    }
                    break;
                default:
                    break;

            }
            eventType = xmlPullParser.next();
        }

    } catch (XmlPullParserException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

解析结果示例:

文章标题: 《第一行代码》读书笔记(七)
文章链接: https://www.wshunli.com/posts/941f84ed.html
发布时间: 2018-06-04T08:41:39.000Z
更新时间: 2018-06-04T15:14:46.188Z
文章标题: 《第一行代码》读书笔记(六)
文章链接: https://www.wshunli.com/posts/461ff372.html
发布时间: 2018-06-03T13:54:38.000Z
更新时间: 2018-06-04T15:14:46.188Z

2、SAX 解析方式

SAX 解析方式也是一种比较常用的方法,虽然比 Pull 方式复杂,但是语义更容易理解。

通常会新建一个类继承 DefaultHandler ,并重写其方法。

public class PostsHandler extends DefaultHandler {
    @Override
    public void startDocument() throws SAXException {
        super.startDocument();
    }
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        super.startElement(uri, localName, qName, attributes);
    }
    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        super.characters(ch, start, length);
    }
    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        super.endElement(uri, localName, qName);
    }
    @Override
    public void endDocument() throws SAXException {
        super.endDocument();
    }
}

其中 startDocument 和 endDocument 分别在开始、结束 XMl 接解析时调用。
类似 startElement 和 endElement 分别在开始、结束解析某个节点时调用。
而 characters 方法会在获取节点内容时调用,可能会调用多次,注意换行符也会解析出来。

public class PostsHandler extends DefaultHandler {
    private static final String TAG = "PostsHandler";

    private String nodeName;

    private StringBuilder title;
    private StringBuilder id;
    private StringBuilder published;
    private StringBuilder updated;
    @Override
    public void startDocument() throws SAXException {
        title = new StringBuilder();
        id = new StringBuilder();
        published = new StringBuilder();
        updated = new StringBuilder();
    }

    @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 (nodeName.equals("title")) {
            title.append(ch, start, length);
        } else if (nodeName.equals("id")) {
            id.append(ch, start, length);
        } else if (nodeName.equals("published")) {
            published.append(ch, start, length);
        } else if (nodeName.equals("updated")) {
            updated.append(ch, start, length);
        }
    }
    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        if (localName.equals("entry")) {
            Log.d(TAG, "文章标题: " + title.toString().trim());
            Log.d(TAG, "文章链接: " + id.toString().trim());
            Log.d(TAG, "发布时间: " + published.toString().trim());
            Log.d(TAG, "更新时间: " + updated.toString().trim());

            title.setLength(0);
            id.setLength(0);
            published.setLength(0);
            updated.setLength(0);
        }
    }
    @Override
    public void endDocument() throws SAXException {
        super.endDocument();
    }
}

和前面 Pull 解析功能是一样的。

private void parseXMLWithSAX(String xmlData) {
    SAXParserFactory factory = SAXParserFactory.newInstance();
    try {
        XMLReader xmlReader = factory.newSAXParser().getXMLReader();
        PostsHandler handler = new PostsHandler();
        xmlReader.setContentHandler(handler);
        xmlReader.parse(new InputSource(new StringReader(xmlData)));

    } catch (SAXException e) {
        e.printStackTrace();
    } catch (ParserConfigurationException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

调用是使用 XMLReader 实例的 setContentHandler 方法。

解析 JSON 格式数据

感觉平常 JSON 数据用得更多一些,JSON 和 XML 相比主要有优势在于体积更小。

以前本站是支持生成 JSON 文件的,因为上传太慢了就取消了。

这里使用随便使用一个 JSON 文件做测试吧。

https://raw.githubusercontent.com/wshunli/wshunli.github.io/c28a64a898599442a10c9eee74fed8d54dc89e7f/api/posts.json

1、使用 JSONObject

解析 JSON 数据可以使用 JSONObject 或者 GSON 开源库,还有一些 Jackson 、FastJSON 都不错。

new Thread(new Runnable() {
    @Override
    public void run() {
        OkHttpClient client = new OkHttpClient();

        Request request = new Request.Builder()
                .url("https://raw.githubusercontent.com/wshunli/wshunli.github.io/c28a64a898599442a10c9eee74fed8d54dc89e7f/api/posts.json")
                .build();
        try {
            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 {
        JSONObject jsonObject = new JSONObject(jsonData);
        if (jsonObject.has("data")){
            String data = jsonObject.getString("data");
            JSONArray jsonArray = new JSONArray(data);
            for (int i = 0; i < jsonArray.length(); i++) {
                JSONObject object = jsonArray.getJSONObject(i);
                String title = object.getString("title");
                String path = object.getString("path");
                String date = object.getString("date");
                String updated = object.getString("updated");

                Log.d(TAG, "文章标题: " + title);
                Log.d(TAG, "文章链接: " + path);
                Log.d(TAG, "发布时间: " + date);
                Log.d(TAG, "更新时间: " + updated);
            }
        }
    } catch (JSONException e) {
        e.printStackTrace();
    }
}

也就是使用构造函数传入 JSON 字符串获得 JSONObject 对象实例。

如果遇到数组的使用 JSONArray 实例逐条遍历即可。

2、使用 GSON 库

google/gson: A Java serialization/deserialization library to convert Java Objects into JSON and back
https://github.com/google/gson

使用 GSON 解析 JSON 数据特别简单。

implementation 'com.google.code.gson:gson:2.8.4'

首先根据 JSON 数据定义 Posts 类。

public class Posts {
    int total;
    int pageSize;
    int pageCount;
    List<Article> data;

    public static class Article{
        String title;
        String path;
        String date;
        String updated;

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public String getPath() {
            return path;
        }

        public void setPath(String path) {
            this.path = path;
        }

        public String getDate() {
            return date;
        }

        public void setDate(String date) {
            this.date = date;
        }

        public String getUpdated() {
            return updated;
        }

        public void setUpdated(String updated) {
            this.updated = updated;
        }
    }

    public int getTotal() {
        return total;
    }

    public void setTotal(int total) {
        this.total = total;
    }

    public int getPageSize() {
        return pageSize;
    }

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    public int getPageCount() {
        return pageCount;
    }

    public void setPageCount(int pageCount) {
        this.pageCount = pageCount;
    }

    public List<Article> getData() {
        return data;
    }

    public void setData(List<Article> data) {
        this.data = data;
    }
}

然后实例化 GSON 对象解析 JSON 字符串。

private void parseJSONWithGSON(String jsonDate) {
    Gson gson = new Gson();
    Posts posts = gson.fromJson(jsonDate, Posts.class);
    List<Posts.Article> data = posts.getData();
    for (Posts.Article article : data) {
        Log.d(TAG, "文章标题: " + article.getTitle());
        Log.d(TAG, "文章链接: " + article.getPath());
        Log.d(TAG, "发布时间: " + article.getDate());
        Log.d(TAG, "更新时间: " + article.getUpdated());
    }
}

GSON 也就介绍到这里。

参考资料
1、Building Web Apps in WebView | Android Developers
https://developer.android.com/guide/webapps/webview
2、Android:最全面的 Webview 详解 - CSDN博客
https://blog.csdn.net/carson_ho/article/details/52693322
3、Getting Started: WebView-based Applications for Web Developers - Google Chrome
https://developer.chrome.com/multidevice/webview/gettingstarted
4、X5 浏览器内核调研报告 - 简书
https://www.jianshu.com/p/2a14d303308d
5、前端 WebView 指南之 Android 交互篇 - 怡红院落
https://imnerd.org/android-webview-and-js.html
6、在Android上使用JS引擎是一种什么样的体验? | 网易杭州前端技术部
https://neyoufan.github.io/2016/12/23/android/Android%20Js引擎/在Android上使用JS引擎是一种什么样的体验?/


到这里可能算又浏览了一遍 《第一行代码》 。

这次有些内容没有看,比如第 3、12 章的 UI 部分。

还有 第 11 章 基于位置的服务也没有看,因为前面专业 GIS SDK 接触很多了。

最后是第 13、14 章的进阶、实战部分,后面再了解。

如果本文对您有所帮助,且您手头还很宽裕,欢迎打赏赞助我,以支付网站服务器和域名费用。 https://paypal.me/wshunli 您的鼓励与支持是我更新的最大动力,我会铭记于心,倾于博客。
本文链接:https://www.wshunli.com/posts/6374acae.html