前言:
近期,接手一个广告项目,该项目依赖一个CommmonLibrary,该库中选用Volley库和Gson库实现图片和网络通讯。项目又需要下载文件和上传文件的需求。要么考虑手写文件操作库,实现下载和上传。要么考虑对Volley库进行重构改造。
众所周知,Android Volley库不适合上传文件和下载文件,因Request会走内存流,对文件操作,会导致巨大的内存占用。
因此,想要让Volley支持文件操作,就得走磁盘IO流。具备以下几种实现方案:
- 第一种方式:考虑修改Volley源码,但这种方案存在缺点,Volley会有不同的版本。
- 第二种方式:无入侵代码方式,采用Hook方式,反射动态代理Volley的IO操作。
这里,考虑第二种方案,开发一个VolleyHelper库。
分析Volley的Request的执行内存流
Request的Body的内存IO:
先找到com.android.toolbox
包下的HurlStack类,找到addBodyIfExists()
:
private static void addBodyIfExists(HttpURLConnection connection, Request<?> request)
throws IOException, AuthFailureError {
byte[] body = request.getBody();
if (body != null) {
addBody(connection, request, body);
}
}
private static void addBody(HttpURLConnection connection, Request<?> request, byte[] body)
throws IOException {
connection.setDoOutput(true);
if (!connection.getRequestProperties().containsKey(HttpHeaderParser.HEADER_CONTENT_TYPE)) {
connection.setRequestProperty(
HttpHeaderParser.HEADER_CONTENT_TYPE, request.getBodyContentType());
}
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.write(body);
out.close();
}
从上面源码可知,Request的Body都转成byte[]。上传一个文件,转换成byte[]会占用巨大内存,若是该文件大小上百M,应用程序会立马奔溃,抛出内存溢出。
Response的body的内存IO:
先找到com.android.toolbox
包下的BasicNetwork
类中的inputStreamToBytes()
:
/** Reads the contents of an InputStream into a byte[]. */
private byte[] inputStreamToBytes(InputStream in, int contentLength)
throws IOException, ServerError {
PoolingByteArrayOutputStream bytes = new PoolingByteArrayOutputStream(mPool, contentLength);
byte[] buffer = null;
try {
if (in == null) {
throw new ServerError();
}
buffer = mPool.getBuf(1024);
int count;
while ((count = in.read(buffer)) != -1) {
bytes.write(buffer, 0, count);
}
return bytes.toByteArray();
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
VolleyLog.v("Error occurred when closing InputStream");
}
mPool.returnBuf(buffer);
bytes.close();
}
}
从以上代码可知,Response中body流会转成一个Byte[]。下载一个文件,直接转成byte[],若是文件巨大,程序会内存溢出而奔溃。
分析如下:
根据上面的情况,若是想支持文件上传或者文件下载,而又不导致内存溢出,必须采用Hook方式,反射动态代理掉HurlStack对象、BaseNetWork对象。
接下来开始编写VolleyHelper库,定制化各种Request。
编写VolleyHelper库
1. VolleyHelper库之Hook模块
先找到Hook点
Volley库中RequestQueue是一个单例对象,很好切入的Hook点。还有一个切入点是BaseNetwork对象。
实现思路:
- 反射动态代理掉RequestQueue中的NetWork对象和四个线程中的NetWork对象。
- 接下来,替换掉BasicNetWork中传输层对象,在Volley1.1版本中是BaseHttpStack对象,在Volley1.0版本是HttpStack对象。
先创建一个VolleyHookManager:
public class HookVolleyManager {
private final String TAG=HookVolleyManager.class.getSimpleName();
public void init(Object requestQueue) {
if (requestQueue == null) {
return;
}
try {
Log.i(TAG, " HookVolleyManager init() ");
Class<?> requestQueueClass = requestQueue.getClass();
Field networkField = requestQueueClass.getDeclaredField("mNetwork");
networkField.setAccessible(true);
//获取到BasicNetwork对象
Object network = networkField.get(requestQueue);
//接下来,设置动态代理
Object networkProxy = Proxy.newProxyInstance(requestQueue.getClass().getClassLoader(), network.getClass().getInterfaces(), new NetWorkHandler(network));
networkField.set(requestQueue, networkProxy);
Field mDispatchersField = requestQueueClass.getDeclaredField("mDispatchers");
mDispatchersField.setAccessible(true);
//获取到NetworkDispatcher线程组
Object[] networkDispatchers = (Object[]) mDispatchersField.get(requestQueue);
//代理掉网络线程中的network
for (Object object : networkDispatchers) {
Class<?> mClass = object.getClass();
Field networkFields = mClass.getDeclaredField("mNetwork");
networkFields.setAccessible(true);
networkFields.set(object, networkProxy);
}
//替代传输层
Class<?> baseNetworkClass = network.getClass();
Field mBaseHttpStackField = null;
try { //适配volley 1.1
mBaseHttpStackField = baseNetworkClass.getDeclaredField("mBaseHttpStack");
mBaseHttpStackField.setAccessible(true);
mBaseHttpStackField.set(network, new MyHttpStack());
} catch (Exception e) {
e.printStackTrace();
}
if (mBaseHttpStackField == null) { //适配volley 1.0
Field httpStackField = baseNetworkClass.getDeclaredField("mHttpStack");
httpStackField.setAccessible(true);
httpStackField.set(network, new MyHttpStack());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
接下来,创建一个BasicNetwork的代理处理类NetWorkHandler,用来动态代理,处理文件下载的请求。
public class NetWorkHandler implements InvocationHandler {
private final String TAG = NetWorkHandler.class.getSimpleName();
private Object network;
public NetWorkHandler(Object network) {
this.network = network;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("performRequest")) {
Object request = args[0];
// Log.i(TAG, " NetWorkHandler 代理 " + method.getName());
if (request instanceof DownloadRequest) {//当执行下载任务,就直接IO写入磁盘,不走内存流
Log.i(TAG, " NetWorkHandler 处理 DownloadRequest");
try {
String url = ((DownloadRequest) request).getUrl();
HttpURLConnection httpURLConnection = (HttpURLConnection) (new URL(url)).openConnection();
httpURLConnection.connect();
if (httpURLConnection.getResponseCode() == 200) {
NetworkResponse networkResponse = writeFileStreamIfExist((DownloadRequest) request, httpURLConnection.getResponseCode(), httpURLConnection.getContentLength(), httpURLConnection.getInputStream());
return networkResponse;
} else {
String error = streamToString(httpURLConnection.getErrorStream());
throw new VolleyError(error);
}
} catch (Exception e) {
Log.i(TAG, " NetWorkHandler 发生异常 " + e.getMessage());
throw new VolleyError(e);
}
} else {//当其他的Request,正常走法。
try {
Object result = method.invoke(network, args);
return result;
} catch (Exception error) {
if (error instanceof InvocationTargetException) {
throw ((InvocationTargetException) error).getTargetException();
}
throw error;
}
}
} else {
try {
Object result = method.invoke(network, args);
return result;
} catch (Exception error) {
if (error instanceof InvocationTargetException) {
throw ((InvocationTargetException) error).getTargetException();
}
throw error;
}
}
}
private static NetworkResponse writeFileStreamIfExist(DownloadRequest request, int status, long contentLength, InputStream inputStream) throws IOException {
Log.i("DownloadRequest", "下载");
File file = new File(request.getFilePath());
if (file != null && file.exists()) {
file.delete();
}
FileOutputStream outputStream = new FileOutputStream(file);
long fileLength = contentLength;
byte[] buffer = new byte[4096];
int count;
long total = 0;
while ((count = inputStream.read(buffer)) != -1) {
if (request.isCanceled()) {
outputStream.close();
inputStream.close();
file.deleteOnExit();
throw new IOException("DownloadRequest cancel 被取消");
}
outputStream.write(buffer, 0, count);
total += count;
if (fileLength > 0) {
int progress = (int) ((float) total * 100 / fileLength);
request.deliverProgress(progress);
}
}
outputStream.flush();
inputStream.close();
outputStream.close();
return new NetworkResponse(status, new byte[0], null, false);
}
private static String streamToString(InputStream inputStream) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = null;
BufferedInputStream bufferedInputStream = null;
String result = null;
try {
bufferedInputStream = new BufferedInputStream(inputStream);
byteArrayOutputStream = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int length;
while ((length = bufferedInputStream.read(b)) > 0) {
byteArrayOutputStream.write(b, 0, length);
}
result = byteArrayOutputStream.toString("utf-8");
return result;
} finally {
try {
if (byteArrayOutputStream != null) {
byteArrayOutputStream.close();
}
if (bufferedInputStream != null) {
bufferedInputStream.close();
}
} catch (Exception var11) {
var11.printStackTrace();
}
}
}
}
最后,创建一个HurlStack的子类,替换掉原本的传输层,用来处理文件上传的请求。
public class MyHttpStack extends HurlStack {
public MyHttpStack() {
}
@Override
public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError {
// Log.i(TAG, " MyHttpStack executeRequest " + request.getUrl());
if (request instanceof SingleFileRequest) {//当请求是文件上传的请求,直接从磁盘文件中读取IO,避免转成一个超大的byte[] ,导致内存溢出
HttpURLConnection connection = this.createConnection(new URL(request.getUrl()));
int timeoutMs = request.getTimeoutMs();
connection.setConnectTimeout(timeoutMs);
connection.setReadTimeout(timeoutMs);
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setRequestMethod("POST");
addFileBody(connection, (SingleFileRequest) request);
connection.connect();
int responseCode = connection.getResponseCode();
if (responseCode == -1) {
throw new IOException("Could not retrieve response code from HttpUrlConnection.");
} else {
return !hasResponseBody(request.getMethod(), responseCode) ? new HttpResponse(responseCode, convertHeaders(connection.getHeaderFields())) : new HttpResponse(responseCode, convertHeaders(connection.getHeaderFields()), connection.getContentLength(), connection.getResponseCode()==200?connection.getInputStream():connection.getErrorStream());
}
} else {//非文件上传的请求,正常走法
return super.executeRequest(request, additionalHeaders);
}
}
private static boolean hasResponseBody(int requestMethod, int responseCode) {
return requestMethod != 4 && (100 > responseCode || responseCode >= 200) && responseCode != 204 && responseCode != 304;
}
static List<Header> convertHeaders(Map<String, List<String>> responseHeaders) {
List<Header> headerList = new ArrayList(responseHeaders.size());
Iterator var2 = responseHeaders.entrySet().iterator();
while(true) {
Map.Entry entry;
do {
if (!var2.hasNext()) {
return headerList;
}
entry = (Map.Entry)var2.next();
} while(entry.getKey() == null);
Iterator var4 = ((List)entry.getValue()).iterator();
while(var4.hasNext()) {
String value = (String)var4.next();
headerList.add(new Header((String)entry.getKey(), value));
}
}
}
/**
* 添加文件,避免内存巨大
*
* @param connection
* @param request
* @throws IOException
* @throws AuthFailureError
*/
private static void addFileBody(HttpURLConnection connection, SingleFileRequest request) throws IOException, AuthFailureError {
final String HEADER_CONTENT_TYPE = "Content-Type";
String filePath = request.getFilePath();
if (filePath == null) {
throw new IOException("File文件路径为空");
}
File file = new File(filePath);
if (file == null || !file.exists()) {
throw new IOException("File 文件不存在");
}
//设置post请求方法,允许写入客户端传递的参数
connection.setDoOutput(true);
//设置标头的Content-Type属性
connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType());
DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
FileInputStream fileInputStream = new FileInputStream(file);
long total = 0;
int read;
outputStream.writeUTF(request.getContentHeader());
byte[] buffer = new byte[SingleFileRequest.READ_SIZE];
while ((read = fileInputStream.read(buffer)) != -1) {
if (request.isCanceled()) {
fileInputStream.close();
outputStream.close();
throw new IOException("SingleRequest 被取消");
}
outputStream.write(buffer, 0, read);
outputStream.flush();
total += read;
int progress = (int) (total * 100 / file.length());
request.deliverProgress(progress);
}
outputStream.writeUTF(request.getContentFoot());
outputStream.flush();
fileInputStream.close();
outputStream.close();
}
}
2. 定制化各种Request
一个项目中常见的网络需求,包含Form表单,Json的文本,文件下载或者文件上传。根据这些需求,自定义对应的Request。
2.1 自定义FormRequest
Form表单的内容格式application/x-www-form-urlencoded
,Volley默认的Request超类也是这个格式。因此,只需要继承Request,重写getParams()
,将body按格式写入。
public class FormRequest<T> extends Request<T> {
private final GsonResultListener<T> resultListener;
private Map<String, String> body;
private Map<String, String> headers;
public FormRequest(String url, GsonResultListener<T> resultListener) {
this(Method.GET, url, null, resultListener);
}
public FormRequest(String url, Map<String, String> body, GsonResultListener<T> resultListener) {
this(Method.POST, url, body, resultListener);
}
public FormRequest(int method, String url, Map<String, String> body, GsonResultListener<T> resultListener) {
super(method, url, resultListener);
this.headers = new HashMap<>();
this.body = body;
this.resultListener = resultListener;
}
@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
return this.resultListener.parseResponse(response);
}
@Override
protected void deliverResponse(T response) {
this.resultListener.onResponse(response);
}
@Override
public Map<String, String> getHeaders() {
return headers;
}
public Map<String, String> setHeader(String key, String content) {
if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(content)) {
headers.put(key, content);
}
return headers;
}
@Override
protected Map<String, String> getParams() throws AuthFailureError {
return this.body;
}
}
现今,后台服务器通常返回的Response是Json格式,因此用Gson解析Json生成对应的Bean实体。
这里值得一提的是,Gson解析,泛型实体类的类型。度娘上的gson教程大同小异,解析的实体类型简单,也没有通用解析泛型实体类,换句话说,比较少提到Gson如何一次性解析泛型实体类。
大体思路:先通过反射获取到泛型实体类的type,再通过Gson解析json和type,才能生成对应的复杂实体类。
定义一个返回解析后实体类的监听器:
public abstract class GsonResultListener<T> implements Response.Listener<T>, Response.ErrorListener {
private Type type;
private Gson gson;
public GsonResultListener() {
this.gson = new Gson();
this.type = getSuperclassTypeParameter(this.getClass());
}
public Response<T> parseResponse(NetworkResponse response) {
try {
String json = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
T t = gson.fromJson(json, type);
return Response.success(t, HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JsonSyntaxException e) {
return Response.error(new ParseError(e));
}
}
@Override
public void onResponse(T response) {
this.success(response);
}
@Override
public void onErrorResponse(VolleyError error) {
this.error(error);
}
public static Type getSuperclassTypeParameter(Class<?> subclass) {
//得到带有泛型的类
Type superclass = subclass.getGenericSuperclass();
if (superclass instanceof Class) {
throw new RuntimeException("Missing type parameter.");
}
//取出当前类的泛型
ParameterizedType parameter = (ParameterizedType) superclass;
return $Gson$Types.canonicalize(parameter.getActualTypeArguments()[0]);
}
/**
* 解析结果
*
* @param t
*/
public abstract void success(T t);
/**
* 异常结果
*
* @param volleyError
*/
public abstract void error(VolleyError volleyError);
}
2.2 自定义GsonRequest
现今,通常的网络文本传输格式是json,除之外,还有xml,form表单,String等等。
自定义一个Request子类,重写getBodyContentType()
方法,返回application/json
数据格式,重写getBody()
方法,将body写入。
public class GsonRequest<T> extends Request<T> {
private static final String PROTOCOL_CHARSET = "utf-8";
private static final String PROTOCOL_CONTENT_TYPE = String.format("application/json; charset=%s", PROTOCOL_CHARSET);
private Map<String, String> headers;
private final GsonResultListener<T> resultListener;
private final String body;
public GsonRequest(String url, GsonResultListener<T> resultListener) {
this(Method.GET, url, (String) null, resultListener);
}
public GsonRequest(String url, Object body, GsonResultListener<T> resultListener) {
this(Method.POST, url, GsonUtils.toJson(body), resultListener);
}
public GsonRequest(String url, JSONObject body, GsonResultListener<T> resultListener) {
this(Method.POST, url, body.toString(), resultListener);
}
public GsonRequest(int method, String url, String body, GsonResultListener<T> resultListener) {
super(method, url, resultListener);
this.headers = new HashMap<>();
this.body = body;
this.resultListener = resultListener;
}
@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
return this.resultListener.parseResponse(response);
}
@Override
protected void deliverResponse(T response) {
this.resultListener.onResponse(response);
}
public Map<String, String> setHeader(String key, String content) {
if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(content)) {
headers.put(key, content);
}
return headers;
}
@Override
public Map<String, String> getHeaders() {
return headers;
}
/**
* 重写Content-type格式
*
* @return
*/
@Override
public String getBodyContentType() {
return PROTOCOL_CONTENT_TYPE;
}
@Override
public byte[] getBody() throws AuthFailureError {
byte[] bytes = null;
if (body != null) {
try {
bytes = body.getBytes(PROTOCOL_CHARSET);
} catch (Exception e) {
e.printStackTrace();
}
}
return bytes;
}
}
2.3 自定义SingleFileRequest
文件上传的格式是multipart/form-data
,且需要指定name 和fileName给后台服务器。
自定义Request,重写getBodyContentType()
,如何从磁盘文件写入传输层的代码,请查看前面的MyHttpStack代理类。
public class SingleFileRequest<T> extends Request<T> {
/**
* 默认的名字
*/
public static final String DEFAULT_NAME = "media";
/**
* 字符编码格式
*/
private static final String PROTOCOL_CHARSET = "utf-8";
private static final String BOUNDARY = "----------" + System.currentTimeMillis();
/**
* Content type for request.
*/
private static final String PROTOCOL_CONTENT_TYPE = "multipart/form-data; boundary=" + BOUNDARY;
/**
* 主线程的Handler
*/
private static final Handler mainHandler = new Handler(Looper.getMainLooper());
/**
* 结果监听器
*/
private final GsonResultListener<T> resultListener;
/**
* 进度监听器
*/
private final FileProgressListener progressListener;
//每次读取的长度
public static final int READ_SIZE = 4096;
private final String name;
/**
* 文件进度
*/
private final String filePath;
/**
* Header表头
*/
private Map<String, String> headers;
public SingleFileRequest(String url, File file, FileProgressListener progressListener, GsonResultListener<T> resultListener) {
this(url, DEFAULT_NAME, file, progressListener, resultListener);
}
public SingleFileRequest(String url, String name, File file, FileProgressListener progressListener, GsonResultListener<T> resultListener) {
this(url, name, file.getAbsolutePath(), progressListener, resultListener);
}
public SingleFileRequest(String url, String filePath, FileProgressListener progressListener, GsonResultListener<T> resultListener) {
this(url, DEFAULT_NAME, filePath, progressListener, resultListener);
}
public SingleFileRequest(String url, String name, String filePath, FileProgressListener progressListener, GsonResultListener<T> resultListener) {
super(Method.POST, url, resultListener);
this.headers = new HashMap<>();
this.filePath = filePath;
this.name = name;
this.resultListener = resultListener;
this.progressListener = progressListener;
this.setShouldCache(false);
}
@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
return this.resultListener.parseResponse(response);
}
@Override
protected void deliverResponse(T response) {
this.resultListener.onResponse(response);
}
public Map<String, String> setHeader(String key, String content) {
if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(content)) {
headers.put(key, content);
}
return headers;
}
@Override
public Map<String, String> getHeaders() {
return headers;
}
@Override
public String getBodyContentType() {
return PROTOCOL_CONTENT_TYPE;
}
/**
* 回调传递进度
*
* @param progress
*/
public void deliverProgress(final int progress) {
if (isCanceled()) {
return;
}
mainHandler.post(new Runnable() {
@Override
public void run() {
if (progressListener != null) {
progressListener.progress(progress);
}
}
});
}
/**
* 获取文件名
*
* @return
*/
public String getFileName() {
return filePath != null ? filePath.substring(filePath.lastIndexOf("/") + 1, filePath.length()) : null;
}
/**
* 文件内容的头部
*/
public String getContentHeader() {
try {
StringBuffer buffer = new StringBuffer();
buffer.append("--");
buffer.append(BOUNDARY);
buffer.append("\r\n");
buffer.append("Content-Disposition: form-data;name=\"");
buffer.append(name);
buffer.append("\";filename=\"");
buffer.append(getFileName());
buffer.append("\"\r\n");
buffer.append("Content-Type:application/octet-stream\r\n\r\n");
String s = buffer.toString();
return s;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 文件内容的尾部
*/
public String getContentFoot() {
try {
StringBuffer buffer = new StringBuffer();
buffer.append("\r\n--");
buffer.append(BOUNDARY);
buffer.append("--\r\n");
String s = buffer.toString();
return s;
} catch (Exception e) {
return null;
}
}
public String getFilePath() {
return filePath;
}
}
上传文件,需要监听进度,需要结果状态监听。
定义一个通用的进度监听器,用于文件下载的请求和文件上传的请求。
public interface FileProgressListener {
/**
* 进度百分比
*
* @param progress
*/
void progress(int progress);
}
2.4 自定义DownloadRequest
自定义Request,定义调用进度监听器,结果监听器的方法,用于回调通知。如何将Response写入磁盘文件的操作,请查看前面的NetWorkHandler类。
public class DownloadRequest extends Request<Object> {
/**
* 主线程的Handler
*/
private static final Handler mainHandler = new Handler(Looper.getMainLooper());
private final DownloadListener downloadListener;
private final FileProgressListener progressListener;
private final String downloadUrl, filePath;
public DownloadRequest(String url, String filePath, FileProgressListener progressListener, DownloadListener downloadListener) {
super(Method.GET, url, downloadListener);
this.downloadUrl = url;
this.filePath = filePath;
this.downloadListener = downloadListener;
this.progressListener = progressListener;
this.setShouldCache(false);
}
@Override
protected Response<Object> parseNetworkResponse(NetworkResponse response) {
return Response.success(null, null);
}
@Override
protected void deliverResponse(Object response) {
if (downloadListener != null) {
downloadListener.downloadFinish(this.downloadUrl, this.filePath);
}
}
@Override
public void deliverError(VolleyError error) {
if (downloadListener != null) {
downloadListener.downloadError(this.downloadUrl, error);
}
}
/**
* 回调传递进度
*
* @param progress
*/
public void deliverProgress(final int progress) {
if (isCanceled()) {
return;
}
mainHandler.post(new Runnable() {
@Override
public void run() {
if (progressListener != null) {
progressListener.progress(progress);
}
}
});
}
public String getFilePath() {
return filePath;
}
}
需要定义一个文件下载的状态监听器,用于获取到下载的url和文件地址filePath或者error.
public abstract class DownloadListener implements Response.ErrorListener {
/**
* 下载完成的方法
*
* @param downloadUrl
* @param filePath
*/
public abstract void downloadFinish(String downloadUrl, String filePath);
/**
* 下载失败的方法
*
* @param downloadUrl
* @param volleyError
*/
public abstract void downloadError(String downloadUrl, VolleyError volleyError);
@Override
public void onErrorResponse(VolleyError error) {
}
}
3.创建一个Client端类,对外提供API
public class VolleyHelper {
private RequestQueue requestQueue;
private static VolleyHelper instance;
private HookVolleyManager hookVolleyManager;
static {
instance = new VolleyHelper();
}
private VolleyHelper() {
hookVolleyManager = new HookVolleyManager();
}
public static VolleyHelper getInstance() {
return instance;
}
//初始化操作,hook Volley
public void init(RequestQueue requestQueue) {
this.requestQueue = requestQueue;
this.hookVolleyManager.init(requestQueue);
}
public <T> void sendGsonRequest(String url, JSONObject body, GsonResultListener<T> gsonResultListener) {
sendGsonRequest(new GsonRequest(url, body, gsonResultListener));
}
public <T> void sendFormRequest(String url, GsonResultListener<T> gsonResultListener) {
sendFormRequest(new FormRequest(url, gsonResultListener));
}
public void sendGsonRequest(GsonRequest gsonRequest) {
sendRequest(gsonRequest);
}
public void sendFormRequest(FormRequest formRequest) {
sendRequest(formRequest);
}
public Request sendRequest(Request request) {
if (requestQueue == null) {
return request;
}
this.requestQueue.add(request);
return request ;
}
public void cancelRequest(String tag) {
if (requestQueue == null) {
return;
}
this.requestQueue.cancelAll(tag);
}
}
VolleyHelper使用案例
初始化操作 : 传入指定的ReuqestQueue,进行hook volley。
public class BaseApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
RequestQueue requestQueue = Volley.newRequestQueue(this);
VolleyHelper.getInstance().init(requestQueue);
}
}
使用GsonRequest的案例,搜索张艺谋的电影列表,代码如下:
private void json() {
String url = "https://api.douban.com/v2/movie/search";
JSONObject jsonObject = null;
try {
jsonObject = new JSONObject();
jsonObject.put("q", "张艺谋");
} catch (Exception e) {
e.printStackTrace();
}
VolleyHelper.getInstance().sendGsonRequest(url, jsonObject, new GsonResultListener<MovieList<Movie>>() {
@Override
public void success(MovieList<Movie> movieMovieList) {
Log.i("JsonRequest", "响应结果 " + movieMovieList.toString() + " " + movieMovieList.getSubjects().get(0).getTitle());
}
@Override
public void error(VolleyError volleyError) {
Log.i("JsonRequest", "异常结果" + volleyError.toString());
}
});
}
测试结果:
com.xingen.myapplication I/JsonRequest: 响应结果 {"subjects":[{"id":"20271803","title":"张艺谋:中国故事","year":"1993"},{"id":"1292365","title":"活着","year":"1994"},{"id":"6982558","title":"长城","year":"2016"},{"id":"4864908","title":"影","year":"2018"},{"id":"3649049","title":"金陵十三钗","year":"2011"},{"id":"1306123","title":"英雄","year":"2002"},{"id":"1296436","title":"有话好好说","year":"1997"},{"id":"21352814","title":"归来","year":"2014"},{"id":"1293323","title":"大红灯笼高高挂","year":"1991"},{"id":"1499008","title":"满城尽带黄金甲","year":"2006"},{"id":"1306505","title":"红高粱","year":"1988"},{"id":"1308722","title":"十面埋伏","year":"2004"},{"id":"4151110","title":"山楂树之恋","year":"2010"},{"id":"1299365","title":"菊豆","year":"1990"},{"id":"2181930","title":"大宅门","year":"2001"},{"id":"1294007","title":"我的父亲母亲","year":"1999"},{"id":"1300082","title":"古今大战秦俑情","year":"1989"},{"id":"1300108","title":"秋菊打官司","year":"1992"},{"id":"1294963","title":"一个都不能少","year":"1999"},{"id":"2027945","title":"每个人都有他自己的电影","year":"2007"}]} 张艺谋:中国故事
使用DownloadRequest的案例,下载一个文件到sdcard中,代码如下:
private void download() {
String url2 = "http://yun.aiwan.hk/1441972507.apk";
String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + File.separator + "BaiHeWang.apk";
VolleyHelper.getInstance().sendRequest(new DownloadRequest(url2, filePath, new FileProgressListener() {
@Override
public void progress(int progress) {
Log.i("DownloadRequest", " 下载进度 " + progress);
}
}, new DownloadListener() {
@Override
public void downloadFinish(String downloadUrl, String filePath) {
Log.i("DownloadRequest", " 文件下载完成,路径是 " + filePath);
}
@Override
public void downloadError(String downloadUrl, VolleyError volleyError) {
Log.i("DownloadRequest", " 文件下载异常 " + downloadUrl + " 异常是 " + volleyError.toString());
}
}));
}
测试结果:
com.xingen.myapplication I/NetWorkHandler: NetWorkHandler 处理 DownloadRequest
com.xingen.myapplication I/DownloadRequest: 下载
com.xingen.myapplication I/DownloadRequest: 下载进度 0
com.xingen.myapplication I/DownloadRequest: 下载进度 0
//.........省略部分log
com.xingen.myapplication I/DownloadRequest: 下载进度 99
下载进度 99
com.xingen.myapplication I/DownloadRequest: 下载进度 99
com.xingen.myapplication I/DownloadRequest: 下载进度 100
com.xingen.myapplication I/DownloadRequest: 文件下载完成,路径是 /storage/emulated/0/Download/BaiHeWang.apk
VolleyHelper库的地址:https://github.com/13767004362/VolleyHelper