volley二次封装
产品中使用Volley框架已有多时,本身已有良好封装的Volley确实给程序开发带来了很多便利与快捷。但随着产品功能的不断增加,服务器接口的不断复杂化,直接使用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,把一大堆复杂的数据解析代码剥离了。
转自:http://www.aichengxu.com/view/46975