转自:http://1029457926.iteye.com/blog/2264167
使用Volery已经快整整一年了,下面我来总结一下,我使用Volley时踩到的坑
(一) Volley的二次封装
下面看看我是怎么对Volley的二次封装的:
- protected <T> void doSimpleRequest(String url, HashMap<String, String> params, final Class<?> clazz, final SimpleCallback callback) {
- String requestUrl = urlBuilder(url, params);
- Logger.e("url", requestUrl);
- Response.Listener<String> successListener = new Response.Listener<String>() {
- @Override
- public void onResponse(String response) {
- try {
- T bean = GsonUtils.getFromStr(clazz, response);
- callback.onResponse(bean);
- } catch (Exception e) {
- callback.onError();
- }
- }
- };
- Response.ErrorListener errorListener = new Response.ErrorListener() {
- @Override
- public void onErrorResponse(VolleyError error) {
- callback.onError();
- }
- };
- StringRequest requestRequest = new StringRequest(url, params, successListener, errorListener);
- LeSportsApplication.getApplication().addHttpRequest(requestRequest);
- }
请求的时候使用的StringRequest,请求成功后,会返回一个String类型,然后用Gson解析成JavaBean,我之前一直都这么使用,看似天衣无缝,其实你会发现JSon解析是在主线程中实现的,如果数据量大的话,很容易导致UI卡顿。优化方案就是在数据解析在子线程中进行
(1) 新建一个GsonRequest请求类,继承Request对象
(2) 重写parseNetworkResponse方法,这个方法其实是在CacheDispatch这个子线程中执行的
- <span style="font-size: 18px;">package com.lesports.common.volley.toolbox;
- import android.os.Looper;
- import com.google.gson.Gson;
- import com.google.gson.JsonSyntaxException;
- import com.lesports.common.volley.NetworkResponse;
- import com.lesports.common.volley.ParseError;
- import com.lesports.common.volley.Request;
- import com.lesports.common.volley.Response;
- import com.letv.core.log.Logger;
- import org.json.JSONException;
- import org.json.JSONObject;
- import java.io.UnsupportedEncodingException;
- import java.util.HashMap;
- /**
- * Created by liuyu8 on 2015/9/15.
- */
- public class MyGsonRequest<T> extends Request<T>{
- private final Logger mLogger = new Logger("GsonRequest");
- private final Gson gson = new Gson();
- private final Class<T> clazz;
- private Response.Listener<T> listener;
- /**
- * Make a GET request and return a parsed object from JSON. Assumes
- * {@link Request.Method#GET}.
- *
- * @param url
- * URL of the request to make
- * @param clazz
- * Relevant class object, for Gson's reflection
- */
- public MyGsonRequest(String url,HashMap<String, String> params,Class<T> clazz, Response.Listener<T> listener, Response.ErrorListener errorListener) {
- super(Request.Method.GET, url, params,errorListener);
- this.clazz = clazz;
- this.listener = listener;
- }
- /**
- * Make a GET request and return a parsed object from JSON. Assumes
- * {@link Request.Method#GET}.
- *
- * @param url
- * URL of the request to make
- * @param clazz
- * Relevant class object, for Gson's reflection
- */
- public MyGsonRequest(String url, Class<T> clazz, Response.Listener<T> listener, Response.ErrorListener errorListener) {
- super(Request.Method.GET, url, errorListener);
- this.clazz = clazz;
- this.listener = listener;
- }
- /**
- * Like the other, but allows you to specify which {@link Request.Method} you want.
- *
- * @param method
- * @param url
- * @param clazz
- * @param listener
- * @param errorListener
- */
- public MyGsonRequest(int method, String url, Class<T> clazz, Response.Listener<T> listener, Response.ErrorListener errorListener) {
- super(method, url, errorListener);
- this.clazz = clazz;
- this.listener = listener;
- }
- @Override
- protected void deliverResponse(T response) {
- if(listener != null){
- listener.onResponse(response);
- }
- }
- @Override
- protected Response<T> parseNetworkResponse(NetworkResponse response) {
- try {
- if(Looper.myLooper() == Looper.getMainLooper()){
- mLogger.e("数据是 ==>在主线程中解析的~");
- }else{
- mLogger.e("数据不是 ==>在主线程中解析的~");
- }
- String json = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
- JSONObject jsonObject = new JSONObject(json);
- if(null != jsonObject && jsonObject.has("code") && jsonObject.getInt("code") == 200){
- return Response.success(gson.fromJson(json, clazz), HttpHeaderParser.parseCacheHeaders(response));
- }else{
- return Response.error(new ParseError());
- }
- } catch (UnsupportedEncodingException e) {
- return Response.error(new ParseError(e));
- } catch (JsonSyntaxException e) {
- mLogger.e("JsonSyntaxException ==== ");
- return Response.error(new ParseError(e));
- } catch (JSONException e) {
- return Response.error(new ParseError(e));
- }
- }
- @Override
- public void finish(final String tag) {
- super.finish(tag);
- listener = null;
- }
- }</span>
- /**
- * 优化:
- * (1)避免在主线程中解析json数据
- * (2)添加了取消请求方法
- *
- * @param tag
- * @param url
- * @param params
- * @param clazz
- * @param callback
- * @param <T>
- */
- protected <T> void doRequest(String tag, String url, HashMap<String, String> params, final Class<?> clazz, final HttpCallback callback) {
- String requestUrl = urlBuilder(url, params);
- Logger.e("BaseTVApi", requestUrl);
- callback.onLoading();
- Response.Listener<T> successListener = new Response.Listener<T>() {
- @Override
- public void onResponse(T bean) {
- if (bean != null) {
- callback.onResponse(bean);
- Logger.e("BaseTVApi", "request-->onResponse");
- } else {
- callback.onEmptyData();
- Logger.e("BaseTVApi", "request-->onEmptyData");
- }
- }
- };
- Response.ErrorListener errorListener = new Response.ErrorListener() {
- @Override
- public void onErrorResponse(VolleyError error) {
- callback.onError();
- Logger.e("BaseTVApi", "异常信息-->" + error.getMessage());
- }
- };
- GsonRequest requestRequest = new GsonRequest(url, params, clazz, successListener, errorListener);
- requestRequest.setTag(tag);
- LeSportsApplication.getApplication().addHttpRequest(requestRequest);
- }
(二) 使用Volley内存泄露
在用MAT做内存泄露检查的时候,发现由于Volley的回调没有干掉导致的泄露问题,解决方法就是保证在Activity退出时,cancel掉请求,Request方法有一个cancel和finish方法并在这2个方法中把listener置为空。
(1) 在Application中提供一个取消请求的方法,因为一般请求队列是在Application中初始化的。
- /**
- * 网络请求优化,取消请求
- * @param tag
- */
- public void cancelRequest(String tag){
- try {
- mRequestQueue.cancelAll(tag);
- }catch (Exception e){
- Logger.e("LeSportsApplication","tag =="+ tag + "的请求取消失败");
- }
- }
(3) 在GsonRequest中重写finish方法,并在该方法中把listener置为空
- <span style="font-size: 18px;"> @Override
- public void finish(final String tag) {
- super.finish(tag);
- listener = null;
- }</span>
- public void finish(final String tag) {
- if (mRequestQueue != null) {
- mRequestQueue.finish(this);
- }
- if (MarkerLog.ENABLED) {
- final long threadId = Thread.currentThread().getId();
- if (Looper.myLooper() != Looper.getMainLooper()) {
- // If we finish marking off of the main thread, we need to
- // actually do it on the main thread to ensure correct ordering.
- Handler mainThread = new Handler(Looper.getMainLooper());
- mainThread.post(new Runnable() {
- @Override
- public void run() {
- mEventLog.add(tag, threadId);
- mEventLog.finish(this.toString());
- }
- });
- return;
- }
- mEventLog.add(tag, threadId);
- mEventLog.finish(this.toString());
- } else {
- long requestTime = SystemClock.elapsedRealtime() - mRequestBirthTime;
- if (requestTime >= SLOW_REQUEST_THRESHOLD_MS) {
- VolleyLog.d("%d ms: %s", requestTime, this.toString());
- }
- }
- mErrorListener = null;
- }
(三) 在onStop中取消请求
QA妹子给我报了一个奇葩的bug,具体是第一次打开应用的时候APP一直在loading,找了半天发现原来是由于是取消请求不当引起的。
原因是:我第一次进入应用的时候,立马弹出了一个升级对话框,这个时候刚好触发了onStop方法,自然请求就取消掉了,所以就一直在loading呗! 后来我就把该页面的请求放在onDestroy中cancel。
(四) 嵌套请求
一个页面中可能会有多个请求,有的请求要等到其它的请求完后才能进行。就像这样:
- <span style="font-size: 18px;"> private void requestUserSubscribesGame() {
- uid = LoginUtils.getUid();
- if (StringUtils.isStringEmpty(uid)) {
- return;
- }
- UserTVApi.getInstance().getUserSubscribeGameId(TAG, uid, new SimpleCallback<ApiBeans.SubScribeListModel>() {
- @Override
- public void onResponse(ApiBeans.SubScribeListModel bean) {
- SubScribeModel subScribeModel = bean.data;
- if (subScribeModel != null) {
- scribeGameEntrys = subScribeModel.getEntities();
- requestHallData();
- }
- }
- @Override
- public void onError() {
- Logger.e(TAG, "获取订阅信息失败...");
- }
- });
- }</span>
我之前也很喜欢这样写,后来我查看log的时候发现,第一个请求会超时请求,导致会重试一次。我设置的超时时间是2秒,可能是由于第二个请求太慢,导致请求第一次回调的时候太长导致的吧,具体原因还需要去查。
解决方法:有嵌套请求的情况,建议第一个接口请求完后,用handler发送消息,然后第二个开始请求。