zoukankan      html  css  js  c++  java
  • Picasso加载网络图片失败,提示decodestream时返回null

    最近遇到一个问题,项目用的图片加载框架是Picasso,网络加载框架是okhttp,项目在加载轮播图时有时可以正常加载,有时,会加载失败,提示decodestream时返回null。

    首先,需要确定是哪个环节出了问题。

    网上搜了很多关于“decodestream时返回null”的问题,都说需要在decodestream之前reset stream。不过,由于用的是Picasso,不太方便改代码,再换个思路看一下。

    要确认是否是Picasso的问题,那我就用其他图片加载框架替换Picasso,这里,我使用Glide替换Picasso。

    替换之后,运行程序,图片加载没有任何问题。

    由此,可以确认,问题出在Picasso身上。

    于是,我又在网上搜索关于Picasso的这类问题,在百度上,完全没有类似问题,google上倒是可以找到几个类似问题,百度在这方面还需要加油啊。。。

    google上搜到的都是GitHub上Picasso使用者提的issue,这里也是说是decodestream调用两次的问题。

    于是我查看源码:

     static Bitmap decodeStream(InputStream stream, Request request) throws IOException {
        MarkableInputStream markStream = new MarkableInputStream(stream);
        stream = markStream;
    
        long mark = markStream.savePosition(65536); // TODO fix this crap.
    
        final BitmapFactory.Options options = RequestHandler.createBitmapOptions(request);
        final boolean calculateSize = RequestHandler.requiresInSampleSize(options);
    
        boolean isWebPFile = Utils.isWebPFile(stream);
        markStream.reset(mark);
        // When decode WebP network stream, BitmapFactory throw JNI Exception and make app crash.
        // Decode byte array instead
        if (isWebPFile) {
          byte[] bytes = Utils.toByteArray(stream);
          if (calculateSize) {
            BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
            RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options,
                request);
          }
          return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
        } else {
          if (calculateSize) {
            BitmapFactory.decodeStream(stream, null, options);
            RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options,
                request);
    
            markStream.reset(mark);
          }
          Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);
          if (bitmap == null) {
            // Treat null as an IO exception, we will eventually retry.
            throw new IOException("Failed to decode stream.");
          }
          return bitmap;
        }
      }
    View Code

    并定位到这个地方,进行单步调试,发现decodestream确实调用了两次,但是,jack已经进行了reset操作。这个地方不应该会出现问题。

    继续找资料,在Github找到一个解决方案:在url后面加上“?”+System.currentTimeMillis()。

    经测试,这样做可以正确加载图片。

    为什么url后加时间戳就能成功呢?

    url加时间戳后每次都从网络下载图片,没加的话会从缓存中加载图片。

    据此,我有理由怀疑,是缓存问题,导致这个问题出现。

    我在原有代码的基础上(url后面不加“?”+时间戳)加上

    .memoryPolicy(MemoryPolicy.NO_CACHE)
    .networkPolicy(NetworkPolicy.NO_CACHE)

    再次调试,发现可以正常加载图片。
    由此可以确定:是缓存问题导致加载图片失败。
    嗯,接下去该自定义缓存试一下了。
    再次到网上搜索资料(爱分享的程序员/媛就是这么招人喜欢)
    参考http://www.jianshu.com/p/6241950f9daf写了个自定义缓存。
    贴几处关键代码:
    OkHttpClient client = new OkHttpClient
                    .Builder()
                    .cache(new Cache(BaseApplication.getContext().getCacheDir(), 1000 * 1024))
                    .addInterceptor(new CaheInterceptor(BaseApplication.getContext()))
                    .addNetworkInterceptor(new CaheInterceptor(BaseApplication.getContext()))
                    .build();
            mPicasso = new Picasso.Builder(BaseApplication.getContext())
                    .downloader(new ImageDownLoader(client))
                    .build();
            mPicasso.with(BaseApplication.getContext()).setIndicatorsEnabled(true);//左上角标出颜色,红色为从网络获取,绿色为从内存中获取,蓝色为从硬盘中获取
    View Code
    public class ImageDownLoader implements Downloader {
        OkHttpClient client = null;
    
        public ImageDownLoader(OkHttpClient client) {
            this.client = client;
        }
    
        @Override
        public Response load(Uri uri, int networkPolicy) throws IOException {
    
            CacheControl cacheControl = null;
            if (networkPolicy != 0) {
                if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
                    cacheControl = CacheControl.FORCE_CACHE;
                } else {
                    CacheControl.Builder builder = new CacheControl.Builder();
                    if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
                        builder.noCache();
                    }
                    if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
                        builder.noStore();
                    }
                    cacheControl = builder.build();
                }
            }
    
            Request.Builder builder = new Request.Builder().url(uri.toString());
            if (cacheControl != null) {
                builder.cacheControl(cacheControl);
            }
    
            okhttp3.Response response = client.newCall(builder.build()).execute();
            int responseCode = response.code();
            if (responseCode >= 300) {
                response.body().close();
                throw new ResponseException(responseCode + " " + response.message(), networkPolicy,
                        responseCode);
            }
    
            boolean fromCache = response.cacheResponse() != null;
    
            ResponseBody responseBody = response.body();
            return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength());
    
        }
    
        @Override
        public void shutdown() {
    
            Cache cache = client.cache();
            if (cache != null) {
                try {
                    cache.close();
                } catch (IOException ignored) {
                }
            }
        }
    }
    View Code
    public class CaheInterceptor implements Interceptor {
    
        private Context context;
    
        public CaheInterceptor(Context context) {
            this.context = context;
        }
    
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            if (NetStateUtil.isNetworkAvailable(context)) {
                Response response = chain.proceed(request);
                // read from cache for 60 s
                int maxAge = 300;
                String cacheControl = request.cacheControl().toString();
                Log.e("Tamic", maxAge + "s load cahe:" + cacheControl);
                return response.newBuilder()
                        .removeHeader("Pragma")
                        .removeHeader("Cache-Control")
                        .header("Cache-Control", "public, max-age=" + maxAge)
                        .build();
            } else {
                Log.e("Tamic", " no network load cahe");
                request = request.newBuilder()
                        .cacheControl(CacheControl.FORCE_CACHE)
                        .build();
                Response response = chain.proceed(request);
                //set cahe times is 3 days
                int maxStale = 60 * 60 * 24 * 3;
                return response.newBuilder()
                        .removeHeader("Pragma")
                        .removeHeader("Cache-Control")
                        .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                        .build();
            }
        }
    }
    View Code

    加入缓存后,删除之前测试加上的代码,再次调试,正常加载图片。

    Tips:根源还是在picasso2.5.2底层用的okhttp2,而我在项目中用的是okhttp3,完全不匹配啊,听说picasso2.5.3解决了这个问题,但是

    我并没有找到这个版本,所以先用这个方案~~~写出来容易。。。改这个问题也花了我不少时间~~~咱们程序员还是需要多研究、多分享啊~~~

     
  • 相关阅读:
    学习记录
    cxf client端借口类型找不到问题
    zookeeper dubbo 问题解决录
    unkow jdbc driver : http://maven.apache.org
    推荐eclipse插件Properties Editor
    WPS for Linux,系统缺失字体
    Android 联系人字母排序(仿微信)
    Android之ORMLite实现数据持久化的简单使用
    设计模式之多例模式
    设计模式之单例模式
  • 原文地址:https://www.cnblogs.com/shenchanghui/p/6425395.html
Copyright © 2011-2022 走看看