Android 详解网络开发

1.【笔试题】手写一个简单的使用 WebViewDemo?

答:

  • Demo

    // 首先,需要在 AndroidManifest.xml 文件中声明网络权限:<uses-permission android:name="android.permission.INTERNET" />
    public class MainActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            WebView webView = (WebView) findViewById(R.id.web_view);
            webView.getSettings().setJavaScriptEnabled(true); // 支持 JavaScript 脚本
            // 当需要从一个网页跳转到另一个网页时,目标网页仍然在当前 WebView 中显示,而不是打开系统浏览器
            webView.setWebViewClient(new WebViewClient()); 
            webView.loadUrl("http://www.baidu.com");
        }
    }
    
  • WebView 详解:参考Android:这是一份全面 & 详细的Webview使用攻略

2. Android 中发送 HTTP 请求的方式?

答:

  • 一般有两种:HttpURLConnectionHttpClient
  • 官方建议使用 HttpURLConnection
  • HttpClient 存在 API 数量过多、扩展困难等缺点,Android 6.0 系统中,HttpClient 的功能被完全移除了,标志此功能被正式弃用。

3. 【笔试题】手写一个简单的使用 HttpURLConnectionDemo?

答:

// 首先,需要在 AndroidManifest.xml 文件中声明网络权限:<uses-permission android:name="android.permission.INTERNET" />
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private TextView responseText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button sendRequest = (Button) findViewById(R.id.send_request);
        responseText = (TextView) findViewById(R.id.response_text);
    }

    @Override
    public void onClick(View v) {
        if(v.getId() == R.id.send_request) {
            sendRequestWithHttpURLConnection();
        }
    }

    private void sendRequestWithURLConnection() {
        // 开启线程来发起网络请求并读取返回数据
        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("POST");
                    // DataOutputStream out = new DataOutputStream(connection.getOutputStream());
                    // out.writeBytes("username=admin&password=123456");
                    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.printStackTrace();
                } finally {
                    if(reader != null) {
                        try {
                            reader.close();
                        } catch(IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if(connection != null) {
                        connection.disconnection();
                    }
                }
            }
        }).start();
    }

    // 这里,服务器返回的是没有解析过的 HTML 代码
    private void showResponse(final String response) {
        runOnUiThread(new Runnable() { // 切换到 UI 线程,即主线程中进行 UI 操作
            @Override
            public void run() {
                // 在这里进行 UI 操作,将结果显示到界面上
                responseText.setText(response); // Android 不允许在子线程中进行 UI 操作
            }
        });
    }
}

4. 【笔试题】手写一个简单的使用 OkHttp 库发送网络请求的 Demo

答:

  • Demo

    // 首先,需要在 AndroidManifest.xml 文件中声明网络权限:<uses-permission android:name="android.permission.INTERNET" />
    // 其次,需要在 app/build.gradle 文件的 dependencies 闭包中添加依赖:compile 'com.squareup.okhttp3:okhttp:3.4.1'
    // 上述依赖会自动下载两个库,一个是 OkHttp 库,一个是 Okio 库,后者是前者的通信基础
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
        ...
        @Override
        public void onClick(View v) {
            if(v.getId() == R.id.send_request) {
                sendRequestWithOkHttp();
            }
        }
    
        private void sendRequestWithOkHttp() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        OkHttpClient client = new OkHttpClient();
                        // 如果发起 POST 请求,可以这样写:
                        // 先构建一个 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();
                        // 接下来就和 GET 请求一样了,调用 execute() 方法发送请求并获取服务器返回的数据
                        Request request = new Request.Builder().url("http://www.baidu.com").build();
                        Response response = client.newCall(request).execute(); // execute() 方法发送请求并获取服务器返回的数据
                        String responseData = response.body().string();
                        showResponse(responseData); // 方法同上题
                    } catch(Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        ...
    }
    
  • OkHttp源码分析

5. 网络传输数据时常用的格式有?特点是?解析方式是?

答:

  • 格式
    • XMLExtensible Markup Language,可扩展标记语言,参考:XML 教程
    • JSONJavaScript Object NotationJavaScript 对象表示法。
      • 参考:JSON 教程(数据使用键值对形式、数据由逗号分隔、花括号保存对象、方括号保存数组)。
      • 注意:由于 JSON 中的一些字段可能不太适合直接作为 Java 字段来命名,因此可以使用 @SerializedName 注解的方式让 JSON 字段和 Java 字段之间建立映射关系。
  • 特点

    • XML:语义性较好,比较直观。
    • JSON:体积更小,更省流量。
  • 解析方式

    • XMLPULL 解析、 SAX 解析、DOM 解析。
    • JSON:官方提供的 JSONObjectGoogle 的开源库 GSON、第三方的开源库 JacksonFastJSON 等。

6. 【笔试题】手写一个使用 PULL 解析方式解析 XML 数据的 Demo?

答:

// 省略在本机搭建 Web 服务器和在服务器目录下新建 xml 文件的过程
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    ...
    private void sendRequestWithOkHttp() {
        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(); // url() 方法参数中指定访问的服务器地址是电脑本机
                    Response response = client.newCall(request).execute();
                    String responseData = response.body().string();
                    parseXMLWithPull(responseData);
                } catch(Exception 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 id = "";
            String name = "";
            String version = "";
            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("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();
        }
    }

}

7. 【笔试题】手写一个使用 SAX 解析方式解析 XML 数据的 Demo?

答:

// 省略在本机搭建 Web 服务器和在服务器目录下新建 xml 文件的过程
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    ...
    private void sendRequestWithOkHttp() {
        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(); // url() 方法参数中指定访问的服务器地址是电脑本机
                    Response response = client.newCall(request).execute();
                    String responseData = response.body().string();
                    parseXMLWithSAX(responseData);
                } catch(Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
    ...
    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();
        }
    }

}

// 新建一个 ContentHandler 类继承自 DefaultHandler,并重写父类的 5 个方法
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();
    }
}

8. 【笔试题】手写一个使用 JSONObject 解析 JSON 数据的 Demo

答:

// 省略在本机搭建 Web 服务器和在服务器目录下新建 json 文件的过程
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    ...
    private void sendRequestWithOkHttp() {
        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(); // url() 方法参数中指定访问的服务器地址是电脑本机
                    Response response = client.newCall(request).execute();
                    parseJSONWithJSONObject(responseData);
                } catch(Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
    ...
    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();
        }
    }
}

9. 【笔试题】手写一个使用 GSON 解析 JSON 数据的 Demo?

答:

// 省略在本机搭建 Web 服务器和在服务器目录下新建 json 文件的过程
// GSON 并没有被添加到 Android 官方的 API 中,所以需要在 app/build.gradle 文件的 dependencies 闭包中添加依赖:compile 'com.google.code.gson:gson:2.7'
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    ...
    private void sendRequestWithOkHttp() {
        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(); // url() 方法参数中指定访问的服务器地址是电脑本机
                    Response response = client.newCall(request).execute();
                    parseJSONWithGSON(responseData);
                } catch(Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
    ...
    private void parseJSONWithGSON(String jsonData) {
        Gson gson = new Gson();
        List<App> appList = gson.fromJson(jsonData, new TypeToken<List<App>>() {}.getType()); // 解析单个 JSON 对象:Person person = gson.fromJson(jsonData, Person.class);
        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());
        }
    }
}

// 新建一个 App 类,并加入 id、name 和 version 这 3 个字段和 setter、getter 方法
public class App {
    private String id;
    private String name;
    private String version;

    public String getId() {
        return id;
    }

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

10. 【笔试题】分别使用 HttpURLConnectionOkHttp 手写一个封装的网络请求工具类 Demo?

答:

  • 使用 HttpURLConnention

    // 工具类 HttpUtil
    public class HttpUtil {
        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.setConnectionTimeout(8000);
                        connection.setReadTimeout(8000);
                        connection.setDoInput(true);
                        connection.setDoOutput(true);
                        InputStream in = connection.getInpustStream();
                        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.disconnection();
                        }
                    }
                }
            }).start();
        }
    }
    
    // 定义回调接口 HttpCallbackListener
    public interface HttpCallbackListener {
        void onFinish(String response);
        void onError(Exception e);
    }
    
    // 使用这个工具类
    HttpUtil.sendHttpRequest(address, new HttpCallbackListener() {
        @Override
        public void onFinish(String response) {
            // 在这里根据返回内容执行具体的逻辑
        }
    
        @Override
        public void onError(Exception e) {
            // 在这里对异常情况进行处理
        }
    });
    
  • 使用 OkHttp

    // 工具类 HttpUtil
    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("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) {
            // 在这里对异常情况进行处理
        }
    });
    

11. OkHttp3execute() 方法和 enqueue() 方法的区别是?

答:execute() 是同步方法;enqueue() 是异步方法

-------------本文结束感谢您的阅读-------------