最近遇到一個問題,項目用的圖片加載框架是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; } }
並定位到這個地方,進行單步調試,發現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);//左上角標出顏色,紅色為從網絡獲取,綠色為從內存中獲取,藍色為從硬盤中獲取

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

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(); } } }
加入緩存后,刪除之前測試加上的代碼,再次調試,正常加載圖片。
Tips:根源還是在picasso2.5.2底層用的okhttp2,而我在項目中用的是okhttp3,完全不匹配啊,聽說picasso2.5.3解決了這個問題,但是
我並沒有找到這個版本,所以先用這個方案~~~寫出來容易。。。改這個問題也花了我不少時間~~~咱們程序員還是需要多研究、多分享啊~~~