产品中使用Volley框架已有多时,本身已有良好封装的Volley确实给程序开发带来了很多便利与快捷。但随着产品功能的不断增加,服务器接口的不断复杂化,直接使用Volley原生的JSONObjectRequest已经导致Activity或Fragment层中耦合了大量的数据解析代码,同时当多处调用同一接口时,类似的数据解析代码还不可复用,导致大量重复代码的出现,已经让我越发地无法忍受。基于此,最近思考着对Volley原生的JSONObjectRequest(因为产品中目前和服务器交互所有的接口,数据都是json格式的)进行二次封装,把Activity和Fragment中大量的数据解析代码剥离出来,同时实现数据解析代码的复用。
为了把问题表现出来,先上一段坑爹的代码。
package com.backup; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.json.JSONException; import org.json.JSONObject; import com.amuro.volleytest01_image.R; import com.android.volley.RequestQueue; import com.android.volley.Response; import com.android.volley.VolleyError; import com.android.volley.toolbox.JsonObjectRequest; import com.android.volley.toolbox.Volley; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; import android.widget.SimpleAdapter; import android.widget.TextView; public class TestActivity02 extends Activity { private RequestQueue mQueue; private ListView listView; private List<Map<String, String>> list = new ArrayList<Map<String,String>>(); String url = "http://10.24.4.196:8081/weather.html"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test02_layout); listView = (ListView)findViewById(R.id.lv_test02); mQueue = Volley.newRequestQueue(this); getWeatherInfo(); SimpleAdapter simpleAdapter = new SimpleAdapter(this, list, android.R.layout.simple_list_item_2, new String[] {"title","content"}, new int[] {android.R.id.text1, android.R.id.text2}); listView.setAdapter(simpleAdapter); listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { TextView tv = (TextView)view.findViewById(android.R.id.text1); tv.setText("111111111111111111"); } }); } public void getWeatherInfo() { JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(url, null, new Response.Listener<JSONObject>() { @SuppressWarnings("unchecked") @Override public void onResponse(JSONObject jsonObject) { list.clear(); Iterator<String> it = jsonObject.keys(); while (it.hasNext()) { String key = it.next(); JSONObject obj = null; try { obj = jsonObject.getJSONObject(key); } catch (JSONException e) { e.printStackTrace(); } if (obj != null) { Iterator<String> objIt = obj.keys(); while (objIt.hasNext()) { String objKey = objIt.next(); String objValue; try { objValue = obj.getString(objKey); HashMap<String, String> map = new HashMap<String, String>(); map.put("title", objKey); map.put("content", objValue); list.add(map); } catch (JSONException e) { e.printStackTrace(); } } } } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError arg0) { } }); mQueue.add(jsonObjectRequest); } } <span style="font-family:Arial;BACKGROUND-COLOR: #ffffff"></span>
<span style="font-family:Arial;BACKGROUND-COLOR: #ffffff"> 上面的代码大家可以看到,复杂的json解析代码全部写在Activity里,现在如果又来一个Activity需要调用这个接口,这些解析json的代码是完全无法复用的,这不科学~</span>
<span style="font-family:Arial;BACKGROUND-COLOR: #ffffff"> 好,下面开始装逼,哦不,分析:</span>
<span style="font-family:Arial;BACKGROUND-COLOR: #ffffff"> 1. 面向对象,对于Activity这层来说,它要的只是拿到数据进行展示,至于数据怎么变出来的,它不应该关注,所以第一件事,对数据进行封装,每个接口返回的最终数据,不应该是一个未经解析的jsonObject,而应该是一个bean,千千万万的bean最终可通过泛型来统一,so,我们先需要一个监听器,让我们封装后的Volley层直接把bean回调给Activity。</span>
<span style="font-family:Arial;BACKGROUND-COLOR: #ffffff"> 2. 对错误的处理,从目前的产品需求来看,上层Activity就是要对不同的错误展示不同的界面或跳转不同的界面,所以我们把错误统一为errorCode和errorMessage,在底层封装好后,直接抛给Activity。所以这样一个返回bean或者error的接口就出来了。</span>
package com.amuro.volley_framwork.network_helper; public interface UIDataListener<T> { public void onDataChanged(T data); public void onErrorHappened(String errorCode, String errorMessage); }
3. 好,监听器剥离了Activity与我们的Volley层,下面我们就要自己对Volley的JsonObjectRequest进行封装了,先贴这个类:
package com.amuro.volley_framwork.network_request; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.List; import java.util.Map; import org.apache.http.NameValuePair; import org.apache.http.client.utils.URLEncodedUtils; import org.json.JSONObject; import com.android.volley.DefaultRetryPolicy; import com.android.volley.NetworkResponse; import com.android.volley.ParseError; import com.android.volley.Response; import com.android.volley.Response.ErrorListener; import com.android.volley.Response.Listener; import com.android.volley.toolbox.HttpHeaderParser; import com.android.volley.toolbox.JsonRequest; public class NetworkRequest extends JsonRequest<JSONObject> { private Priority mPriority = Priority.HIGH; public NetworkRequest(int method, String url, Map<String, String> postParams, Listener<JSONObject> listener, ErrorListener errorListener) { super(method, url, paramstoString(postParams), listener, errorListener); setRetryPolicy(new DefaultRetryPolicy(30000, 0, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)); } public NetworkRequest(String url, List<NameValuePair> params, Listener<JSONObject> listener, ErrorListener errorListener) { this(Method.GET, urlBuilder(url, params), null, listener, errorListener); } public NetworkRequest(String url, Listener<JSONObject> listener, ErrorListener errorListener) { this(Method.GET, url, null, listener, errorListener); } private static String paramstoString(Map<String, String> params) { if (params != null && params.size() > 0) { String paramsEncoding = "UTF-8"; StringBuilder encodedParams = new StringBuilder(); try { for (Map.Entry<String, String> entry : params.entrySet()) { encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding)); encodedParams.append('='); encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding)); encodedParams.append('&'); } return encodedParams.toString(); } catch (UnsupportedEncodingException uee) { throw new RuntimeException("Encoding not supported: " + paramsEncoding, uee); } } return null; } @Override protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) { try { JSONObject jsonObject = new JSONObject(new String(response.data, "UTF-8")); return Response.success(jsonObject, HttpHeaderParser.parseCacheHeaders(response)); } catch (Exception e) { return Response.error(new ParseError(e)); } } @Override public Priority getPriority() { return mPriority; } public void setPriority(Priority priority) { mPriority = priority; } private static String urlBuilder(String url, List<NameValuePair> params) { return url + "?" + URLEncodedUtils.format(params, "UTF-8"); } }
4. 接下来就是我们的重头戏,写一个Controller来操作这个request,同时对数据进行bean或error的封装,这是一个抽象类,让不同的子类根据不同的接口,趋实现不同的数据解析方式:
package com.amuro.volley_framwork.network_helper; import java.util.List; import java.util.Map; import org.apache.http.NameValuePair; import org.json.JSONObject; import android.content.Context; import android.util.Log; import com.amuro.volley_framwork.network_request.NetworkRequest; import com.amuro.volley_framwork.volley_queue_controller.VolleyQueueController; import com.android.volley.Request.Method; import com.android.volley.Response; import com.android.volley.Response.ErrorListener; import com.android.volley.VolleyError; public abstract class NetworkHelper<T> implements Response.Listener<JSONObject>, ErrorListener { private Context context; public NetworkHelper(Context context) { this.context = context; } protected Context getContext() { return context; } protected NetworkRequest getRequestForGet(String url, List<NameValuePair> params) { if(params == null) { return new NetworkRequest(url, this, this); } else { return new NetworkRequest(url, params, this, this); } } protected NetworkRequest getRequestForPost(String url, Map<String, String> params) { return new NetworkRequest(Method.POST, url, params, this, this); } public void sendGETRequest(String url, List<NameValuePair> params) { VolleyQueueController.getInstance(). getRequestQueue(getContext()).add(getRequestForGet(url, params)); } public void sendPostRequest(String url, Map<String, String> params) { VolleyQueueController.getInstance(). getRequestQueue(context).add(getRequestForPost(url, params)); } @Override public void onErrorResponse(VolleyError error) { Log.d("Amuro", error.getMessage()); disposeVolleyError(error); } protected abstract void disposeVolleyError(VolleyError error); @Override public void onResponse(JSONObject response) { Log.d("Amuro", response.toString()); disposeResponse(response); } protected abstract void disposeResponse(JSONObject response); private UIDataListener<T> uiDataListener; public void setUiDataListener(UIDataListener<T> uiDataListener) { this.uiDataListener = uiDataListener; } protected void notifyDataChanged(T data) { if(uiDataListener != null) { uiDataListener.onDataChanged(data); } } protected void notifyErrorHappened(String errorCode, String errorMessage) { if(uiDataListener != null) { uiDataListener.onErrorHappened(errorCode, errorMessage); } } }
这里对外直接提供了sendGetRequest方法和sendPostRequest方法,做为api就是要清晰明了,不要让调用者去了解还有Method.GET这样的东西,同时getRequestForGet方法和getRequestForPost方法把最常用的request直接封装好,不需要子类再去写new request的代码。当然为了拓展,这两个方法是protected的,default的request不能符合要求的时候,子类就可直接覆盖这两个方法返回自己的request,而disposeResponse和disponseError两个方法都为抽象方法,让子类针对不同的接口,实现不同的功能。
5. 下面来个子类实例,一看就懂。
package com.amuro.controller.networkhelper; import org.json.JSONObject; import android.content.Context; import com.amuro.bean.RRBean; import com.amuro.utils.SystemParams; import com.amuro.volley_framwork.network_helper.NetworkHelper; import com.android.volley.VolleyError; //{"errorCode":"0000","errorMessage":"成功","respMsg":"success","success":"true"} public class ReverseRegisterNetworkHelper extends NetworkHelper<RRBean> { public ReverseRegisterNetworkHelper(Context context) { super(context); } @Override protected void disposeVolleyError(VolleyError error) { notifyErrorHappened( SystemParams.VOLLEY_ERROR_CODE, error == null ? "NULL" : error.getMessage()); } @Override protected void disposeResponse(JSONObject response) { RRBean rrBean = null; if(response != null) { try { String errorCode = response.getString("errorCode"); String errorMessage = response.getString("errorMessage"); String respMsg = response.getString("respMsg"); String success = response.getString("success"); if("0000".equals(errorCode)) { rrBean = new RRBean(); rrBean.setErrorCode(errorCode); rrBean.setErrorMessage(errorMessage); rrBean.setRespMsg(respMsg); rrBean.setSuccess(success); notifyDataChanged(rrBean); } else { notifyErrorHappened(errorCode, errorMessage); } } catch(Exception e) { notifyErrorHappened(SystemParams.RESPONSE_FORMAT_ERROR, "Response format error"); } } else { notifyErrorHappened(SystemParams.RESPONSE_IS_NULL, "Response is null!"); } } }
5. 大功告成,这个NetworkHelper封装了数据解析的代码,完全可复用,最后看Activity
package com.amuro.ui; import com.amuro.bean.RRBean; import com.amuro.controller.networkhelper.ReverseRegisterNetworkHelper; import com.amuro.utils.SystemParams; import com.amuro.volley_framwork.network_helper.NetworkHelper; import com.amuro.volley_framwork.network_helper.UIDataListener; import com.amuro.volleytest01_image.R; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.Toast; public class MyVolleyTestActivity extends Activity implements UIDataListener<RRBean> { private Button button; private NetworkHelper<RRBean> networkHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my_volley_test_layout); networkHelper = new ReverseRegisterNetworkHelper(this); networkHelper.setUiDataListener(this); button = (Button)findViewById(R.id.bt); button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { sendRequest(); } }); } private void sendRequest() { networkHelper.sendGETRequest(SystemParams.TEST_URL, null); } @Override public void onDataChanged(RRBean data) { Toast.makeText( this, data.getErrorCode() + ":" + data.getErrorMessage() + ":" + data.getRespMsg() + ":" + data.getSuccess(), Toast.LENGTH_SHORT).show(); } @Override public void onErrorHappened(String errorCode, String errorMessage) { Toast.makeText( this, errorCode + ":" + errorMessage, Toast.LENGTH_SHORT).show(); } }
看,Activity直接拿到的就是数据或者errorCode,把一大堆复杂的数据解析代码剥离了。
今天就到这里,下一篇讲用简单工厂实现listView中不同item不同界面的实现方式,干掉Adapter的getView方法中,无尽的if else。
原文地址:http://blog.csdn.net/amurocrash/article/details/44256495