也谈Volley的二次封装

        产品中使用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);
	}
}
<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;
	}

	@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。


                
  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值