第一行代码读书笔记 9 -- 网络技术

作者: 开心wonderful | 来源:发表于2017-01-18 11:39 被阅读337次

    本篇文章主要介绍以几下个知识点:

    • 使用 HTTP 协议访问网络:
      使用 HttpURLConnection 和 OKHttp;
    • 解析 XML 格式数据:
      Pull 和 SAX 解析;
    • 解析 JSON 数据:
      JSONObject 和 GSON 解析。
    图片来源于网络

    9.1 使用 HTTP 协议访问网络

    HTTP 协议,其工作原理很简单:客户端向服务器发出一条 HTTP 请求,服务器收到请求后会返回一些数据给客户端,然后客户端再对这些数据进行解析和处理。

    9.1.1 使用 HttpURLConnection

    下面学习 HttpURLConnection 的用法,其请求步骤代码如下:

       /**
         *  HttpURLConnection 发送请求
         */
        private void sendRequestWithHttpURLConnection() {
            // 开启线程来发送网络请求
            new Thread(new Runnable() {
                @Override
                public void run() {
                    HttpURLConnection connection = null;
                    BufferedReader reader = null;
                    try{
                        URL url = new URL("http://www.baidu.com");
                        // 1. 获取 HttpURLConnection 实例
                        connection = (HttpURLConnection) url.openConnection();
                        // 2. 设置请求方法
                        connection.setRequestMethod("GET");
                        // 3. 自由定制,如设置连接超时、读取超时等
                        connection.setConnectTimeout(8000);
                        connection.setReadTimeout(8000);
                        // 4. 获取服务器返回的输入流
                        InputStream in = connection.getInputStream();
                        // 下面对获取到的输入流进行读取
                        reader = new BufferedReader(new InputStreamReader(in));
                        StringBuilder response = new StringBuilder();
                        String line;
                        while ((line = reader.readLine())!= null){
                            response.append(line);
                        }
                        showResponse(response.toString());// 显示请求结果
                    }catch (Exception e){
                        e.printStackTrace();
                    }finally {
                        if (reader != null){
                            try{
                                reader.close();
                            }catch (IOException e){
                                e.printStackTrace();
                            }
                        }
                        if (connection != null){
                            // 5.把 HTTP 连接关掉
                            connection.disconnect();
                        }
                    }
                }
            }).start();
        }
    

    别忘了声明网络权限:

    <uses-permission android:name="android.permission.INTERNET" />
    

    若是想要提交数据给服务器只需把请求方法改为 POST,并在获取输入流之前把要提交的数据写出即可。注意每条数据要以键值对的形式存在,数据与数据之间用 “&” 隔开,比如向服务器提交用户名和密码可写成:

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

    9.1.2 使用 OKHttp

    接下来学习下网络请求开源项目 OKHttp,其项目主页地址是:https://github.com/square/okhttp

    在使用 OKHttp 前,需要在项目中添加 OKHttp 库的依赖,如下:

    compile 'com.squareup.okhttp3:okhttp:3.5.0'
    

    下面学习 OKHttp 请求步骤,如下:

       /**
         *  OKHttp 发送请求
         */
        private void sendRequestWithOKHttp() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try{
                        // 1. 创建 OkHttpClient 实例
                        OkHttpClient client = new OkHttpClient();
                        // 2. 创建 Request 对象
                        Request request = new Request.Builder().url("http://www.baidu.com").build();
                        // 3. 调用 OkHttpClient 的 newCall() 方法来创建 Call 对象
                        Response response = client.newCall(request).execute();
                        // 4. 获取返回的内容
                        String responseData = response.body().string();
                        showResponse(responseData);// 显示请求结果
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    

    相比 HttpURLConnection,OKHttp 简单易用,若是发起一条 POST 请求,会比 GET 请求稍微复杂点,需要构建一个 RequestBody 对象来存放待提交的参数:

    RequestBody requestBody = new FormBody.Builder()
            .add("username","admin")
            .add("password","123456")
            .build();
    

    然后在 Request.Builder 中调用一下 post() 方法,并将 RequestBody 对象传入:

    Request request = new Request.Builder()
            .url("http://www.baidu.com")
            .post(RequestBody)
            .build();
    

    9.1.3 网络编程的最佳实践

    在实际开发中,我们通常将这些通用的网络操作提取到一个公共类里,接下来就简单封装下网络操作。

    首先针对 HttpURLConnection 定义一个回调接口:

    public interface HttpCallbackListener {
        void onFinish(String response);// 请求成功时调用
        void onError(Exception e);// 请求失败时调用
    }
    

    接着编写工具类 HttpUtil:

    public class HttpUtil {
    
        /**
         * 用 HttpURLConnection 发送请求
         * @param address
         * @param listener
         */
        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);
                        // 1. 获取 HttpURLConnection 实例
                        connection = (HttpURLConnection) url.openConnection();
                        // 2. 设置请求方法
                        connection.setRequestMethod("GET");
                        // 3. 自由定制,如设置连接超时、读取超时等
                        connection.setConnectTimeout(8000);
                        connection.setReadTimeout(8000);
                        connection.setDoInput(true);
                        connection.setDoOutput(true);
                        // 4. 获取服务器返回的输入流
                        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){
                            // 回调 onFinish() 方法
                            listener.onFinish(response.toString());
                        }
                    }catch (Exception e){
                        if (listener != null){
                            // 回调 onError() 方法
                            listener.onError(e);
                        }
                    }finally {
                        if (connection != null){
                            // 5.把 HTTP 连接关掉
                            connection.disconnect();
                        }
                    }
                    
                }
            }).start();
        }
    
        /**
         * 用 OKHttp 发送请求
         * @param address
         * @param callback
         */
        public static void sendOKHttpRequest(String address, Callback callback){
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder().url(address).build();
            client.newCall(request).enqueue(callback);
        }
    }
    

    这时候用 HttpURLConnection 发送请求就可以写成:

            HttpUtil.sendHttpRequest(address, new HttpCallbackListener() {
                @Override
                public void onFinish(String response) {
                    // 在这里根据返回内容执行具体的逻辑
                }
    
                @Override
                public void onError(Exception e) {
                    // 在这里对异常情况进行处理
                }
            });
    

    用 OKHttp 发送请求就可以写成:

           HttpUtil.sendOKHttpRequest("http://www.baidu.com", 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,最终回调接口都还是在子线程中运行的。

    下面举个例子巩固下,在布局中放置 Button 用于发送 HTTP 请求,放置一个 TextView 用于显示服务器返回的数据,主要代码如下:

    public class HttpActivity extends AppCompatActivity implements View.OnClickListener {
    
        private Button send_url_request,send_okHttp_request,clear_content;
        private TextView response_text;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_http);
    
            response_text = (TextView) findViewById(R.id.response_text);
    
            send_url_request = (Button) findViewById(R.id.send_url_request);
            send_okHttp_request = (Button) findViewById(R.id.send_okHttp_request);
            clear_content = (Button) findViewById(R.id.clear_content);
            send_url_request.setOnClickListener(this);
            send_okHttp_request.setOnClickListener(this);
            clear_content.setOnClickListener(this);
    
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.send_url_request:
                    sendRequestWithHttpURLConnection();
    
                case R.id.send_okHttp_request:
                    sendRequestWithOKHttp();
    
                case R.id.clear_content:
                    showResponse(""); //清空数据
            }
        }
    
        /**
         *  OKHttp 发送请求
         */
        private void sendRequestWithOKHttp() {
            HttpUtil.sendOKHttpRequest("http://www.baidu.com", 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();
                    showResponse(responseData);
                }
            });
        }
    
        /**
         *  HttpURLConnection 发送请求
         */
        private void sendRequestWithHttpURLConnection() {
            HttpUtil.sendHttpRequest("http://www.baidu.com", new HttpCallbackListener() {
                @Override
                public void onFinish(String response) {
                    // 在这里根据返回内容执行具体的逻辑
                    showResponse(response);
                }
    
                @Override
                public void onError(Exception e) {
                    // 在这里对异常情况进行处理
                }
            });
        }
    
        /**
         * 显示请求结果
         * @param response
         */
        private void showResponse(final String response) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    // 在这里进行UI 操作,将结果显示到界面上
                    response_text.setText(response);
                }
            });
        }
    }
    

    运行效果如下:

    两种网络请求效果

    9.2 解析 XML 格式数据

    在网络上传输数据时最常用的格式有两种:XML 和 JSON。本节来学习下如何解析 XML 格式的数据。

    解析 XML 格式的数据有多种方式,这里主要介绍 Pull 解析和 SAX 解析。解析前先来看看等下要解析的 XML 文本:

    xml 格式的内容

    9.2.1 Pull 解析方式

    Pull 解析整个过程比较简单,具体看代码注释:

       /**
         * pull 解析
         * @param xmlData 要解析的xml数据
         */
        private void parseXMLWithPull(String xmlData) {
            try {
                // 1. 获取 XmlPullParserFactory 实例
                XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
                // 2. 借助 XmlPullParserFactory 实例得到 XmlPullParser 对象
                XmlPullParser xmlPullParser = factory.newPullParser();
                // 3. 调用 setInput() 方法设置xml数据
                xmlPullParser.setInput(new StringReader(xmlData));
                // 4. 获取当前的解析事件
                int eventType = xmlPullParser.getEventType();
                String id = "";
                String name = "";
                String sex = "";
                // 5. 通过 while 循环不断地进行解析
                while (eventType != XmlPullParser.END_DOCUMENT){
                    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 ("sex".equals(nodeName)){
                                sex = xmlPullParser.nextText();
                            }
                            break;
    
                        // 完成解析某个节点
                        case  XmlPullParser.END_TAG:
                            if ("student".equals(nodeName)){
                                Log.d("pull解析:", "id is" + id);
                                Log.d("pull解析:", "name is" + name);
                                Log.d("pull解析:", "sex is" + sex);
                            }
                            break;
    
                        default:
                            break;
                    }
                    // 获取下一个解析事件
                    eventType = xmlPullParser.next();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    

    9.2.2 SAX 解析方式

    SAX 解析的用法比 Pull 解析要复杂些,但在语义方面会更加清楚。

    用 SAX 解析需要建一个类继承 DefaultHandler,并重写父类的5个方法。为实现上面同样的功能,新建一个 ContentHandler 类,如下所示:

    public class ContentHandler extends DefaultHandler {
    
        private String nodeName;
        private StringBuilder id;
        private StringBuilder name;
        private StringBuilder sex;
    
        /**
         * 开始 XML 解析时调用
         * @throws SAXException
         */
        @Override
        public void startDocument() throws SAXException {
            id = new StringBuilder();
            name = new StringBuilder();
            sex = new StringBuilder();
        }
    
        /**
         * 开始解析某个节点时调用
         * @param uri
         * @param localName
         * @param qName
         * @param attributes
         * @throws SAXException
         */
        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            // 记录当前节点名
            nodeName = localName;
        }
    
        /**
         * 获取节点中的内容时调用
         * @param ch
         * @param start
         * @param length
         * @throws SAXException
         */
        @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 ("sex".equals(nodeName)){
                sex.append(ch,start,length);
            }
        }
    
        /**
         * 完成解析某个节点时调用
         * @param uri
         * @param localName
         * @param qName
         * @throws SAXException
         */
        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            if ("student".equals(localName)){
                Log.d("sax解析:", "id is" + id.toString().trim());
                Log.d("sax解析:", "name is" + name.toString().trim());
                Log.d("sax解析:", "sex is" + sex.toString().trim());
                // 最后要将 StringBuilder 清空掉
                id.setLength(0);
                name.setLength(0);
                sex.setLength(0);
            }
        }
    
    
        /**
         * 完成整个 XML 解析时调用
         * @throws SAXException
         */
        @Override
        public void endDocument() throws SAXException {
            super.endDocument();
        }
    }
    

    接下来就非常简单了,代码如下:

       /**
         * sax 解析
         * @param xmlData
         */
        private void parseXMLWithSAX(String xmlData){
            try {
                // 创建 SAXParserFactory 对象
                SAXParserFactory factory = SAXParserFactory.newInstance();
                // 获取 XMLReader 对象
                XMLReader xmlReader = factory.newSAXParser().getXMLReader();
                ContentHandler handler = new ContentHandler();
                // 将 ContentHandler 的实例设置到 XMLReader 中
                xmlReader.setContentHandler(handler);
                // 开始执行解析
                xmlReader.parse(new InputSource(new StringReader(xmlData)));
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    

    9.2.3 举个例子实在点

    下面在布局中放置两个按钮,分别进行pull解析和sax解析:

    public class ParseXMLActivity extends AppCompatActivity implements View.OnClickListener {
    
        private Button btn_pull,btn_sax;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_parse_xml);
    
            btn_pull = (Button) findViewById(R.id.btn_pull);
            btn_sax = (Button) findViewById(R.id.btn_sax);
    
            btn_pull.setOnClickListener(this);
            btn_sax.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.btn_pull:
                    HttpUtil.sendOKHttpRequest("http://10.0.2.2/get_data.xml", new Callback() {
                        @Override
                        public void onFailure(Call call, IOException e) {
                            //ToastUtils.showShort("请求失败");
                        }
    
                        @Override
                        public void onResponse(Call call, Response response) throws IOException {
                            String responseData = response.body().string();
                            parseXMLWithPull(responseData);  // pull 解析
                        }
                    });
                    break;
    
                case R.id.btn_sax:
                    HttpUtil.sendOKHttpRequest("http://10.0.2.2/get_data.xml", new Callback() {
                        @Override
                        public void onFailure(Call call, IOException e) {
                            //ToastUtils.showShort("请求失败");
                        }
    
                        @Override
                        public void onResponse(Call call, Response response) throws IOException {
                            String responseData = response.body().string();
                            parseXMLWithSAX(responseData);  // sax 解析
                        }
                    });
                    break;
            }
        }
    
        /**
         * pull 解析
         * @param xmlData 要解析的xml数据
         */
        private void parseXMLWithPull(String xmlData) {  . . .  }
    
        /**
         * sax 解析
         * @param xmlData
         */
        private void parseXMLWithSAX(String xmlData){  . . .  }
    }
    

    运行程序,打印的日志分别如下:

    pull 解析 sax 解析

      可以看到,已经将 XML 数据成功解析出来了。

    9.3 解析 JSON 数据

      类似的,解析 JSON 格式的数据有多种方式,这里主要介绍官方提供的 JSONObject 和谷歌的开源库 GSON 来解析。解析前先来看看等下要解析的 JSON 文本:

    json 格式的内容

    9.3.1 使用 JSONObject

      使用 JSONObject 解析上面内容比较简单,具体看代码:

       /**
         * 用 JSONObject 解析
         * @param jsonData 需要解析的数据
         */
        private void parseJSONWithJSONObject(String jsonData) {
            try {
                // 把需要解析的数据传入到 JSONArray 对象中
                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 sex = jsonObject.getString("sex");
                    Log.d("JSONObject解析", "id is "+id);
                    Log.d("JSONObject解析", "name is "+name);
                    Log.d("JSONObject解析", "sex is "+sex);
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    

    9.3.2 使用 GSON

      接下来学习下开源库 GSON,其项目主页地址是:https://github.com/google/gson

      在使用 GSON 前,需要在项目中添加 GSON 库的依赖,如下:

    compile 'com.google.code.gson:gson:2.8.0'
    

      GSON 可以将一段 JSON 格式的字符串自动映射成一个对象,从而不需要手动去编写代码进行解析了。

      比如解析一段 JSON 格式数据:

    {"name":"Tom","age":20}
    

      就可以定义一个 Person 类,并加入 name 和 age 两字段,然后只需调用如下代码就可以将 JSON 数据自动解析成一个 Person 对象:

    Gson gson = new Gson();
    Person person = gson.fromJson(jsonData,Person.class);
    

      若解析一段 JSON 数组会麻烦些,需要借助 TypeToken 把期望解析成的数据类型传入到 fromJson() 方法中:

    List<Person> people = gson.fromJson(jsonData,new TypeToken<List<Person>>(){}.getType());
    

      GSON 的基本用法就是这样。下面来解析上面的 JSON 文本,首先新增一个 Student 类:

    public class Student {
        
        private String id;
        private String name;
        private String sex;
    
        // Getter and Setter
        . . .
    }
    

      接下来就非常简单了,代码如下:

       /**
         *  用 GSON 解析
         * @param jsonData
         */
        private void parseJSONWithGSON(String jsonData){
            Gson gson = new Gson();
            List<Student>studentList = gson.fromJson(jsonData,new TypeToken<List<Student>>(){}.getType());
            for (Student student:studentList){
                Log.d("GSON解析", "id is "+student.getId());
                Log.d("GSON解析", "name is "+student.getName());
                Log.d("GSON解析", "sex is "+student.getSex());
            }
        }
    

    9.3.3 举个例子实在点

      下面在布局中放置两个按钮,分别用 JSONObject 和 GSON 进行 json 解析:

    public class ParseJSONActivity extends AppCompatActivity implements View.OnClickListener {
    
        private Button btn_object,btn_gson;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_parse_json);
    
            btn_object = (Button) findViewById(R.id.btn_object);
            btn_gson = (Button) findViewById(R.id.btn_gson);
    
            btn_object.setOnClickListener(this);
            btn_gson.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.btn_object:
                    HttpUtil.sendOKHttpRequest("http://10.0.2.2/get_data.json", 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();
                            parseJSONWithJSONObject(responseData);   // 用 JSONObject 解析
                        }
                    });
                    break;
    
                case R.id.btn_gson:
                    HttpUtil.sendOKHttpRequest("http://10.0.2.2/get_data.json", 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();
                            parseJSONWithGSON(responseData);  // 用 GSON 解析
                        }
                    });
                    break;
            }
    
        }
    
        /**
         * 用 JSONObject 解析
         * @param jsonData 需要解析的数据
         */
        private void parseJSONWithJSONObject(String jsonData) { . . . }
    
        /**
         *  用 GSON 解析
         * @param jsonData
         */
        private void parseJSONWithGSON(String jsonData){ . . . }
    }
    

      运行程序,打印的日志分别如下:

    JSONObject 解析 GSON 解析

      关于网络编程先学习到这,下篇文章将进入安卓四大组件之服务的学习。

    相关文章

      网友评论

        本文标题:第一行代码读书笔记 9 -- 网络技术

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