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解決了這個問題,但是

我並沒有找到這個版本,所以先用這個方案~~~寫出來容易。。。改這個問題也花了我不少時間~~~咱們程序員還是需要多研究、多分享啊~~~

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM