Android volley封装实践

在项目中一般使用使用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() {

           @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() {
            @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 extends Request {
    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 getHeaders() throws AuthFailureError {
        Map 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 extends AuthenticatedRequestBase {
    private String TAG = this.getClass().getSimpleName();
    private final static Gson gson = new Gson();
    private final Response.Listener listener;
    private final Class tResponseClazz;
    private TRequest request;
    private static final String PROTOCOL_CHARSET = "utf-8";
    private boolean cacheHit;
    private String mUrl;
    private NetCallback cb;

    /**
     * @param url
     * @param callback
     */
    public CommonRequest(String url, NetCallback callback) {
        this(url, null, null, false, callback);
    }

    /**
     * @param url
     * @param request
     * @param callback
     */
    public CommonRequest(String url, TRequest request, NetCallback callback) {
        this(url, request, null, false, callback);
    }

    /**
     * @param url
     * @param responseClass
     * @param cache
     * @param callback
     */
    public CommonRequest(String url, Class responseClass, boolean cache, NetCallback callback) {
        this(url, null, responseClass, cache, callback);
    }

    /**
     * @param url
     * @param request
     * @param responseClass
     * @param cache
     * @param callback
     */
    public CommonRequest(String url, TRequest request, Class responseClass, boolean cache, NetCallback 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 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内部统一处理:

@UiThread
public abstract class NetCallback {
    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 getSuccessListener() {
        return new Response.Listener() {
            @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 callback) {
        new CommonRequest<>(INetConstant.NEWS, News.class, true, callback);
    }

    public static void postInfo(NetCallback 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配置支持缓存,并设定好缓存时间,否则无法生效。

最后贴一张效果图:
图片发自App

到此结束,后期还会进行优化,代码在[github] (https://github.com/daydaydate/sample)。感谢您的阅读。

你可能感兴趣的:(Android volley封装实践)