第 9 章 - Android 网络编程

1. WebView 的用法

1.1 在布局文件中添加 WebView 控件,使其充满屏幕
1.2 修改 MainActivity 中的代码
  • getSetting() 方法可以设置一些浏览器的属性,在这里只调用了 setJavaScriptEnabled() 方法来使 WebView 支持 JavaScript 脚本。
  • 接着调用了 setWebViewClient() 方法,并传入一个 WebViewClient 实例。这段代码的作用是,当需要从一个网页跳转到另一个网页时,目标网页仍然在当前 WebView 中显示,而不是打开系统浏览器。
  • 最后调用 loadUrl() 方法,将网址传入即可展示相应网页的内容。
WebView webView = findViewById(R.id.web_view);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient());
webView.loadUrl("http://www.baidu.com");
1.3 声明访问网络权限
  • 由于程序加入了网络功能,因此需要到 AndroidManifest.xml 中声明权限

2. 使用 HTTP 协议访问网络

  • HTTP 原理:客户端向服务器发出一条 HTTP 请求,服务器收到请求后会返回一些数据给客户端,然后客户端再对这些数据进行解析和处理即可。如上一节中向百度的服务器发起了一条 HTTP 请求,接着服务器分析出我们想要访问的是百度的首页,于是会把该网址的 HTML 代码进行返回,然后 WebView 再调用手机浏览器的内核对返回的 HTML 代码进行解析,最终将页面展示出来。
2.1 使用 HttpURLConnection:

目标:使用此技术获取数据,并显示到界面上

public class MainActivity extends AppCompatActivity {

    private TextView responseText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        responseText = findViewById(R.id.response_text);
        Button sendRequest = findViewById(R.id.send_request);
        sendRequest.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                sendRequestWithHttpURLConnection();
            }
        });
    }

    /**
     * 定义一个发送请求方法,具体的逻辑都在里面编写
     */
    private void sendRequestWithHttpURLConnection(){
        // 因为网络请求是耗时操作,所以开启子线程来发起请求
        new Thread(new Runnable() {
            @Override
            public void run() {
                HttpURLConnection connection = null;
                BufferedReader reader = null;
                try{
                    // 首先要获取 HttpURLConnection 实例
                    URL url = new URL("https://www.baidu.com");
                    connection = (HttpURLConnection)url.openConnection();

                    // 设置请求方法,各种超时
                    connection.setRequestMethod("GET");
                    connection.setConnectTimeout(8000);
                    connection.setReadTimeout(8000);

                    // 获取服务器返回的输入流,并使用 BufferedReader 进行读取
                    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{
                    try{
                        if(reader != null){
                            reader.close();
                        }
                    }catch(IOException e){
                        e.printStackTrace();
                    }
                    if(connection != null){
                        connection.disconnect();
                    }
                }
            }
        }).start();
    }

    /**
     * 定义此方法用于将结果显示在界面上,由于发起请求的逻辑操作
     * 是在子线程进行,因此在这里需要将线程切换回主线程来进行
     * UI 操作。
     *
     * @param response 服务器返回的响应结果,声明为 final 是
     *                 因为参数需要在内部类中使用
     */
    private void showResponse(final String response){
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                responseText.setText(response);
            }
        });
    }
}
  • 步骤:
  1. 获取 HTTPURLConnection 实例
  2. 设置 HTTP 请求方法,GET 表示希望从服务器获取数据,POST 表示希望提交数据给服务器
  3. 设置连接超时,读取超时等
  4. 获取返回的输入流,并使用 BufferedReader 进行读取
  5. 关闭 HTTP 连接
  • 发起一条 POST 请求:
connection.setRequestMethod("POST");
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.writeBytes("username=admin&password=123456");
2.2 使用 OkHttp:
  • app/build.gradle 中添加依赖:
    implementation 'com.squareup.okhttp3:okhttp:3.11.0'
  • 接着上一节内容,只需修改 sendRequestWithHttpURLConnection() 方法即可:
private void sendRequestWithOkHttp(){
    new Thread(new Runnable() {
        @Override
        public void run() {
            try{
                // 创建 OkHttpClient 实例
                OkHttpClient client = new OkHttpClient();
                // 创建 Request 对象发起请求
                Request request = new Request.Builder()
                        .url("https://www.baidu.com")
                        .build();
                
                /* 调用 newCall(request) 方法创建一个 Call 对象,再调用 Call 对象
                   的 execute() 方法来发送请求并并并获取服务器返回的数据,
                   再赋值给 Response 对象 */
                Response response = client.newCall(request).execute();
                // 将 Response 对象转换为 String 类型
                String responseData = response.body().string();
                showResponse(responseData);
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }).start();
}
  • 发起一条 POST 请求:
RequestBody requestBody = new FormBody.Builder()
        .add("username","admin")
        .add("password", "123456")
        .build();
Request request = new Request.Builder()
        .url("https://www.baidu.com")
        .post(requestBody)
        .build();

3. 解析 XML 格式数据


    
        1
        Google Maps
        1.0
    
    
        2
        Chrome
        2.1
    
    
        3
        Google Play
        2.3
    

3.1 Pull 解析方式:
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button sendRequest = findViewById(R.id.send_request);
        sendRequest.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                sendRequestWithHttpURLConnection();
            }
        });
    }

    /**
     * 定义一个发送请求方法,具体的逻辑都在里面编写
     */
    private void sendRequestWithHttpURLConnection(){
        // 因为网络请求是耗时操作,所以开启子线程来发起请求
        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();
                    parseXMLWithPull(responseData);
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
        }).start();
    }
    private void parseXMLWithPull(String xmlData){
        try{
            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
            XmlPullParser parser = factory.newPullParser();
            parser.setInput(new StringReader(xmlData));
            // 得到当前解析事件
            int eventType = parser.getEventType();
            String id = null;
            String name = null;
            String version = null;
            while(eventType != XmlPullParser.END_DOCUMENT){
                String nodeName = parser.getName();
                switch(eventType){
                    case XmlPullParser.START_TAG:
                        if("id".equals(nodeName)){
                            id = parser.nextText();
                        }else if("name".equals(nodeName)){
                            name = parser.nextText();
                        }else if("version".equals(nodeName)){
                            version = parser.nextText();
                        }
                        break;
                    case XmlPullParser.END_TAG:
                        if("app".equals(nodeName)){
                            Log.d(TAG, "id is " + id);
                            Log.d(TAG, "name is " + name);
                            Log.d(TAG, "version is " + version);
                        }
                        break;
                }
                // 获取下一个解析事件
                eventType = parser.next();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
3.2 SAX 解析方式:
  • 新建一个类继承自 DefaultHandler,并复写父类的 5 个方法
public class ContentHandler extends DefaultHandler{

    private String nodeName;
    private StringBuilder id;
    private StringBuilder name;
    private StringBuilder version;

    /**
     * 此方法在开始 XML 解析时调用
     *
     * @throws SAXException
     */
    @Override
    public void startDocument() throws SAXException{
        id = new StringBuilder();
        name = new StringBuilder();
        version = 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 当前读到的 TextNode 字节数组
     * @param start 字节开始的位置,如果要读取全部,那就是从 0 开始
     * @param length 当前 TextNode 的长度
     * @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("version".equals(nodeName)){
            version.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("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);
        }
    }

    /**
     * 此方法会在完成整个 XML 解析时调用
     *
     * @throws SAXException
     */
    @Override
    public void endDocument() throws SAXException{
        super.endDocument();
    }
}
  • 修改 MainActivity 中的代码:
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button sendRequest = findViewById(R.id.send_request);
        sendRequest.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                sendRequestWithHttpURLConnection();
            }
        });
    }

    /**
     * 定义一个发送请求方法,具体的逻辑都在里面编写
     */
    private void sendRequestWithHttpURLConnection(){
        // 因为网络请求是耗时操作,所以开启子线程来发起请求
        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(IOException e){
                    e.printStackTrace();
                }
            }
        }).start();
    }
    private void parseXMLWithSAX(String xmlData){
        try{
            SAXParserFactory factory = SAXParserFactory.newInstance();
            XMLReader reader = factory.newSAXParser().getXMLReader();
            ContentHandler handler = new ContentHandler();
            // 将 ContentHandler 的实例设置到 XMLReader 中
            reader.setContentHandler(handler);
            // 开始解析
            // public void parse (InputSource input)
            // public InputSource (Reader characterStream)
            // public StringReader(String s)
            reader.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":"7.0","name":"Boom Beach"},
{"id":"7","version":"3.5","name":"Clash Royale"}]

4.1 使用 JSONObject:
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button sendRequest = findViewById(R.id.send_request);
        sendRequest.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                sendRequestWithHttpURLConnection();
            }
        });
    }

    /**
     * 定义一个发送请求方法,具体的逻辑都在里面编写
     */
    private void sendRequestWithHttpURLConnection(){
        // 因为网络请求是耗时操作,所以开启子线程来发起请求
        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(IOException e){
                    e.printStackTrace();
                }
            }
        }).start();
    }
    private void parseJSONWithJSONObject(String jsonData){
        try{
            JSONArray jsonArray = new JSONArray(jsonData);
            // 用循环遍历 JSONArray ,从中取出的每一个元素都是一个,JSONObject 对象
            for(int i = 0; i < jsonArray.length(); i++){
                JSONObject object = jsonArray.getJSONObject(i);
                String id = object.getString("id");
                String name = object.getString("name");
                String version = object.getString("version");
                Log.d(TAG, "id is " + id);
                Log.d(TAG, "name is " + name);
                Log.d(TAG, "version is " + version);
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
4.2 使用 GSON:

GSON 可以将一段 JSON 格式的字符串自动映射成一个对象

  • 添加 app/build.gradle 依赖库:
    implementation 'com.google.code.gson:gson:2.7'
  • 定义一个 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 {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button sendRequest = findViewById(R.id.send_request);
        sendRequest.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                sendRequestWithHttpURLConnection();
            }
        });
    }

    /**
     * 定义一个发送请求方法,具体的逻辑都在里面编写
     */
    private void sendRequestWithHttpURLConnection(){
        // 因为网络请求是耗时操作,所以开启子线程来发起请求
        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();
                    parseJSONWithGSON(responseData);
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
        }).start();
    }
    private void parseJSONWithGSON(String jsonData){
        Gson gson = new Gson();
        List appList = gson.fromJson(jsonData, new TypeToken>(){}.getType());
        for(App app : appList){
            Log.d(TAG, "id is " + app.getId());
            Log.d(TAG, "name is " + app.getName());
            Log.d(TAG, "version is " + app.getVersion());
        }
    }
}

5. 封装一个网络库

当想要发起网络请求时,只需调用里面的静态方法即可

5.1 使用 HttpURLConnection 进行封装
  • 定义一个接口用于回调:
public interface HttpCallbackListener {

    /**
     * 当服务器成功响应请求时调用
     * @param response 服务器返回的数据
     */
    void onFinish(String response);

    /**
     * 当进行网络操作出现错误时调用
     * @param e 异常对象
     */
    void onError(Exception e);
}
  • 定义一个公共类用于发起网络请求:
public class HttpUtil {

    public static void sendHttpRequest(final String address, final HttpCallbackListener listener){
        new Thread(new Runnable() {
            @Override
            public void run() {
                HttpURLConnection connection = null;
                BufferedReader reader = null;
                try{
                    URL url = new URL(address);
                    connection = (HttpURLConnection)url.openConnection();
                    connection.setRequestMethod("GET");
                    connection.setConnectTimeout(8000);
                    connection.setReadTimeout(8000);
                    // DoInput() 用于 GET,默认是 true,可以不调用
                    // DoOutput() 用于 POST,默认是 false,用 POST 时一定要调用
                    connection.setDoInput(true);
                    connection.setDoOutput(true);
                    InputStream input = connection.getInputStream();
                    reader = new BufferedReader(new InputStreamReader(input));
                    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{
                    try{
                        if(reader != null){
                            reader.close();
                        }
                    }catch (IOException e){
                        e.printStackTrace();
                    }
                    if(connection != null){
                        connection.disconnect();
                    }
                }
            }
        }).start();
    }
}
  • 发起请求时:
HttpUtil.sendHttpRequest(address, new HttpCallbackListener(){
    @Override
    public void onFinish(String response){
        // 根据返回内容执行逻辑
    }
    @Override
    public void onError(Exception e){
        // 对异常情况进行处理
    }
});
5.2 使用 OkHttp 进行封装
  • HttpUtil 中加入 sendOkHttpRequest() 方法:
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);
    }
}
  • 调用请求方法时:
HttpUtil.sendOkHttpRequest("https://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){
        // 在这里对异常情况进行处理
        e.printStackTrace();
    }
});

你可能感兴趣的:(第 9 章 - Android 网络编程)