使用改造下载图像文件

时间:2014-06-11 08:39:37

标签: android retrofit

我在Android项目中使用Retrofit 1.6.0,

请求网址:

https://example.com/image/thumbs/filename/sample.png

我的界面是这样的:

public interface ImageService {
    @GET("/image/thumbs/filename/{filename}")
    @Streaming
    void getThumbs(
        @Path("filename") String filename,
        Callback<Response> callback
    );
}

HTTP请求成功,但发生了一些错误

D/Retrofit(27613): ---> HTTP GET https://example.com/image/thumbs/filename/sample.png
D/Retrofit(27613): ---> END HTTP (no body)
D/Retrofit(27613): <--- HTTP 200 https://example.com/image/thumbs/filename/sample.png (451ms)
D/Retrofit(27613): : HTTP/1.1 200 OK
D/Retrofit(27613): Connection: Keep-Alive
D/Retrofit(27613): Content-Disposition: inline; filename="sample.png"
D/Retrofit(27613): Content-Type: image/png; charset=binary
D/Retrofit(27613): Date: Wed, 11 Jun 2014 06:02:31 GMT
D/Retrofit(27613): Keep-Alive: timeout=5, max=100
D/Retrofit(27613): OkHttp-Received-Millis: 1402466577134
D/Retrofit(27613): OkHttp-Response-Source: NETWORK 200
D/Retrofit(27613): OkHttp-Sent-Millis: 1402466577027
D/Retrofit(27613): Server: Apache/2.2.22 (Ubuntu)
D/Retrofit(27613): Transfer-Encoding: chunked
D/Retrofit(27613): X-Powered-By: PHP/5.4.28-1+deb.sury.org~precise+1
D/Retrofit(27613): ---- ERROR https://example.com/image/thumbs/filename/sample.png
D/Retrofit(27613): java.io.UnsupportedEncodingException: binary
D/Retrofit(27613):      at java.nio.charset.Charset.forNameUEE(Charset.java:322)
D/Retrofit(27613):      at java.lang.String.<init>(String.java:228)
D/Retrofit(27613):      at retrofit.RestAdapter.logAndReplaceResponse(RestAdapter.java:478)
D/Retrofit(27613):      at retrofit.RestAdapter.access$500(RestAdapter.java:109)
D/Retrofit(27613):      at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:333)
D/Retrofit(27613):      at retrofit.RestAdapter$RestHandler.access$100(RestAdapter.java:220)
D/Retrofit(27613):      at retrofit.RestAdapter$RestHandler$2.obtainResponse(RestAdapter.java:278)
D/Retrofit(27613):      at retrofit.CallbackRunnable.run(CallbackRunnable.java:42)
D/Retrofit(27613):      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
D/Retrofit(27613):      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
D/Retrofit(27613):      at retrofit.Platform$Android$2$1.run(Platform.java:142)
D/Retrofit(27613):      at java.lang.Thread.run(Thread.java:841)
D/Retrofit(27613): Caused by: java.nio.charset.UnsupportedCharsetException: binary
D/Retrofit(27613):      at java.nio.charset.Charset.forName(Charset.java:309)
D/Retrofit(27613):      at java.nio.charset.Charset.forNameUEE(Charset.java:320)
D/Retrofit(27613):      ... 11 more
D/Retrofit(27613): ---- END ERROR

如何解决这个问题?

4 个答案:

答案 0 :(得分:8)

问题是响应中的内容类型标头包含伪造的字符集:

Content-Type: image/png; charset=binary

Retrofit看到了这一点,并推断响应是可以记录的文本。您应该将问题报告给服务器的管理员。

如果您将问题报告给GitHub上的Retrofit问题跟踪器,我们可能会从此问题中恢复而不是崩溃。

答案 1 :(得分:4)

这些天我正在玩rxjava并进行改造,我在这里有一个快速演示。 谈话很便宜,直接向我展示我的代码,希望它有所帮助。

public interface ImageService {

    String ENDPOINT = "HTTP://REPLACE.ME";

    @GET
    @Streaming
    Observable<Bitmap> getThumbs(@Url String filepath);

    /********
     * Helper class that sets up a new services
     *******/
    class Instance {

        static ImageService instance;

        public static ImageService get() {
            if (instance == null)
                instance = newImageService();
            return instance;
        }

        public static ImageService newImageService() {
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(ImageService.ENDPOINT)
                    .addConverterFactory(BitmapConverterFactory.create())
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .build();
            return retrofit.create(ImageService.class);
        }
    }
}

我编写了自己的BitmapConverterFactory来将字节流转换为位图:

public final class BitmapConverterFactory extends Converter.Factory {

    public static BitmapConverterFactory create() {
        return new BitmapConverterFactory();
    }


    private BitmapConverterFactory() {
    }

    @Override
    public Converter<ResponseBody, Bitmap> responseBodyConverter(Type type, Annotation[] annotations,
                                                                 Retrofit retrofit) {
        if (type == Bitmap.class) {
            return new Converter<ResponseBody, Bitmap>(){

                @Override
                public Bitmap convert(ResponseBody value) throws IOException {
                    return BitmapFactory.decodeStream(value.byteStream());
                }
            };
        } else {
            return null;
        }
    }

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] annotations,
                                                          Retrofit retrofit) {
        return null;
    }
}

Gradle依赖项:

final RETROFIT_VERSION = '2.0.0-beta3'
compile "com.squareup.retrofit2:retrofit:$RETROFIT_VERSION"
compile "com.squareup.retrofit2:adapter-rxjava:$RETROFIT_VERSION"

干杯, vanvency

答案 2 :(得分:3)

当然我们通常使用Picasso来加载图像,但有时我们确实需要使用Retrofit来加载特殊图像(比如获取验证码图像),你需要为请求添加一些标题,从响应的标题中获取一些值(当然你也可以使用Picasso + OkHttp,但是在一个项目中你已经使用Retrofit来处理大多数网络请求),所以这里介绍如何通过Retrofit 2.0.0实现(我已经在我的项目中实现了)。

关键是您需要使用okhttp3.ResponseBody来接收响应,否则Retrofit会将响应数据解析为JSON,而不是二进制数据。

代码:

public interface Api {
    // don't need add 'Content-Type' header, it not works for Retrofit 2.0.0
    // @Headers({"Content-Type: image/png"})
    @GET
    Call<ResponseBody> fetchCaptcha(@Url String url);
}

Call<ResponseBody> call = api.fetchCaptcha(url);
call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if (response.isSuccessful()) {
                if (response.body() != null) {
                    // display the image data in a ImageView or save it
                    Bitmap bm = BitmapFactory.decodeStream(response.body().byteStream());
                    ivCaptcha.setImageBitmap(bm);
                } else {
                    // TODO
                }
            } else {
                // TODO
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            // TODO
        }
    });

答案 3 :(得分:1)

另一种方法是关闭完整日志记录,因此retrofit.RestAdapter.logAndReplaceResponse不会尝试使用响应正文