產品中使用Volley框架已有多時,自己已有良好封裝的Volley確實給程序開發帶來了不少便利與快捷。但隨着產品功能的不斷增長,服務器接口的不斷複雜化,直接使用Volley原生的JSONObjectRequest已經致使Activity或Fragment層中耦合了大量的數據解析代碼,同時當多處調用同一接口時,相似的數據解析代碼還不可複用,致使大量重複代碼的出現,已經讓我愈加地沒法忍受。基於此,最近思考着對Volley原生的JSONObjectRequest(由於產品中目前和服務器交互全部的接口,數據都是json格式的)進行二次封裝,把Activity和Fragment中大量的數據解析代碼剝離出來,同時實現數據解析代碼的複用。php
爲了把問題表現出來,先上一段坑爹的代碼。html
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 }java
上面的代碼你們能夠看到,複雜的json解析代碼所有寫在Activity裏,如今若是又來一個Activity須要調用這個接口,這些解析json的代碼是徹底沒法複用的,這不科學android
下面開始分析:apache
1. 面向對象,對於Activity這層來講,它要的只是拿到數據進行展現,至於數據怎麼變出來的,它不該該關注,因此第一件事,對數據進行封裝,每一個接口返回的最終數據,不該該是一個未經解析的jsonObject,而應該是一個bean,千千萬萬的bean最終可經過泛型來統一,so,咱們先須要一個監聽器,讓咱們封裝後的Volley層直接把bean回調給Activity。
2. 對錯誤的處理,從目前的產品需求來看,上層Activity就是要對不一樣的錯誤展現不一樣的界面或跳轉不一樣的界面,因此咱們把錯誤統一爲errorCode和errorMessage,在底層封裝好後,直接拋給Activity。因此這樣一個返回bean或者error的接口就出來了。json
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 }api
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 }app
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,把一大堆複雜的數據解析代碼剝離了。