Spring for Android,文件上传进度

7

我正在使用Spring for Android作为Android应用程序中远程调用的REST模板。

目前正在处理将图像上传到服务器的工作。

我想到了以下解决方案:

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;
}

这个方法本身是可行的,但现在我想从中获取进度更新。

有没有人知道如何实现以及怎么做呢?

提前致谢 :)


可能是重复问题 https://dev59.com/c1XTa4cB1Zd3GeqPzC_n - Marcin Wasiluk
不完全如此——你发布的链接是关于带有内置Apache HttpClient的帖子,而不是针对Spring for Android的。如果可以使用Spring来完成,我希望避免使用HttpClient。 - Damian Walczak
你有没有运气解决这个问题? - Nick
2个回答

3

我遇到了同样的问题,于是决定研究Spring-Android源代码。经过大量挖掘,我发现需要扩展哪些内容。

从这个链接得到了一些灵感。

进度监听器

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);
    }

我的服务

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;
    }

}

我使用ListenerFileSystemResource代替FileSystemResource,并且它有效。希望这对未来的某个人有所帮助,因为我在Spring框架中没有找到任何相关信息。


是的,我在我的项目中使用了Spring。我在答案中添加了ListenerFileSystemResource类。 - Davi Alves
你能否将上传代码添加到答案中(在实际使用Spring的地方),这样我就可以接受答案了吗? - Damian Walczak
完成。但是正如我所说,你只需要使用ListenerFileSystemResource而不是Spring中的FileSystemResource。 - Davi Alves
嘿!你的解决方案很聪明,但只有在没有连接中断的情况下才能正常工作。如果连接以某种方式中断,则从InputStream读取过程仍将继续进行,直到超时期结束,但是远程服务器不会接收到读取的字节,因此客户端应用程序中会出现状态错误 - 您将不知道此中断是否仅为暂时性(网络暂时中断),并且读取的数据仍将传递到远程服务器,还是更像是在一段时间内永久性的(连接丢失无法重新建立),因此读取的数据将无法传递。 - bpawlowski
1
我尝试实现这个功能,但我认为它实际上并没有起作用。我有一个请求拦截器,在发送请求之前打印请求头。我还将进度打印到logcat中,并且在请求头之前正确打印了进度。 我调查了RestTemplate源代码和请求的不同部分首先被复制到字节数组中。您可以在FormHttpMessageConverter messageConverter.write(partBody, partContentType, multipartOutputMessage);中看到这一点。因此,您正在监听的进度不会在网络调用期间发生。 - grandouassou

0

你需要重写FormHttpMessageConverter和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);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接