继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Android volley封装实践

慕村9548890
关注TA
已关注
手记 1260
粉丝 227
获赞 990

在项目中一般使用使用volley方式如下,用起来给人一种很乱的感觉,于是一种盘它的想法油然而生。

public void get() {
String url = "https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=......";
StringRequest request = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {           @Override

           public void onResponse(String s) {

               Toast.makeText(MainActivity.this,s,Toast.LENGTH_SHORT).show();

           }

       }, new Response.ErrorListener() {           @Override

           public void onErrorResponse(VolleyError volleyError) {

               Toast.makeText(MainActivity.this,volleyError.toString(),Toast.LENGTH_SHORT).show();

           }

       });

       request.setTag("abcGet");

       MyApplication.getHttpQueues().add(request);

   }

首先看一下我封装后的使用例子:

 private void initData() {
        NewsApi.getInfo(new NetCallback<News>() {            @Override
            public void OnSuccess(final News result) {
                mAdapter.setData(result.getResult().getData());
            }            @Override
            public void OnError(RestfulError error) {
            }
        });
    }

有没有看起来很舒服的感觉。好吧,让我开始盘它吧!
1.首先我先去写了一个基类,用来创建一个新的request并把它加入到volley内部封装的请求队列中,代码如下:

public abstract class AuthenticatedRequestBase<T> extends Request<T> {    private final static String TAG = "AuthenticatedRequestBase";    private static final int TIME_OUT = 30000;    private static final int MAX_RETRIES = 1;    private static final float BACKOFF_MULT = 2f;    protected Context mContext;    protected RequestQueue mRequestQueue;    /**
     * 创建新的请求,并把请求加入到请求队列requestQueue中
     *
     * @param method
     * @param url
     * @param cache
     * @param errorListener
     */
    @SuppressLint("LongLogTag")    public AuthenticatedRequestBase(int method, String url, boolean cache, Response.ErrorListener errorListener) {        super(method, url, errorListener);        //this.setShouldCache(cache);
        this.setRetryPolicy(new DefaultRetryPolicy(
                TIME_OUT,
                MAX_RETRIES,
                BACKOFF_MULT));

        mRequestQueue = YZ.getInstance().getRequestQueue();        if (mRequestQueue == null) {            throw new IllegalArgumentException("mRequestQueue can't be null");
        }

        mContext = YZ.getInstance().getContext();        if (mContext == null) {            throw new IllegalArgumentException("mContext can't be null");
        }        //如果重新发出服务器请求的时候,需要清除之前的缓存。
        if (!cache) {
            Cache volleyCache = mRequestQueue.getCache();
            Cache.Entry cacheEntry = volleyCache.get(url);            if (cacheEntry != null) {
                volleyCache.remove(url);
                Log.d(TAG, "remove volley cache:" + url);
            }
        }
        mRequestQueue.add(this);
    }    /**
     * 重写这个方法,可以在http请求头里面加入token,客户端能接受的数据类型
     *
     * @return
     * @throws AuthFailureError
     */
    @CallSuper
    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
        Map<String, String> headers = new HashMap<>();
        String token = "............";        //headers.put("Authorization", "bearer " + token);
        //针对Get方法,申明接受的enum类型
        // headers.put("Accept", "charset=utf-8");
        return headers;
    }    /**
     * 网络错误问题统一处理
     *
     * @param volleyError
     * @return
     */
    @CallSuper
    @Override
    protected VolleyError parseNetworkError(VolleyError volleyError) {        return super.parseNetworkError(volleyError);
    }
}

代码注释比较清晰,就不在赘述。
2.新建一个CommonRequest去继承这个基类,并出解析结果:

/**
 * Created by yz on 2019/2/2.
 */public class CommonRequest<TRequest, TResponse> extends AuthenticatedRequestBase<TResponse> {    private String TAG = this.getClass().getSimpleName();    private final static Gson gson = new Gson();    private final Response.Listener<TResponse> listener;    private final Class<TResponse> tResponseClazz;    private TRequest request;    private static final String PROTOCOL_CHARSET = "utf-8";    private boolean cacheHit;    private String mUrl;    private NetCallback<TResponse> cb;    /**
     * @param url
     * @param callback
     */
    public CommonRequest(String url, NetCallback<TResponse> callback) {        this(url, null, null, false, callback);
    }    /**
     * @param url
     * @param request
     * @param callback
     */
    public CommonRequest(String url, TRequest request, NetCallback<TResponse> callback) {        this(url, request, null, false, callback);
    }    /**
     * @param url
     * @param responseClass
     * @param cache
     * @param callback
     */
    public CommonRequest(String url, Class<TResponse> responseClass, boolean cache, NetCallback<TResponse> callback) {        this(url, null, responseClass, cache, callback);
    }    /**
     * @param url
     * @param request
     * @param responseClass
     * @param cache
     * @param callback
     */
    public CommonRequest(String url, TRequest request, Class<TResponse> responseClass, boolean cache, NetCallback<TResponse> callback) {        super(request == null ? Method.GET : Request.Method.POST, url, cache, callback.getErrorListener());        this.request = request;        this.tResponseClazz = responseClass;        this.mUrl = url;        this.listener = callback.getSuccessListener();        this.cb = callback;
    }    /**
     * get请求返回null,post请求返回byte【】
     *
     * @return
     */
    @Override
    public byte[] getBody() {        if (request == null) return null;

        String mRequestBody = gson.toJson(this.request);        try {            return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET);
        } catch (UnsupportedEncodingException uee) {
            VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s",
                    mRequestBody, PROTOCOL_CHARSET);            return null;
        }
    }    /**
     * 这个是缓存的标记,与本地缓存相关,返回response时才会使用此标志
     *
     * @param tag
     */
    @Override
    public void addMarker(String tag) {        super.addMarker(tag);
        cacheHit = tag.equals("cache-hit");
    }    /**
     * clazz为空时,返回null
     *
     * @param response
     * @return
     */
    @Override
    protected Response<TResponse> parseNetworkResponse(NetworkResponse response) {        //无需返回数据
        if (tResponseClazz == null) {            return Response.success(null, parseCacheHeaders(response));
        }

        Gson gson = new Gson();        //无网络时,使用本地缓存,通过url去匹配缓存,volley sdk是通过url创建不同的文件来实现缓存的
        if (!NetUtils.isConnect(mContext) && mRequestQueue.getCache().get(mUrl) != null) {
            String json = new String(mRequestQueue.getCache().get(mUrl).data);
            Log.d(TAG, "url==" + mUrl + ",json" + json);
            cb.fResponseCacheStatus = ResponseCacheStatus.StaleFromCache;            return Response.success(gson.fromJson(json, tResponseClazz), parseCacheHeaders(response));
        }        //数据是否有更新
        try {            if (response.statusCode == 304) {                //服务端返回缓存数据
                cb.fResponseCacheStatus = ResponseCacheStatus.NotModifiedFromServer;
            } else if (response.statusCode == 200) {                if (cacheHit) {                    //使用本地缓存
                    cb.fResponseCacheStatus = ResponseCacheStatus.FreshFromCache;
                } else {                    //使用服务端更新数据
                    cb.fResponseCacheStatus = ResponseCacheStatus.NewFromServer;
                }
            } else {
                cb.fResponseCacheStatus = ResponseCacheStatus.NewFromServer;
            }

            Log.d(TAG, "fResponseCacheStatus = " + cb.fResponseCacheStatus);
            String json = new String(response.data, parseCharset(response.headers));            return Response.success(gson.fromJson(json, tResponseClazz), parseCacheHeaders(response));
        } catch (UnsupportedEncodingException | JsonSyntaxException e) {            return Response.error(new ParseError(e));
        }
    }    @Override
    protected void deliverResponse(TResponse response) {
        listener.onResponse(response);
    }

}

3.上面只做了返回成功的处理方式,返回失败时由NetCallback内部统一处理:

@UiThreadpublic abstract class NetCallback<TResponse> {    public ResponseCacheStatus fResponseCacheStatus = ResponseCacheStatus.NewFromServer;    private String TAG = this.getClass().getSimpleName();    public boolean enableAutomaticToastOnError = true;    public NetCallback() {
    }    public NetCallback(boolean enableAutomaticToastOnError) {        this.enableAutomaticToastOnError = enableAutomaticToastOnError;
    }    public abstract void OnSuccess(TResponse result);    public abstract void OnError(RestfulError error);    public void OnNetworkOff() {        //do nothing ,use it according to requirement
    }    public Response.Listener<TResponse> getSuccessListener() {        return new Response.Listener<TResponse>() {            @Override
            public void onResponse(TResponse result) {
                OnSuccess(result);
            }
        };
    }    public Response.ErrorListener getErrorListener() {        return new Response.ErrorListener() {            @Override
            public void onErrorResponse(VolleyError volleyError) {                if (volleyError instanceof TimeoutError) {
                    Log.e(TAG, "networkResponse == null");                    //volley TimeoutError
                    OnError(new RestfulError());
                }                if (volleyError.networkResponse != null) {                    int statusCode = volleyError.networkResponse.statusCode;
                    String errorMessage = new String(volleyError.networkResponse.data);                    switch (statusCode) {                        case 401:                            //post a Permission authentication failed event
                            break;                        default:
                            Log.d(TAG, "errorMessage =" + errorMessage);                            try {
                                RestfulError error = new Gson().fromJson(errorMessage, RestfulError.class);                                if (enableAutomaticToastOnError && error.getCode() != null) {                                    //toast(error.ExceptionMessage); //toast it in main thread
                                }
                                OnError(error);
                            } catch (Exception e) {
                                OnError(new RestfulError());
                                Log.d(TAG, "e =" + e.toString());
                            }                            break;
                    }
                }
            }
        };
    }
}

4.注意到没有,在AuthenticatedRequestBase内部有一个环境类YZ,主要负责获取项目主程序中的context和请求队列:

public class YZ implements AppRequestQueue {    private static final int DEFAULT_VOLLEY_CACHE_SIZE = 100 * 1024 * 1024;    private Context context;    private int cacheSize;    private YZ() {
    }    @Override
    public RequestQueue getRequestQueue() {        return Volley.newRequestQueue(context, cacheSize);
    }    public Context getContext() {        return context;
    }    private static class SingletonHolder {        private static YZ instance = new YZ();
    }    public static YZ getInstance() {        return SingletonHolder.instance;
    }    /**
     * need a app context
     *
     * @param appContext
     */
    public void init(final Context appContext) {
        init(appContext, DEFAULT_VOLLEY_CACHE_SIZE);
    }    /**
     * @param appContext
     * @param cacheSize
     */
    public void init(final Context appContext, final int cacheSize) {        this.context = appContext;        this.cacheSize = cacheSize;
    }
}

这个类需要在app的application中初始化:

public class BaseApp extends Application {    public String TAG = this.getClass().getSimpleName();    public static Context applicationContext;    public static Executor threadPool;    public static final int THREAD_POOL_SIZE = 3;    public static final boolean isDebug = BuildConfig.BUILD_TYPE.equals("debug");    @Override
    public void onCreate() {        super.onCreate();
        applicationContext = getApplicationContext();
        threadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);

        initNet();
    }    private void initNet() {
        YZ.getInstance().init(this);
    }    public Context getInstance() {        return applicationContext;
    }

}

4.现在可以开始外部封装啦,测试了一下,get和post请求都可以成功。

public class NewsApi {

    public static void getInfo(NetCallback<News> callback) {        new CommonRequest<>(INetConstant.NEWS, News.class, true, callback);
    }    public static void postInfo(NetCallback<News> callback) {
        RequestBody body = new RequestBody("top", "b2f8e4aeacfa310cabfadd5189bbe4d5");        new CommonRequest<>(INetConstant.NEWS, body, News.class, true, callback);
    }    public static void cancelGetInfo() {        //how to do
    }
}

还有一点,volley的缓存实现需要服务端配合在http请求的Cache-control: max-age配置支持缓存,并设定好缓存时间,否则无法生效。



作者:一朵喇叭花呜拉呜拉
链接:https://www.jianshu.com/p/1f09e91bee66


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP