其他分享
首页 > 其他分享> > Glide + okHttp3 去除图片流前八个字节后显示图片

Glide + okHttp3 去除图片流前八个字节后显示图片

作者:互联网

今天在学习群里碰到了一个问题:用Glide + okHttp3加载图片,这个时候有一个需求就是,有一个url对应的图片流,这个图片数据流需要去除前面八个字节后才能正常显示图片,所以那位大佬的思路就是添加一个okHttp的应用层拦截器,并在该拦截器中对图片流前面的八个字节进行移除,于是就有了最开始的下面这段代码

@GlideModule
public class MyOkHttpGlideModule extends AppGlideModule {
    @Override
    public void registerComponents(Context context, Glide glide, Registry registry) {
        Log.i("zhang_xin", "减去了若干字节00000000");
        //定制OkHttp
        OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
        //请求头设置
        httpClientBuilder.interceptors().add(new ReduceByteInterceptor());
        OkHttpClient okHttpClient = httpClientBuilder.build();
        registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(okHttpClient));
    }


    public static class ReduceByteInterceptor implements Interceptor {
        public ReduceByteInterceptor() {
        }

        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Response originalResponse = chain.proceed(request);
            ResponseBody body = originalResponse.body();
            // 获取
            InputStream inputStream = body.byteStream();
            inputStream.read();
            Log.i("Test", "减去了若干字节前" + originalResponse.body().byteStream().getClass().getName());
            inputStream.skip(7);
            Log.i("Test", "减去了若干字节后" + originalResponse.body().byteStream().getClass().getName());
            return originalResponse.newBuilder().body(body).build();
        }
        
    }
}

我们主要看一下ReduceByteInterceptor的intercept方法

@Override
public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    Response originalResponse = chain.proceed(request);
    ResponseBody body = originalResponse.body();
    InputStream inputStream = body.byteStream();
    // 通过body.byteStream()获取的inputStream调用read和skip方法的时候都会操作body.source().buffer,具体原因看源码
    inputStream.read();
    inputStream.skip(7);

    return originalResponse.newBuilder().body(body).build();
}

实际上

inputStream.read();
inputStream.skip(7);

等价于

inputStream.skip(8);

InputStream对象是通过ResponseBody.byteStream()获得的,调用该InputStream.read()方法的时候虽然会新建一个InputStream对象,但是无论哪个InputStream对象,操作的都是同一个ResponseBody.source().buffer,而ResponseBody.source().buffer就是网络实体数据的缓冲区。既然通过ResponseBody.byteStream()获取的InputStream对象操作的是ResponseBody.source().buffer,那我们可以直接操作buffer (直接操作buffer的话需要先调用InputStream.read()或者ResponseBody.source().readByte(),具体原因看源码,我是没怎么看懂,最好是不要这么使用) ,或者可以通过ResponseBody.source()操作buffer即可。

所以

ResponseBody body = originalResponse.body();
InputStream inputStream = body.byteStream();
inputStream.read();
inputStream.skip(7);

等价于

ResponseBody body = originalResponse.body();
// 这里便会直接操作buffer跳过8个字节
originalResponse.body().source().skip(8);

那这段代码运行得起来结果正确吗?

不正确!!

我们在Glide加一个载回调,打印出失败的信息

Glide.with(this)
        .load("https://srcfiles-1301875640.cos.accelerate.myqcloud.com/movie/cn/yz/HKDOLL-014/HKDOLL-014.jpg")
        .skipMemoryCache(true)
        .diskCacheStrategy(DiskCacheStrategy.NONE)
        .addListener(new RequestListener<Drawable>() {
            @Override
            public boolean onl oadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
                Log.i("Shzy", "onLoadFailed: " + e.getMessage());
                return false;
            }

            @Override
            public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                return false;
            }
        })
        .into(imageView);

我们看看结果

这是java的Io异常,异常原因就是:预期收到296947个字节数据,但实际上只收到了296939个字节的数据,因为我们在拦截器移除了前面八个字节。而296947也就是预期收到的字节数实际上就是ResponseBody.contentLength(),也就是响应体的总长度(和响应头的Content-Length字段对应),而ResponseBody.contentLength属性是一个final对象我们没法直接修改,所以我们必须新建一个响应体,并保证contentLength和实际的内容长度一致

// 构建一个新的请求体,目的是让 content-Length = 去除8字节后的
ResponseBody newResponseBody = ResponseBody
        .create(originalResponse.body().contentType(), bufferedSource.readByteArray());
return originalResponse.newBuilder().body(newResponseBody).build();

所以最后的代码应该是这样的

@GlideModule
public class MyOkHttpGlideModule extends AppGlideModule {
    @Override
    public void registerComponents(Context context, Glide glide, Registry registry) {
        //定制OkHttp
        OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
        //请求头设置
        httpClientBuilder.addInterceptor(new ReduceByteInterceptor());
        OkHttpClient okHttpClient = httpClientBuilder.build();
        registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(okHttpClient));
    }


    public static class ReduceByteInterceptor implements Interceptor {

        public ReduceByteInterceptor() {
        }

        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Response originalResponse = chain.proceed(request);

            if(originalResponse.body() == null){
                return null;
            }
            BufferedSource bufferedSource = originalResponse.body().source();
            bufferedSource.skip(8);
            // 构建一个新的请求体,目的是让 contentLength 与 去除8字节后的内容长度一致
            ResponseBody newResponseBody = ResponseBody
                    .create(originalResponse.body().contentType(), bufferedSource.readByteArray());

            return originalResponse.newBuilder().body(newResponseBody).build();
        }

    }
}

标签:body,Glide,流前,inputStream,ResponseBody,originalResponse,InputStream,public,图片
来源: https://www.cnblogs.com/shzy-android/p/15841594.html