网络

标签: Android


1 WebView

在应用内部显示各种网页,WebView控件用法类似其它控件,需要权限声明如下

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

<WebView
    android:id="@+id/web_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

WebView webView = (WebView) findViewById(R.id.web_view);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient());
webView.loadUrl("http://www.baidu.com");

2 HTTP协议

流程:发送HTTP请求、接收服务响应、解析返回数据、显示结果

2.1使用HttpURLConnection发送HTTP请求

通过URL对象的openConnection方法获取HttpURLConnection实例

URL url = new URL("http://www.baidu.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

设置conncetion的请求方法,主要是GET和POST

connection.setRequestMethod("GET");

自定义请求,如设置连接超时、读取超时的毫秒数

connection.setConnectionTimeout(8000);
connection.setReadTimeout(8000);

调用connection的getInputStream方法获取从服务器返回的输入流

InputStream in = connection.getInputStream();

关闭HTTP连接

connection.disconnect();

发起网络请求,需要在子线程中处理

new Thread(new Runnable(){
    @Override
    public void run(){
        HttpURLConnection connection = null;
        BufferedReader 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 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.printStackTracee();
            }finally{
                if (reader != null){
                    try{
                        reader.close();
                    }catch(IOException e){
                        e.printStackTrace();
                    }
                }
                if (connection != null){
                    connection.disconnect();
                }
            }
        }
    }).start.();

private void showResponse(final String response){
    runOnUiThread(new Runnable(){
        @Onverride
        public void run(){
            responseText.setText(response);
        }
    });

Android不允许在子线程中进行UI操作,runOnUiThread可以将线程切换到主线程,更新UI元素

提交数据到服务器,如用户名和密码

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

2.2网络通信库OkHttp

替代原生HttpURLConnection

添加OkHttp库依赖

compile 'com.squareup.okhttp3:3.7.0'

创建OkHttpClient实例

OkHttpClient client = new OkHttpClient();

创建Request对象

Request request = new Request.Builder().build();

连缀其它方法丰富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 requestBody = new FormBody.Builder()
        .add("username","accfcx")
        .add("password","123456")
        .build();

调用Request.Builder的post方法

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

和HttpURLConnection发送请求稍有不同

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);
            ...
            }
        }
    }

3 解析XML格式数据

<apps>
    <app>
        <id>1</id>
        <name>Google Maps</name>
        <version>1.0</version>
    </app>
    <app>
        <id>2</id>
        <name>Chrome></name>
        <version>57</version>
    </app>
    <app>
        <id>3</id>
        <name>YouTube</name>
        <version>2.3</version>
    </app>
</apps>

3.1 Pull解析方式

本机可以按照Apache服务器,在Request中url设置为本机服务器地址

.url("http://10.0.2.2/get_data.xml")
.build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
parseXMLWithPull(responseData);


private void parseXMLWithPull(String xmlData) {
    try {
        XmlPullParserFactory factory = XmlPullParserFactory.newInstance();//获取XmlPullParserFactory实例
        XmlPullParser xmlPullParser = factory.newPullParser();//XmlPullParser对象
        xmlPullParser.setInput(new StringReader(xmlData));//XML数据
        int eventType = xmlPullParser.getEventType();//获得当前解析事件
        String id = "";
        String name = "";
        String version = "";
        //循环中判断解析事件是否完成,否则调用next方法获取下一个解析事件
        while (eventType != XmlPullParser.END_DOCUMENT) {
            String nodeName = xmlPullParser.getName();//获取当前节点名字
            switch (eventType) {
                // 开始解析某个结点
                case XmlPullParser.START_TAG: {
                    if ("id".equals(nodeName)) {
                        id = xmlPullParser.nextText();//获取id节点的内容
                    } 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("MainActivity", "id is " + id);
                        Log.d("MainActivity", "name is " + name);
                        Log.d("MainActivity", "version is " + version);
                    }
                    break;
                }
                default:
                    break;
            }
            eventType = xmlPullParser.next();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

3.2 SAX解析方式

创建一个类继承DefaultHandler,从写父类的5个方法

public class MyHandler extends DefaultHandler {
    //XML解析
    @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 {
    }

    //完成整个XML解析
    @Override
     public void endDocument() throws SAXException {
     }

针对前面的XML数据的类

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();
    }

    @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("ContentHandler", "id is " + id.toString().trim());
            Log.d("ContentHandler", "name is " + name.toString().trim());
            Log.d("ContentHandler", "version is " + version.toString().trim());
            // 最后要将StringBuilder清空掉
            id.setLength(0);
            name.setLength(0);
            version.setLength(0);
        }
    }

    @Override
    public void endDocument() throws SAXException {
        super.endDocument();
    }
}

id,name和version用来存储具体解析的内容。解析节点的时候nodeName保存当前节点的名字,在具体解析节点内容的时候,根据节点名字,把解析的内容放在不同的StringBuilder对象中

在发送请求后,通过SAX方式解析XML数据

private void parseXMLWithSAX(String xmlData) {
    try {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        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();
    }
}

4 解析JSON数据

[{"id":"5","version":"5.5","name":"Clash of Clans"},
 {"id":"6","version":"6","name":"Boom of Beach"},
 {"id":"7","version":"3.5","name":"Clash Royale"}]

解析JSON数据有多种方法,如官方JSONObject,Google的GSON,第三方开源库Jackson\FastJSON

4.1 使用JSONObject

参数为
Response resonse = client.newCall(request).execute();
String responseData = response.body().string();

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");
            Log.d("MainActivity", "id is " + id);
            Log.d("MainActivity", "name is " + name);
            Log.d("MainActivity", "version is " + version);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

服务器上定义是JSON数组,解析时将数据传入到JSONArray对象中,再循环遍历,取出每个元素JSONObject,每个JSONObject对象包含id、name和version数据,调用getString取出数据

4.2 GSON

添加库依赖

compile 'com.google.code.gson:gson:2.7"

GSON可以将一段JSON格式的字符串自动映射成一个对象 JSON数据: {"name":"Tom","age":20} 定义Person类,添加name和age,如下会解析成一个Person对象

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

JSON数组需要借助TypeToken将数据类型如Person传入到fromJson方法中

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

具体针对前面的JSON数据 定义一个App类,Java Bean

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;
    }

}

解析获取到JSON数据

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("MainActivity", "id is " + app.getId());
        Log.d("MainActivity", "name is " + app.getName());
        Log.d("MainActivity", "version is " + app.getVersion());
    }
}

5 总结

网络操作作为工具类,提供一个静态方法发起网络请求。为了避免调用方法开启新线程后,在服务器可能还未执行响应执行结束,通过使用回调机制。

接口

public interface HttpCallbackListener {
    void onFinish(String response);
    void onError(Exception e);
}

服务器成功响应后调用onFinish方法,其中参数为返回的数据;网络操作错误时调用onError方法

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.setConnectTimeout(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);
                }
                if (listener != null) {
                    // 回调onFinish()方法
                    listener.onFinish(response.toString());
                }
            } catch (Exception e) {
                if (listener != null) {
                    // 回调onError()方法
                    listener.onError(e);
                }
            } finally {
                if (connection != null) {
                    connection.disconnect();
                }
            }
        }
    }).start();
}

在具体使用的时候,需要传递一个HttpCallbackListener

HttpUtil.sendHttpRequest(address, new HttpCallbackListener(){
    @Override
    public void onFinsh(String response){
        //对返回数据操作
    }
    @Override
    public void onError(Exceptio e){
    }
}

回调机制将响应数据返回给调用者 使用OkHttp发送请求的方式

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);
}

OkHttp在enqueue方法内部开启子线程,执行HTTP请求,最终结果回调到okhttp3.Callback中 具体用法

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){
    }
};