Android volley封装实践

938 阅读4分钟
原文链接: www.jianshu.com

在项目中一般使用使用volley方式如下,用起来给人一种很乱的感觉,于是一种盘它的想法油然而生。
'''
private void volley_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.以get方法为例,新建一个GetRequest去继承这个基类,并出解析结果:
'''

public class GetRequest<TResponse> extends AuthenticatedRequestBase<TResponse> {

private final Response.Listener<TResponse> listener;
private final Class<TResponse> clazz;
private final static String TAG = "GetRequest";
private String mUrl;
private NetCallback<TResponse> cb;
private boolean cacheHit;


public GetRequest(String url, Class<TResponse> clazz, boolean cache, NetCallback<TResponse> callback) {
    super(Request.Method.GET, url, cache, callback.getErrorListener());
    this.listener = callback.getSuccessListener();
    this.clazz = clazz;
    this.mUrl = url;
    this.cb = callback;

    //无网络时300ms后返回callback
    if (!NetUtils.isConnect(mContext) && mRequestQueue.getCache().get(url) == null) {
        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                cb.OnNetworkOff();
            }
        }, 300);
    }
}

/**
 * 这个是缓存的标记,与本地缓存相关
 * @param tag
 */
@Override
public void addMarker(String tag) {
    super.addMarker(tag);
    cacheHit = tag.equals("cache-hit");
}

@Override
protected Response<TResponse> parseNetworkResponse(NetworkResponse 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, clazz), 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, clazz), parseCacheHeaders(response));
    } catch (UnsupportedEncodingException | JsonSyntaxException e) {
        return Response.error(new ParseError(e));
    }
}

@Override
protected void deliverResponse(TResponse response) {
    listener.onResponse(response);
}

@Override
protected VolleyError parseNetworkError(VolleyError volleyError) {
    return super.parseNetworkError(volleyError);
}

}
'''
3.上面只做了返回成功的处理方式,返回失败时由NetCallback内部统一处理:
'''
@UiThread
public 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.现在可以开始外部封装啦。
'''
public class NewsApi {

public static void getInfo(NetCallback<News> callback) {
    new GetRequest<>(INetConstant.NEWS, News.class, true, callback);
}

}
'''
还有一点,volley的缓存实现需要服务端配合在http请求的Cache-control: max-age配置支持缓存,并设定好缓存时间,否则无法生效。
到此结束,后期还会进行优化,代码在 github.com/daydaydate/…。感谢阅读,觉得不错的话点赞哦!