美文网首页Android知识
第一行代码(九)

第一行代码(九)

作者: radish520like | 来源:发表于2018-04-05 21:27 被阅读0次

    第九章主要讲了WebView、网络访问,解析xml 和 json 格式数据

    一、WebView

      WebView主要用来在应用程序里展示一些网页。

        <WebView
            android:id="@+id/webview"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
        </WebView>
    
              WebView webview = (WebView) findViewById(R.id.webview);
            /*
                通过 WebView 的 getSettings() 方法区设置一些浏览器属性
                这里是设置让 Webview 支持 JavaScript 脚本
             */
            webview.getSettings().setJavaScriptEnabled(true);
            /*
                当需要从一个网页跳转到另一个网页时,我们希望目标网页仍在当前 WebView 中显示,
                而不是打开系统浏览器
             */
            webview.setWebViewClient(new WebViewClient());
            /*
                加载网页
             */
            webview.loadUrl("http://www.baidu.com");
    

      不要忘了添加权限

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

    二、使用 HttpURLConnection 访问网络

      对于 HTTP协议,工作原理很简单,就是客户端向服务器发出一条 HTTP 请求,服务器收到请求之后会返回一些数据给客户端,然后客户端再对这些数据进行解析和处理就可以了。
      以前,Android 上发送 HTTP 请求一般有两种方式:HttpURLConnection 和 HttpClient,在 Android6.0系统中,HttpClient 的功能被完全移除了,官方建议使用 HttpURLConnection。

        /**
         * 发送请求
         */
        private void sendRequestWithHttpURLConnection() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    HttpURLConnection connection = null;
                    BufferedReader reader = null;
                    try {
                        //获取 URL 对象并传入目标网络地址
                        URL url = new URL("http://www.baidu.com");
                        connection = (HttpURLConnection) url.openConnection();
                        //设置请求方法:GET 或者 POST
                        connection.setRequestMethod("GET");
                        //设置连接超时和读取超时
                        connection.setConnectTimeout(8000);
                        connection.setReadTimeout(8000);
                        //获取服务器返回的流
                        InputStream inputStream = connection.getInputStream();
                        /*
                            POST请求的写法:
                                connection.setRequestMethod("POST");
                                DataOutputStream out = new DataOutputStream(connection.getOuputStream());
                                out.writeBytes(username=admin&password=123456);
                         */
                        //获取输入流进行读取数据
                        reader = new BufferedReader(new InputStreamReader(inputStream));
                        StringBuilder builder = new StringBuilder();
                        String line;
                        while ((line = reader.readLine()) != null) {
                            builder.append(line);
                        }
                        showResponse(builder.toString());
                    } catch (MalformedURLException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            if (reader != null) {
                                reader.close();
                            }
                            //将 HTTP 连接关闭掉
                            if(connection != null){
                                connection.disconnect();
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    
        private void showResponse(final String s) {
            /*
                Android 是不允许在子线程中进行 UI 操作的,通过
                下面的方法切换到主线程,然后再更新UI元素
             */
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    TextView tv = (TextView) findViewById(R.id.tv_receive_response);
                    tv.setText(s);
                }
            });
        }
    

    三、使用 OkHttp

      OkHttp 是由大名鼎鼎的 Square 公司开发的,该公司还开发了像 Picasso、Retrofit等著名的开源项目,项目主页地址是:
    https://github.com/square/okhttp

    记得添加依赖,一个是 OkHttp 库,一个是 Okio 库,后者是前者的通信基础,具体继承步骤看OkHttp 项目的主页。

        private void sendRequestWithOkHttp(){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //获取 OkHttpClient 实例
                        OkHttpClient client = new OkHttpClient();
                        //构建 Request对象
                        Request request = new Request.Builder()
                                .url("http://www.baidu.com")
                                .build();
                        /*
                            OkHttpClient 的 newCall()方法获取一个 Call()对象,并调用 execute()
                            方法来发送请求兵器获取服务器返回的数据
                         */
                        Response response = client.newCall(request).execute();
                        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();
                         */
                        showResponse(responseData);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    

    四、解析 XML 格式数据

      一般我们会在网络上传输一些格式化后的数据,这种数据会有一定的结构规格和语义,当另一方收到数据消息之后就可以按照相同的结构规格进行解析,从而取出想要的内容。
      在网络上传输数据时常用的格式有两种:XML和 JSON,我们新建一个 get_data.xml 文件,内容如下

    <apps>
        <app>
            <id>1</id>
            <name>Goole Maps</name>
            <version>1.0</version>
        </app>
    
        <app>
            <id>2</id>
            <name>Chrome</name>
            <version>2.1</version>
        </app>
    
        <app>
            <id>3</id>
            <name>Google Play</name>
            <version>2.3</version>
        </app>
    
    </apps>
    

      解析 XML 格式的数据比较常见的有两种:Pull 解析和 SAX 解析

    • Pull 解析
       private void parseXMLWithPull(String xmlData){
            try{
                /*
                    首先获取 XmlPullParserFactory 实例对象,然后
                    再根据该对象获取 XmlPullParser 对象
                 */
                XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
                XmlPullParser xmlPullParser = factory.newPullParser();
                //将服务器返回的 xml 数据设置进去
                xmlPullParser.setInput(new StringReader(xmlData));
                //获取当前的解析事件
                int eventType = xmlPullParser.getEventType();
                String id = "";
                String name = "";
                String version = "";
                //进入 while 说明解析工程还没结束
                while(eventType != XmlPullParser.END_DOCUMENT){
                    /*
                        通过 getName() 获取当前节点的名字
                        通过 getText() 获取节点内具体的内容
                     */
                    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("version".equals(nodename)){
                                version = xmlPullParser.nextText();
                            }
                            break;
                        //完成解析某个节点
                        case XmlPullParser.END_TAG:
                            if("app".equals(nodename)){
                                Log.d(TAG, "parseXMLWithPull: id = " + id + "--- name =  "
                                        + name + "--- version = " + version);
                            }
                            break;
                    }
                    //获取下一个解析事件
                    eventType = xmlPullParser.next();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    
    • SAX 解析
        SAX 解析虽然比 Pull 解析要复杂一些,但是在语义方面会更加清楚。首先新建一个类继承自 DefaultHandler,并重写方法。
    public class MyHandler extends DefaultHandler {
    
        private static final String TAG = "MyHandler";
    
        private String nodename;
        private StringBuilder id;
        private StringBuilder name;
        private StringBuilder version;
    
        /**
         * 开始解析 XML 的时候调用
         */
        @Override
        public void startDocument() throws SAXException {
            id = new StringBuilder();
            name = new StringBuilder();
            version = new StringBuilder();
        }
    
        /**
         * 开始解析某个节点的时候调用
         * localName 参数记录当前节点的名字
         */
        @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("id".equals(nodename)){
                id.append(ch,start,length);
            } else if("name".equals(nodename)){
                name.append(ch,start,length);
            } else if("version".equals(nodename)){
                version.append(ch,start,length);
            }
        }
    
        /**
         * 完成解析某个节点的时候调用
         */
        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            if("app".equals(localName)){
                Log.d(TAG, "endElement: id is " + id.toString().trim()
                        +" name is " + name.toString().trim() + " version is " + version.toString().trim());
                /*
                    最后别忘了要将 StringBuilder 清空掉,
                    不然会影响下一次内容的读取
                 */
                id.setLength(0);
                name.setLength(0);
                version.setLength(0);
            }
        }
    
        /**
         * 完成整个 XML 解析的时候调用
         */
        @Override
        public void endDocument() throws SAXException {
            super.endDocument();
        }
    }
    
        private void parseXMLWithSAX(String xmlData) {
            try {
                SAXParserFactory factory = SAXParserFactory.newInstance();
                XMLReader xmlReader = factory.newSAXParser().getXMLReader();
                ContentHandler handler = new MyHandler();
                //将 ContentHandler 的实例设置到 XMLReader
                xmlReader.setContentHandler(handler);
                //开始执行解析
                xmlReader.parse(new InputSource(new StringReader(xmlData)));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    五、解析 JSON 格式数据

      比起 XML,JSON的优势在于它的体积更小,在网络上传输的时候可以更省流量,但是缺点是语义性差,没有 XML 直观。我们继续新建一个get_data.json文件,内容是:

    [{"id":"5","version":"5.5","name":"Clash of Clans"},
    {"id":"6","version":"7.0","name":"Boom Beach"},
    {"id":"7","version":"3.5","name":"Clash Royale"}]
    
    • 使用 JSONObject
        官方提供的 JSONObject 可以用于解析 json 字符串。
        private void parseJSONWithJSONObject(String jsonData) {
            try {
                //因为是个 JSON 数组,所以用 JSONArray 来接收
                JSONArray jsonArray = new JSONArray(jsonData);
                for (int i = 0; i < jsonArray.length(); i++) {
                    //JSONArray 中每个元素都是一个 JSONObject 对象
                    JSONObject jsonObject = jsonArray.getJSONObject(i);
                    //调用 getString()方法将这些数据取出
                    String id = jsonObject.getString("id");
                    String name = jsonObject.getString("name");
                    String version = jsonObject.getString("version");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    • 使用 GSON
        使用 GSON 要添加库依赖compile 'com.google.code.gson:gson:2.7',其优势是可以将 JSON 格式的字符串自动映射成一个对象,不用我们手动解析。
        private void parseJSONWithGSON(String jsonData){
            Gson gson = new Gson();
            //解析数组,需要借助 TypeToken 将期望解析成的数据类型传入到 fromJson()方法中
            List<Bean> beanList = gson.fromJson(jsonData,new TypeToken<List<Bean>>(){}.getType());
            for (Bean bean : beanList){
                Log.d(TAG, "parseJSONWithGSON: " + bean.getName());
            }
            /*
                简单的解析一个对象
                Gson gson = new Gson();
                Bean bean = gson.fromJson(jsonData,Bean.class);
             */
        }
    

    六、网络编程优化

      许多地方都使用网络功能,而发送 HTTP 请求的代码基本都是相同的。所以我们应该将这些通用的网络操作提取到一个公共的类里,并提供一个静态方法,要发起网络请求时,只需要简单调用一下该方法即可。

    public class HttpUtil {
    
        public static String sendHttpRequest(String address) {
            HttpURLConnection connection = null;
            try {
                URL url = new URL(address);
                connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("GET");
                connection.setReadTimeout(8000);
                connection.setConnectTimeout(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();
                return e.getMessage();
            } finally {
                if(connection != null){
                    connection.disconnect();
                }
            }
        }
    }
    
            /**
             * 需要发起请求的时候调用
             */
            String address = "http://www.baidu.com";
            String response = HttpUtil.sendHttpRequest(address);
    

      注意,网络请求通常都属于耗时操作,我们需要放到子线程中进行。但是如果我们在 sendHttpRequest()方法中开启了一个线程来发起 HTTP请求,那么服务器响应的数据是无法返回的。于是我们需要使用到 Java 的回调机制。

    public interface HttpCallbackListener {
    
        //服务器成功响应我们的请求
        void onFinish(String response);
    
        //网络操作出现错误的时候调用
        void onError(Exception e);
    }
    

      修改 HttpUtil 里面的代码

    public class HttpUtil {
    
        //子线程是无法通过 return 语句来返回数据的
        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);
                        connection = (HttpURLConnection) url.openConnection();
                        connection.setRequestMethod("GET");
                        connection.setReadTimeout(8000);
                        connection.setConnectTimeout(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);
                        }
                        if(listener != null){
                            listener.onFinish(response.toString());
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        if(listener != null){
                            listener.onError(e);
                        }
                    } finally {
                        if(connection != null){
                            connection.disconnect();
                        }
                    }
                }
            }).start();
        }
    }
    
            /**
             * 需要发起请求的时候调用
             */
            String address = "http://www.baidu.com";
            HttpUtil.sendHttpRequest(address, new HttpCallbackListener() {
                @Override
                public void onFinish(String response) {
                    //对返回数据内容执行具体的逻辑
                }
    
                @Override
                public void onError(Exception e) {
                    //对异常情况进行处理
                }
            });
    

      如果我们使用 OkHttp 来请求网络呢

    public class HttpUtil {
    
    //    ...
    
        //Callback 是 OkHttp 库中自带的一个回调接口
        public static void sendOkHttpRequest(String address, Callback callback){
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder()
                    .url(address)
                    .build();
            /*
                这里执行的是 enqueue 方法,是一个异步的请求,而 execute 是同步的请求
                最终的请求结果回调到 Call 当中
             */
            client.newCall(request).enqueue(callback);
        }
    }
    
            /**
             * 需要发起请求的时候调用
             */
            String address = "http://www.baidu.com";
            HttpUtil.sendOkHttpRequest(address, 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,最终的回调接口都还是在子线程中运行的,因此我们不可以在这里执行任何 UI 操作,必须要切换到主线程才能进行 UI 操作

    下一篇文章:https://www.jianshu.com/p/8b482ffca782

    相关文章

      网友评论

        本文标题:第一行代码(九)

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