9.4 解析JSON格式数据
比起XML,JSON的主要优势在于它的体积更小,在网络上传输的时候可以更省流量。但是缺点在于语义性较差,看起来不如XML直观。
在开始之前,需要在服务器的/htdocs目录中新建一个get_data.json的文件并编辑:
//json格式 [{"id":"5","version":"5.5","name":"Wang"}, {"id":"3","version":"3.0","name":"Xiong"}, {"id":"1","version":"3.1","name":"Wu"}]这时在浏览器中访问http://127.0.0.1/get_data.json网址就可以查看到上面的内容
9.4.1 使用JSONObject
解析JSON数据也有很多种方法,这里介绍两种JSONObject和GSON。
- //修改MainActivity中的代码
- public class MainActivity extends AppCompatActivity implements Views.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();
- Response response = Client.newCall(request).execute();
- String responseData = response.body().string();
- //解析服务器返回的数据
- parseJSONWithJSONObject(responseData);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }).start();
- }
- ...
- private void parseJSONWithJSONObject(String jsonData) {
- try {
- //将服务器返回的数据传入一个JSONArray对象中
- 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("MainActivtiy","id is"+id);
- Log.d("MainActivity","name is"+name);
- Log.d("MainActivity","version is"+version);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
9.4.2 使用GSON
谷歌提供的GSON开源库可以让解析JSON数据的额工作简单到让你不敢想象的地步。
注意:GSON并没有被添加到Android官方的API中,因此如果想要使用这个功能的话,就必须要在项目中添加GSON的依赖库。编辑app/build.gradle文件,在dependencies闭包中添加:
compile 'com.google.code.gson:gson:2.7'
GSON的用法
1)例如JSON的字段有三个,我们可以定义一个类,并加入JSON的三个字段
eg:Gson gson = new Gson();
Person person = gson.fromJson(jsonData,Person.class);
2)注意:如果解析的是一段JSON数组会稍微麻烦一点,我们需要借助TypeToken将期望解析成的数据类型传入到fromJson()方法中
eg: Listpeople = gson.fromJson(jsonData,newTypeToken >(){}.getType());
- //新增一个App类
- public class App {
- private String id;
- private String name;
- private String version;
- public String getId() {
- return id;
- }
- public String getName() {
- return name;
- }
- public String getVersion() {
- return version;
- }
- public void setId(String id) {
- this.id = id;
- }
- public void setName(String name) {
- this.name = name;
- }
- public void setVersion(String version) {
- this.version= version;
- }
- }
- //修改MainActivity中的代码
- public class MainActivity extends AppCompatActivity implements Views.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();
- Response response = Client.newCall(request).execute();
- String responseData = response.body().string();
- //解析服务器返回的数据
- parseJSONWithGSON(responseData);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }).start();
- }
- ...
- private void parseJSONWithGSON(String jsonData) {
- Gson gson = new Gson();
- List
appList = gson.formJson(jsonData,new TypeToken>(){}.getType());
- for (App app : appList) {
- Log.d("MainActivtiy","id is"+app.getId());
- Log.d("MainActivity","name is"+app.getName());
- Log.d("MainActivity","version is"+app.getVersion());
- }
- }
- }
9.5 网络编程的最佳实践
由于一个应用程序很可能会在许多地方都用到网络功能,而发送HTTP请求的代码基本都是相同的,所以可以将这些通用的网络操作提取到一个公共的类里,并提供一个静态方法,当想要发起网络请求的时候,只需简单地调用一下这个方法即可。
- //创建一个类,并在类中新建一个静态方法
- public class HttpUtil {
- HttpURLConnection connection = null;
- try {
- URL url = new URL("http://www.baidu.com");
- connection = (HttpURLConnection) url.openConnetion(); //获取连接对象
- 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 reponse = new StringBuilder();
- String line;
- while((line = reader.readLine()) != null) {
- response.append(line); //一行行的读取追加到StringBuilder中
- }
- return response.toString();
- } catch (Exception e) {
- e.printStackTrace();
- return e.getMessage();
- } finally {
- if (connection != null) {
- connection.disconnect(); // 关闭连接
- }
- }
- }
- }
- //每当需要发起一条HTTP请求的时候就可以这样写
- String address = "Http://www.baidu.com";
- String response = HttpUtil.sendHttpRequest(address);
注意:由于sendHttpRequest()方法的内部并没有开启线程,这样就可能导致在调用sendHttpRequest()方法的时候内部并没有开启线程,这样就有可能导致在调用sendHttpRequest()方法的时候使得主线程被阻塞住。
解决方法:使用Java的回调机制就可以解决。
- //首先需要定义一个接口,比如将它命名成HttpCallbackListener
- public interface HttpCallbackListener {
- void onFinish(String response); //服务器响应成功时调用,response是服务器返回的数据
- void onError(Exception e); //网络操作出现错误时调用
- }
- //继续修改HttpUtil中的代码
- public class HttpUtil {
- //给sendHttpRequest方法添加了一个HttpCallbackListener参数,并在内部开启了一个线程,在子线程里去执行具体的网络操作
- //子线程中是无法通过return语气来返回数据的,因此这里我们将服务器响应的数据传入了HttpCallbackListener的onFinish()方法中,,如果出现了异常就将异常原因传入到了onError()方法只能怪
- public static void sendHttpRequest(final String address, final HttpCallbackListener listener) {
- new Thread(new Runnbale() {
- @Override
- public void run() {
- HttpURLConnection connection = null;
- try {
- URL url = new URL("http://www.baidu.com");
- connection = (HttpURLConnection) url.openConnetion(); //获取连接对象
- 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 reponse = new StringBuilder();
- String line;
- while((line = reader.readLine()) != null) {
- response.append(line); //一行行的读取追加到StringBuilder中
- }
- 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();
- }
- }
- //现在sendHttpRequest()方法接受两个参数了,因此我们在调用它的时候还需要将HttpCallbackListener的实例传入
- HttpUtil.sendHttpRequest(address,new HttpCallbackListener() {
- @Override
- public void onFinish(String response) {
- //在这里根据返回内容执行具体的逻辑
- }
- @Override
- public void onError(Exception e) {
- //在这里对异常情况进行处理
- }
- });
- //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); //enqueue方法内部已经帮我们开好线程了
- }
- }
- //那么我们在调用sendOkHttpRequest()方法的时候就可以这样写
- 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) {
- //在这里对异常情况进行处理
- }
- });
注意:不管是HttpURLConnection还是OkHttp,最终的回调接口都还是在子线程中运行的,因此我么不可以在这里执行任何的UI操作,除非借助runOnUIThread()方法来进行线程转换。