Android开发一个VolleyHelper库,Hook Volley方式,无入侵实现(Form表单、JSON、文件上传、文件下载)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hexingen/article/details/81385125

前言:

近期,接手一个广告项目,该项目依赖一个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

猜你喜欢

转载自blog.csdn.net/hexingen/article/details/81385125
今日推荐