0%

Android 详解网络开发

1. 写一个简单的使用 WebView 的 Demo

  • Demo

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 首先,需要在 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 详解

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

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

3. 写一个简单的使用 HttpURLConnection 的 Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// 首先,需要在 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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    //  首先,需要在 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. Retrofit2.0 源码分析

6. 网络传输数据时常用的格式、特点和解析方式

  • 格式

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

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

    • XML:PULL 解析、 SAX 解析、DOM 解析
    • JSON:官方提供的 JSONObject、Google 的开源库 GSON、第三方的开源库 Jackson、FastJSON

7. 写一个使用 PULL 解析方式解析 XML 数据的 Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// 省略在本机搭建 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();
}
}
}

8. 写一个使用 SAX 解析方式解析 XML 数据的 Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// 省略在本机搭建 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();
}
}

9. 写一个使用 JSONObject 解析 JSON 数据的 Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 省略在本机搭建 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();
}
}
}

10. 写一个使用 GSON 解析 JSON 数据的 Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 省略在本机搭建 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;
}
}

11. 分别使用 HttpURLConnectionOkHttp 写一个封装的网络请求工具类 Demo

  • 使用 HttpURLConnention

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    // 工具类 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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 工具类 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) {
    // 在这里对异常情况进行处理
    }
    });

12. OkHttp3 中 execute() 方法和 enqueue() 方法的区别是

  • execute() 是同步方法
  • enqueue() 是异步方法
-------------------- 本文结束感谢您的阅读 --------------------