程序开发带来了很多便利与快捷。但随着产品功能的不断增加,服务器接口的不断复杂化,直接使用Volley原生的JSONObjectRequest已经导致Activity或Fragment层中耦合了大量的数据解析代码,同时当多处调用同一接口时,类似的数据解析代码还不可复用,导致大量重复代码的出现,已经让我越发地无法忍受。基于此,最近思考着对Volley原生的JSONObjectRequest(因为产品中目前和服务器交互所有的接口,数据都是json格式的)进行二次封装,把Activity和Fragment中大量的数据解析代码剥离出来,同时实现数据解析代码的复用。
为了把问题表现出来,先上一段坑爹的代码。
1 package com.backup;
2 import java.util.ArrayList;
3 import java.util.HashMap;
4 import java.util.Iterator;
5 import java.util.List;
6 import java.util.Map;
7
8 import org.json.JSONException;
9 import org.json.JSONObject;
10
11 import com.amuro.volleytest01_image.R;
12 import com.android.volley.RequestQueue;
13 import com.android.volley.Response;
14 import com.android.volley.VolleyError;
15 import com.android.volley.toolbox.JsonObjectRequest;
16 import com.android.volley.toolbox.Volley;
17
18 import android.app.Activity;
19 import android.os.Bundle;
20 import android.view.View;
21 import android.widget.AdapterView;
22 import android.widget.AdapterView.OnItemClickListener;
23 import android.widget.ListView;
24 import android.widget.SimpleAdapter;
25 import android.widget.TextView;
26
27 public class TestActivity02 extends Activity
28 {
29 private RequestQueue mQueue;
30 private ListView listView;
31 private List<Map<String, String>> list = new ArrayList<Map<String,String>>();
32
33 String url = "http://10.24.4.196:8081/weather.html";
34
35 @Override
36 protected void onCreate(Bundle savedInstanceState)
37 {
38 super.onCreate(savedInstanceState);
39 setContentView(R.layout.activity_test02_layout);
40 listView = (ListView)findViewById(R.id.lv_test02);
41 mQueue = Volley.newRequestQueue(this);
42 getWeatherInfo();
43
44 SimpleAdapter simpleAdapter = new SimpleAdapter(this, list,
45 android.R.layout.simple_list_item_2, new String[] {"title","content"},
46 new int[] {android.R.id.text1, android.R.id.text2});
47
48 listView.setAdapter(simpleAdapter);
49
50 listView.setOnItemClickListener(new OnItemClickListener()
51 {
52
53 @Override
54 public void onItemClick(AdapterView<?> parent, View view,
55 int position, long id)
56 {
57 TextView tv = (TextView)view.findViewById(android.R.id.text1);
58 tv.setText("111111111111111111");
59 }
60 });
61 }
62
63 public void getWeatherInfo()
64 {
65 JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(url, null,
66
67 new Response.Listener<JSONObject>()
68 {
69
70 @SuppressWarnings("unchecked")
71 @Override
72 public void onResponse(JSONObject jsonObject)
73 {
74 list.clear();
75 Iterator<String> it = jsonObject.keys();
76 while (it.hasNext())
77 {
78 String key = it.next();
79 JSONObject obj = null;
80 try
81 {
82 obj = jsonObject.getJSONObject(key);
83 }
84 catch (JSONException e)
85 {
86 e.printStackTrace();
87 }
88 if (obj != null)
89 {
90 Iterator<String> objIt = obj.keys();
91 while (objIt.hasNext())
92 {
93 String objKey = objIt.next();
94 String objValue;
95 try
96 {
97 objValue = obj.getString(objKey);
98 HashMap<String, String> map = new HashMap<String, String>();
99 map.put("title", objKey);
100 map.put("content", objValue);
101 list.add(map);
102 }
103 catch (JSONException e)
104 {
105 e.printStackTrace();
106 }
107 }
108 }
109 }
110 }
111 },
112
113 new Response.ErrorListener()
114 {
115 @Override
116 public void onErrorResponse(VolleyError arg0)
117 {
118 }
119 });
120
121 mQueue.add(jsonObjectRequest);
122 }
123 }
上面的代码大家可以看到,复杂的json解析代码全部写在Activity里,现在如果又来一个Activity需要调用这个接口,这些解析json的代码是完全无法复用的,这不科学
下面开始分析:
1. 面向对象,对于Activity这层来说,它要的只是拿到数据进行展示,至于数据怎么变出来的,它不应该关注,所以第一件事,对数据进行封装,每个接口返回的最终数据,不应该是一个未经解析的jsonObject,而应该是一个bean,千千万万的bean最终可通过泛型来统一,so,我们先需要一个监听器,让我们封装后的Volley层直接把bean回调给Activity。
2. 对错误的处理,从目前的产品需求来看,上层Activity就是要对不同的错误展示不同的界面或跳转不同的界面,所以我们把错误统一为errorCode和errorMessage,在底层封装好后,直接抛给Activity。所以这样一个返回bean或者error的接口就出来了。
1 package com.amuro.volley_framwork.network_helper;
2
3 public interface UIDataListener<T>
4 {
5 public void onDataChanged(T data);
6 public void onErrorHappened(String errorCode, String errorMessage);
7 }
3. 好,监听器剥离了Activity与我们的Volley层,下面我们就要自己对Volley的JsonObjectRequest进行封装了,先贴这个类:
1 package com.amuro.volley_framwork.network_request;
2
3 import java.io.UnsupportedEncodingException;
4 import java.net.URLEncoder;
5 import java.util.List;
6 import java.util.Map;
7
8 import org.apache.http.NameValuePair;
9 import org.apache.http.client.utils.URLEncodedUtils;
10 import org.json.JSONObject;
11
12 import com.android.volley.DefaultRetryPolicy;
13 import com.android.volley.NetworkResponse;
14 import com.android.volley.ParseError;
15 import com.android.volley.Response;
16 import com.android.volley.Response.ErrorListener;
17 import com.android.volley.Response.Listener;
18 import com.android.volley.toolbox.HttpHeaderParser;
19 import com.android.volley.toolbox.JsonRequest;
20
21 public class NetworkRequest extends JsonRequest<JSONObject>
22 {
23 private Priority mPriority = Priority.HIGH;
24
25 public NetworkRequest(int method, String url,
26 Map<String, String> postParams, Listener<JSONObject> listener,
27 ErrorListener errorListener)
28 {
29 super(method, url, paramstoString(postParams), listener, errorListener);
30 setRetryPolicy(new DefaultRetryPolicy(30000, 0, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
31 }
32
33 public NetworkRequest(String url, List<NameValuePair> params,
34 Listener<JSONObject> listener, ErrorListener errorListener)
35 {
36 this(Method.GET, urlBuilder(url, params), null, listener, errorListener);
37 }
38
39 public NetworkRequest(String url, Listener<JSONObject> listener, ErrorListener errorListener)
40 {
41 this(Method.GET, url, null, listener, errorListener);
42 }
43
44 private static String paramstoString(Map<String, String> params)
45 {
46 if (params != null && params.size() > 0)
47 {
48 String paramsEncoding = "UTF-8";
49 StringBuilder encodedParams = new StringBuilder();
50 try
51 {
52 for (Map.Entry<String, String> entry : params.entrySet())
53 {
54 encodedParams.append(URLEncoder.encode(entry.getKey(),
55 paramsEncoding));
56 encodedParams.append('=');
57 encodedParams.append(URLEncoder.encode(entry.getValue(),
58 paramsEncoding));
59 encodedParams.append('&');
60
61 }
62 return encodedParams.toString();
63 }
64 catch (UnsupportedEncodingException uee)
65 {
66 throw new RuntimeException("Encoding not supported: "
67 + paramsEncoding, uee);
68 }
69 }
70 return null;
71 }
72
73 @Override
74 protected Response<JSONObject> parseNetworkResponse(NetworkResponse response)
75 {
76
77 try
78 {
79
80 JSONObject jsonObject = new JSONObject(new String(response.data, "UTF-8"));
81
82 return Response.success(jsonObject,
83 HttpHeaderParser.parseCacheHeaders(response));
84
85 }
86 catch (Exception e)
87 {
88
89 return Response.error(new ParseError(e));
90
91 }
92 }
93
94 @Override
95 public Priority getPriority()
96 {
97 return mPriority;
98 }
99
100 public void setPriority(Priority priority)
101 {
102 mPriority = priority;
103 }
104
105 private static String urlBuilder(String url, List<NameValuePair> params)
106 {
107 return url + "?" + URLEncodedUtils.format(params, "UTF-8");
108 }
109 }
4. 接下来就是我们的重头戏,写一个Controller来操作这个request,同时对数据进行bean或error的封装,这是一个抽象类,让不同的子类根据不同的接口,趋实现不同的数据解析方式:
1 package com.amuro.volley_framwork.network_helper;
2
3 import java.util.List;
4 import java.util.Map;
5
6 import org.apache.http.NameValuePair;
7 import org.json.JSONObject;
8
9 import android.content.Context;
10 import android.util.Log;
11
12 import com.amuro.volley_framwork.network_request.NetworkRequest;
13 import com.amuro.volley_framwork.volley_queue_controller.VolleyQueueController;
14 import com.android.volley.Request.Method;
15 import com.android.volley.Response;
16 import com.android.volley.Response.ErrorListener;
17 import com.android.volley.VolleyError;
18
19 public abstract class NetworkHelper<T> implements Response.Listener<JSONObject>, ErrorListener
20 {
21 private Context context;
22
23 public NetworkHelper(Context context)
24 {
25 this.context = context;
26 }
27
28 protected Context getContext()
29 {
30 return context;
31 }
32
33 protected NetworkRequest getRequestForGet(String url, List<NameValuePair> params)
34 {
35 if(params == null)
36 {
37 return new NetworkRequest(url, this, this);
38 }
39 else
40 {
41 return new NetworkRequest(url, params, this, this);
42 }
43
44 }
45
46 protected NetworkRequest getRequestForPost(String url, Map<String, String> params)
47 {
48 return new NetworkRequest(Method.POST, url, params, this, this);
49 }
50
51 public void sendGETRequest(String url, List<NameValuePair> params)
52 {
53 VolleyQueueController.getInstance().
54 getRequestQueue(getContext()).add(getRequestForGet(url, params));
55 }
56
57 public void sendPostRequest(String url, Map<String, String> params)
58 {
59 VolleyQueueController.getInstance().
60 getRequestQueue(context).add(getRequestForPost(url, params));
61 }
62
63 @Override
64 public void onErrorResponse(VolleyError error)
65 {
66 Log.d("Amuro", error.getMessage());
67 disposeVolleyError(error);
68 }
69
70 protected abstract void disposeVolleyError(VolleyError error);
71
72 @Override
73 public void onResponse(JSONObject response)
74 {
75 Log.d("Amuro", response.toString());
76 disposeResponse(response);
77 }
78
79 protected abstract void disposeResponse(JSONObject response);
80
81 private UIDataListener<T> uiDataListener;
82
83 public void setUiDataListener(UIDataListener<T> uiDataListener)
84 {
85 this.uiDataListener = uiDataListener;
86 }
87
88 protected void notifyDataChanged(T data)
89 {
90 if(uiDataListener != null)
91 {
92 uiDataListener.onDataChanged(data);
93 }
94 }
95
96 protected void notifyErrorHappened(String errorCode, String errorMessage)
97 {
98 if(uiDataListener != null)
99 {
100 uiDataListener.onErrorHappened(errorCode, errorMessage);
101 }
102 }
103
104 }
这里对外直接提供了sendGetRequest方法和sendPostRequest方法,做为api就是要清晰明了,不要让调用者去了解还有Method.GET这样的东西,同时getRequestForGet方法和getRequestForPost方法把最常用的request直接封装好,不需要子类再去写new request的代码。当然为了拓展,这两个方法是protected的,default的request不能符合要求的时候,子类就可直接覆盖这两个方法返回自己的request,而disposeResponse和disponseError两个方法都为抽象方法,让子类针对不同的接口,实现不同的功能。
5. 下面来个子类实例,一看就懂。
1 package com.amuro.controller.networkhelper;
2
3 import org.json.JSONObject;
4
5 import android.content.Context;
6
7 import com.amuro.bean.RRBean;
8 import com.amuro.utils.SystemParams;
9 import com.amuro.volley_framwork.network_helper.NetworkHelper;
10 import com.android.volley.VolleyError;
11
12 //{"errorCode":"0000","errorMessage":"成功","respMsg":"success","success":"true"}
13 public class ReverseRegisterNetworkHelper extends NetworkHelper<RRBean>
14 {
15
16
17 public ReverseRegisterNetworkHelper(Context context)
18 {
19 super(context);
20 }
21
22 @Override
23 protected void disposeVolleyError(VolleyError error)
24 {
25 notifyErrorHappened(
26 SystemParams.VOLLEY_ERROR_CODE,
27 error == null ? "NULL" : error.getMessage());
28 }
29
30 @Override
31 protected void disposeResponse(JSONObject response)
32 {
33 RRBean rrBean = null;
34
35 if(response != null)
36 {
37 try
38 {
39 String errorCode = response.getString("errorCode");
40 String errorMessage = response.getString("errorMessage");
41 String respMsg = response.getString("respMsg");
42 String success = response.getString("success");
43
44 if("0000".equals(errorCode))
45 {
46 rrBean = new RRBean();
47 rrBean.setErrorCode(errorCode);
48 rrBean.setErrorMessage(errorMessage);
49 rrBean.setRespMsg(respMsg);
50 rrBean.setSuccess(success);
51
52 notifyDataChanged(rrBean);
53 }
54 else
55 {
56 notifyErrorHappened(errorCode, errorMessage);
57 }
58 }
59 catch(Exception e)
60 {
61 notifyErrorHappened(SystemParams.RESPONSE_FORMAT_ERROR, "Response format error");
62 }
63 }
64 else
65 {
66 notifyErrorHappened(SystemParams.RESPONSE_IS_NULL, "Response is null!");
67 }
68
69 }
70
71
72
73 }
5. 大功告成,这个NetworkHelper封装了数据解析的代码,完全可复用,最后看Activity
1 package com.amuro.ui;
2
3 import com.amuro.bean.RRBean;
4 import com.amuro.controller.networkhelper.ReverseRegisterNetworkHelper;
5 import com.amuro.utils.SystemParams;
6 import com.amuro.volley_framwork.network_helper.NetworkHelper;
7 import com.amuro.volley_framwork.network_helper.UIDataListener;
8 import com.amuro.volleytest01_image.R;
9
10 import android.app.Activity;
11 import android.os.Bundle;
12 import android.view.View;
13 import android.view.View.OnClickListener;
14 import android.widget.Button;
15 import android.widget.Toast;
16
17 public class MyVolleyTestActivity extends Activity implements UIDataListener<RRBean>
18 {
19 private Button button;
20
21 private NetworkHelper<RRBean> networkHelper;
22
23 @Override
24 protected void onCreate(Bundle savedInstanceState)
25 {
26 super.onCreate(savedInstanceState);
27 setContentView(R.layout.activity_my_volley_test_layout);
28
29 networkHelper = new ReverseRegisterNetworkHelper(this);
30 networkHelper.setUiDataListener(this);
31
32 button = (Button)findViewById(R.id.bt);
33 button.setOnClickListener(new OnClickListener()
34 {
35
36 @Override
37 public void onClick(View v)
38 {
39 sendRequest();
40 }
41 });
42 }
43
44 private void sendRequest()
45 {
46 networkHelper.sendGETRequest(SystemParams.TEST_URL, null);
47 }
48
49 @Override
50 public void onDataChanged(RRBean data)
51 {
52 Toast.makeText(
53 this,
54 data.getErrorCode() + ":" +
55 data.getErrorMessage() + ":" +
56 data.getRespMsg() + ":" +
57 data.getSuccess(),
58 Toast.LENGTH_SHORT).show();
59
60 }
61
62 @Override
63 public void onErrorHappened(String errorCode, String errorMessage)
64 {
65 Toast.makeText(
66 this,
67 errorCode + ":" + errorMessage,
68 Toast.LENGTH_SHORT).show();
69
70 }
71 }
看,Activity直接拿到的就是数据或者errorCode,把一大堆复杂的数据解析代码剥离了。