`
砺雪凝霜
  • 浏览: 151713 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

那些年我使用Volley遇到的坑

阅读更多

                               那些年我使用Volley遇到的坑

  使用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方法,这个方法其实是在NetworkDispatcher这个子线程中执行的
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;
    }
}
 (3) 然后进行二次封装
/**
     * 优化:
     * (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);
    }
  打印log你会发现,你会发现数据解析是在子线程中执行的。

(二) 使用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 + "的请求取消失败");
        }
    }
(2) 在onStop中调用cancelRequest方法,最终会调用request的finish方法
(3) 在GsonRequest中重写finish方法,并在该方法中把listener置为空
  @Override
    public void finish(final String tag) {
        super.finish(tag);
        listener = null;
    }
  (4) 在再看看GsonRequest 的父类Request的finish方法,我已经把mErrorListener干掉了。
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;
    }
     总结:请求取消最终都会调用Request的finish方法,我们只要在这个方法中,把2个回调给干掉,问题就解决了。

(三) 在onStop中取消请求

    QA妹子给我报了一个奇葩的bug,具体是第一次打开应用的时候APP一直在loading,找了半天发现原来是由于是取消请求不当引起的。
   原因是:我第一次进入应用的时候,立马弹出了一个升级对话框,这个时候刚好触发了onStop方法,自然请求就取消掉了,所以就一直在loading呗! 后来我就把该页面的请求放在onDestroy中cancel。

(四) 嵌套请求

  一个页面中可能会有多个请求,有的请求要等到其它的请求完后才能进行。就像这样:
 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, "获取订阅信息失败...");
            }
        });
    }
 
      我之前也很喜欢这样写,后来我查看log的时候发现,第一个请求会超时请求,导致会重试一次。我设置的超时时间是2秒,可能是由于第二个请求太慢,导致请求第一次回调的时候太长导致的吧,具体原因还需要去查。
    解决方法:有嵌套请求的情况,建议第一个接口请求完后,用handler发送消息,然后第二个开始请求。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
0
分享到:
评论
1 楼 zhu_jinlong 2016-01-31  
Volley持有context引用导致的memory leak实际上是很严重的,很多Volley的使用者并没有关注到这个问题,我是采用EventBus把结果Post回Activity/Fragment来解决的,并在OnDestory()里UnRegister EventBus.

相关推荐

Global site tag (gtag.js) - Google Analytics