Spring for Android, file upload progress

2019-04-07 10:56发布

问题:

I'm using Spring for Android as a REST template for remote calls in Android app.

Currently working on uploading images to the server.

I came up with something like that:

public Picture uploadPicture(String accessToken, String fileToUpload) throws RestClientException {
    RestTemplate rest = new RestTemplate();
    FormHttpMessageConverter formConverter = new FormHttpMessageConverter();
    formConverter.setCharset(Charset.forName("UTF8"));
    CustomGsonHttpMessageConverter jsonConverter = new CustomGsonHttpMessageConverter();
    rest.getMessageConverters().add(formConverter);
    rest.getMessageConverters().add(jsonConverter);
    String uploadUri = AppConfig.ROOT_URL.concat(AppConfig.ADD_PHOTO);

    HashMap<String, Object> urlVariables = new HashMap<String, Object>();
    urlVariables.put("accessToken", accessToken);

    HttpHeaders httpHeaders = new HttpHeaders();
    httpHeaders.setAccept(Collections.singletonList(MediaType.parseMediaType("application/json")));

    MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
    parts.add("picture", new FileSystemResource(fileToUpload));
    Picture response = rest.postForObject(uploadUri, parts, Picture.class, urlVariables);
    return response;
}

which works OK, but now I'd like to get progress updates from it.

Does anyone know if it's possible and how to do that?

Thanks in advance :)

回答1:

So I had this same problem and decided to take a look into Spring-Android sources. After a lot of digging I found out what I need to extend. Got some of my inspiration from this link.

ProgressListener

public interface ProgressListener {
  void transferred(long num);
}

CountingInputStream

public class CountingInputStream extends FileInputStream {

 private final ProgressListener listener;
 private long transferred;

 public CountingInputStream(File file, ProgressListener listener) throws FileNotFoundException {
    super(file);
    this.listener = listener;
    this.transferred = 0;
 }

 @Override
 public int read(byte[] buffer) throws IOException {
    int bytesRead = super.read(buffer);
    if (bytesRead != -1) {
        this.transferred += bytesRead;
    }
    this.listener.transferred(this.transferred);
    return bytesRead;
 }

}

ListenerFileSystemResource

public class ListenerFileSystemResource extends FileSystemResource {

 private final ProgressListener listener;

 public ListenerFileSystemResource(File file, ProgressListener listener) {
     super(file);
     this.listener = listener;
 }

 @Override
 public InputStream getInputStream() throws IOException {
     return new CountingInputStream(super.getFile(), listener);
 }

}

SendFileTask

private class SendFileTask extends AsyncTask<String, Integer, Boolean> {
    private ProgressListener listener;
    private long totalSize;

    @Override
    protected Boolean doInBackground(String... params) {
        File file = new File(filePath);
        totalSize = file.length();
        listener = new ProgressListener() {
            @Override
            public void transferred(long num) {
                publishProgress((int) ((num / (float) totalSize) * 100));
            }
        };
        ListenerFileSystemResource resource = new ListenerFileSystemResource(file, listener);
        MyResult result = new MyService().uploadFile(resource);
    }

MyService

public FileResult uploadFile(ListenerFileSystemResource resource, Long userId, FileType type) {
    String[] pathParams = {ConnectorConstants.FILE_RESOURCE };
    String[] headerKeys = {"manager_user_id"};
    String[] headerValues = {String.valueOf(userId)};
    String[] formKeys = {ConnectorConstants.FILE_FORM_PARAM};
    Object[] formValues = {resource};

    MultiValueMap<String, Object> body = createMultiValueMap(formKeys, formValues);

    HttpConnector<FileResult> connector = new HttpConnector<FileResult>(FileResult.class);
    return connector.path(pathParams).header(createValuePairs(headerKeys, headerValues)).multipart().body(body).post();
}

HttpConnector

public final class HttpConnector<T> {

    public static String API_URL = "https://myapi.com";

    private UriComponentsBuilder builder;

    private RestTemplate template;

    private Class<T> generic;

    private HttpEntity<?> requestEntity;

    private HttpHeaders headers;

    /**
     * 
     * @param generic
     */
    protected HttpConnector(Class<T> generic)
    {
            this.builder = UriComponentsBuilder.fromUriString(API_URL);
            this.template = new RestTemplate();
    this.template.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
            this.generic = generic;
            this.template.getMessageConverters().add(new GsonHttpMessageConverter(getGson()));
            this.headers = new HttpHeaders();
    }

    /**
     * 
     * @param pathSegments
     * @return
     */
    protected HttpConnector<T> path(String[] pathSegments)
    {
            this.builder = builder.pathSegment(pathSegments);
            return this;
    }

    /**
     * 
     * @param headerParams
     * @return
     */
    protected HttpConnector<T> header(List<NameValuePair> headerParams)
    {
            for (NameValuePair param : headerParams)
            {
                    headers.add(param.getName(), param.getValue());
            }
            return this;
    }

    /**
     * 
     * @param queryParams
     * @return
     */
    protected HttpConnector<T> query(List<NameValuePair> queryParams)
    {
            for (NameValuePair param : queryParams)
            {
                    this.builder = builder.queryParam(param.getName(), param.getValue());
            }
            return this;
    }

    /**
     * 
     * @param body
     * @return
     */
    protected HttpConnector<T> body(MultiValueMap<String, ? extends Object> body)
    {
            this.requestEntity = new HttpEntity<Object>(body, headers);
            return this;
    }

    /**
     * 
     * @param body
     * @return
     */
    protected HttpConnector<T> body(Object body)
    {
            this.requestEntity = new HttpEntity<Object>(body, headers);
            headers.setContentType(MediaType.APPLICATION_JSON);
            return this;
    }

    /**
     * 
     * @return
     */
    protected HttpConnector<T> form()
    {
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
            return addFormConverter();
    }

    /**
     * 
     * @return
     */
    protected HttpConnector<T> multipart()
    {
            headers.setContentType(MediaType.MULTIPART_FORM_DATA);
            return addFormConverter();
    }

    /**
     * 
     * @return
     */
    private HttpConnector<T> addFormConverter()
    {
            this.template.getMessageConverters().add(new FormHttpMessageConverter());
            return this;
    }

    /**
     * 
     * @return
     * @throws MeplisNotFoundException 
     */
    protected T post() throws MeplisNotFoundException
    {
            return sendRequest(HttpMethod.POST);
    }

    /**
     * 
     * @return
     * @throws MeplisNotFoundException 
     */
    protected T put() throws MeplisNotFoundException
    {
            return sendRequest(HttpMethod.PUT);
    }

    /**
     * 
     * @return
     * @throws MeplisNotFoundException 
     */
    protected T get() throws MeplisNotFoundException
    {
            return sendRequest(HttpMethod.GET);
    }

    /**
     * 
     * @param method
     * @return
     * @throws MeplisNotFoundException 
     */
    private T sendRequest(HttpMethod method) throws MyServiceNotFoundException
    {
            HttpStatus status = null;
            ResponseEntity<T> response;
            try
            {
                    response = template.exchange(toUri(), method, getRequestEntity(), generic);
                    status = response.getStatusCode();
                    if (HttpStatus.OK.equals(status))
                    {
                            return response.getBody();
                    }
            } catch (HttpClientErrorException e)
            {
                    if (HttpStatus.NOT_FOUND.equals(e.getStatusCode()))
                    {
                            throw new MyServiceNotFoundException();
                    }
                    else
                    {
                            Log.e(getClass().toString(), String.format("Error %s request, status[%s]", method.toString(), e.getStatusCode()), e);
                    }
            } catch (Exception e)
            {
                    Log.e(getClass().toString(), String.format("Error %s request, status: %s", method.toString(), status), e);
            }
            return null;
    }

    /**
     * 
     * @return
     */
    private HttpEntity<?> getRequestEntity()
    {
            if (this.requestEntity == null)
            {
                    this.requestEntity = new HttpEntity<Object>(headers);
            }
            return requestEntity;
    }

    /**
     * 
     * @return
     */
    private URI toUri()
    {
            return this.builder.build().toUri();
    }

    /**
     * 
     * @return
     */
    private Gson getGson()
    {
            return new GsonBuilder().create();
    }

    /**
     * 
     * @return
     */
    public HttpHeaders getHeaders()
    {
            return headers;
    }

}

And I use ListenerFileSystemResource instead of FileSystemResource and works. Hope this will be helpful for someone in the future, since I didn't found any info on this for Spring framework.



回答2:

You need to override FormHttpMessageConverter and ResourceHttpMessageConverter :

public class ProgressFormHttpMessageConverter extends FormHttpMessageConverter {

    OnProgressListener mOnProgressListener;

    public ProgressFormHttpMessageConverter() {
        super();

        List<HttpMessageConverter<?>> partConverters = new ArrayList<HttpMessageConverter<?>>();
        partConverters.add(new ByteArrayHttpMessageConverter());
        StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
        stringHttpMessageConverter.setWriteAcceptCharset(false);
        partConverters.add(stringHttpMessageConverter);
        partConverters.add(new ProgressResourceHttpMessageConverter();
        setPartConverters(partConverters);
    }

    public ProgressFormHttpMessageConverter setOnProgressListener(OnProgressListener listener) {
        mOnProgressListener = listener;
        return this;
    }

    class ProgressResourceHttpMessageConverter extends ResourceHttpMessageConverter {

        @Override
        protected void writeInternal(Resource resource, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
            InputStream inputStream = resource.getInputStream();
            OutputStream outputStream = outputMessage.getBody();

            byte[] buffer = new byte[StreamUtils.BUFFER_SIZE];
            long contentLength = resource.contentLength();
            int byteCount = 0;
            int bytesRead = -1;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
                byteCount += bytesRead;

                if(mOnProgressListener != null) {
                    mOnProgressListener.onProgress(resource, byteCount, contentLength);
                }
            }
            outputStream.flush();
        }
    }

    public interface OnProgressListener {
        void onProgress(Resource resource, int downloaded, int downloadSize);
    }
}