也谈Volley的二次封装

来源:互联网 发布:java用for循环打印菱形 编辑:程序博客网 时间:2024/05/17 22:21

        产品中使用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";@Overrideprotected 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(){@Overridepublic 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")@Overridepublic 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(){@Overridepublic void onErrorResponse(VolleyError arg0){}});mQueue.add(jsonObjectRequest);}}
<pre class="java" name="code"><span style="font-family: Arial, Helvetica, sans-serif;"></span>
上面的代码大家可以看到,复杂的json解析代码全部写在Activity里,现在如果又来一个Activity需要调用这个接口,这些解析json的代码是完全无法复用的,这不科学~
          好,下面开始装逼,哦不,分析:
     1. 面向对象,对于Activity这层来说,它要的只是拿到数据进行展示,至于数据怎么变出来的,它不应该关注,所以第一件事,对数据进行封装,每个接口返回的最终数据,不应该是一个未经解析的jsonObject,而应该是一个bean,千千万万的bean最终可通过泛型来统一,so,我们先需要一个监听器,让我们封装后的Volley层直接把bean回调给Activity。
     2. 对错误的处理,从目前的产品需求来看,上层Activity就是要对不同的错误展示不同的界面或跳转不同的界面,所以我们把错误统一为errorCode和errorMessage,在底层封装好后,直接抛给Activity。所以这样一个返回bean或者error的接口就出来了。
</pre><pre class="java" name="code">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;}@Overrideprotected 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));}}@Overridepublic 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));}@Overridepublic void onErrorResponse(VolleyError error){Log.d("Amuro", error.getMessage());disposeVolleyError(error);}protected abstract void disposeVolleyError(VolleyError error);@Overridepublic 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);}@Overrideprotected void disposeVolleyError(VolleyError error){notifyErrorHappened(SystemParams.VOLLEY_ERROR_CODE, error == null ? "NULL" : error.getMessage());}@Overrideprotected 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;@Overrideprotected 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(){@Overridepublic void onClick(View v){sendRequest();}});}private void sendRequest(){networkHelper.sendGETRequest(SystemParams.TEST_URL, null);}@Overridepublic void onDataChanged(RRBean data){Toast.makeText(this, data.getErrorCode() + ":" + data.getErrorMessage() + ":" + data.getRespMsg() + ":" + data.getSuccess(), Toast.LENGTH_SHORT).show();}@Overridepublic void onErrorHappened(String errorCode, String errorMessage){Toast.makeText(this, errorCode + ":" + errorMessage, Toast.LENGTH_SHORT).show();}}


        看,Activity直接拿到的就是数据或者errorCode,把一大堆复杂的数据解析代码剥离了。

 

        今天就到这里,下一篇讲用简单工厂实现listView中不同item不同界面的实现方式,干掉Adapter的getView方法中,无尽的if else。

                                             
5 1