图片加载框架Picasso - 源码分析
簡(jiǎn)書(shū):圖片加載框架Picasso - 源碼分析
前一篇文章講了Picasso的詳細(xì)用法,Picasso 是一個(gè)強(qiáng)大的圖片加載緩存框架,一個(gè)非常優(yōu)秀的開(kāi)源庫(kù),學(xué)習(xí)一個(gè)優(yōu)秀的開(kāi)源庫(kù),,我們不僅僅是學(xué)習(xí)它的用法,停留在使用API層面,我們也要試著去閱讀源碼,有兩個(gè)方面的原因,第一,熟悉了源碼我們才能更好的駕馭,項(xiàng)目中做我們需要的定制。第二,學(xué)習(xí)它的設(shè)計(jì)思想、編碼風(fēng)格、代碼的架構(gòu),然后在項(xiàng)目中對(duì)這些好的思想和架構(gòu)加以實(shí)踐,變成自己的知識(shí),這樣才會(huì)對(duì)我們有更多的提升和幫助。這也是我們學(xué)習(xí)的目的,因此這篇文章對(duì)Picasso 的源碼和流程做一個(gè)分析。
一、Picasso 加載圖片流程圖
Picasso-圖片加載流程.png上面就是Picasso加載圖片的流程,圖畫(huà)的丑,各位見(jiàn)諒。
二、重要的類(lèi)介紹
(0)Picasso: 圖片加載、轉(zhuǎn)換、緩存的管理類(lèi)。單列模式 ,通過(guò)with方法獲取實(shí)例,也是加載圖片的入口。
(1)RequestCreator: Request構(gòu)建類(lèi),Builder 模式,采用鏈?zhǔn)皆O(shè)置該Request的屬性(如占位圖、緩存策略、裁剪規(guī)則、顯示大小、優(yōu)先級(jí)等等)。最后調(diào)用build()方法生成一個(gè)請(qǐng)求(Request)。
(2)DeferredRequestCreator:RequestCreator的包裝類(lèi),當(dāng)創(chuàng)建請(qǐng)求的時(shí)候還不能獲取ImageView的寬和高的時(shí)候,則創(chuàng)建一個(gè)DeferredRequestCreator,DeferredRequestCreator里對(duì) target 設(shè)置監(jiān)聽(tīng),直到可以獲取到寬和高的時(shí)候重新執(zhí)行請(qǐng)求創(chuàng)建。
(3) Action: 請(qǐng)求包裝類(lèi),存儲(chǔ)了該請(qǐng)求和RequestCreator設(shè)置的這些屬性,最終提交給線程執(zhí)行下載。
(4)Dispatcher:分發(fā)器,分發(fā)執(zhí)行各種請(qǐng)求、分發(fā)結(jié)果等等。
(5)PicassoExecutorService:Picasso使用的線程池,默認(rèn)池大小為3。
(6)LruCache:一個(gè)使用最近最少使用策略的內(nèi)存緩存。
(7)BitmapHunter:這是Picasso的一個(gè)核心的類(lèi),開(kāi)啟線程執(zhí)行下載,獲取結(jié)果后解碼成Bitmap,然后做一些轉(zhuǎn)換操作如圖片旋轉(zhuǎn)、裁剪等,如果請(qǐng)求設(shè)置了轉(zhuǎn)換器Transformation,也會(huì)在BitmapHunter里執(zhí)行這些轉(zhuǎn)換操作。
(8)NetworkRequestHandler:網(wǎng)絡(luò)請(qǐng)求處理器,如果圖片需要從網(wǎng)絡(luò)下載,則用這個(gè)處理器處理。
(9)FileRequestHandler:文件請(qǐng)求處理器,如果請(qǐng)求的是一張存在文件中的圖片,則用這個(gè)處理器處理。
(10)AssetRequestHandler: Asset 資源圖片處理器,如果是加載asset目錄下的圖片,則用這個(gè)處理器處理。
(11)ResourceRequestHandler:Resource資源圖片處理器,如果是加載res下的圖片,則用這個(gè)處理器處理。
(12)ContentStreamRequestHandler: ContentProvider 處理器,如果是ContentProvider提供的圖片,則用這個(gè)處理器處理
(13)MediaStoreRequestHandler: MediaStore 請(qǐng)求處理器,如果圖片是存在MediaStore上的則用這個(gè)處理器處理。
(14)ContactsPhotoRequestHandler:ContactsPhoto 請(qǐng)求處理器,如果加載com.android.contacts/ 下的tu圖片用這個(gè)處理器處理。如:
上面8-14 是默認(rèn)的提供的幾個(gè)處理器,分別處理不同來(lái)源的請(qǐng)求。
(15)Response: 返回的結(jié)果信息,Stream流或者Bitmap。
(16)Request: 請(qǐng)求實(shí)體類(lèi),存儲(chǔ)了應(yīng)用在圖片上的信息。
(17)Target:圖片加載的監(jiān)聽(tīng)器接口,有3個(gè)回調(diào)方法,onPrepareLoad 在請(qǐng)求提交前回調(diào),onBitmapLoaded 請(qǐng)求成功回調(diào),并返回Bitmap,onBitmapFailed請(qǐng)求失敗回調(diào)。
(18)PicassoDrawable:繼承BitmapDrawable,實(shí)現(xiàn)了過(guò)渡動(dòng)畫(huà)和圖片來(lái)源的標(biāo)識(shí)(就是圖片來(lái)源的指示器,要調(diào)用 setIndicatorsEnabled(true)方法才生效),請(qǐng)求成功后都會(huì)包裝成BitmapDrawable顯示到ImageView 上。
(19)OkHttpDownloader:用OkHttp實(shí)現(xiàn)的圖片下載器,默認(rèn)就是用的這個(gè)下載器。
(20)UrlConnectionDownloader:使用HttpURLConnection 實(shí)現(xiàn)的下載器。
(21)MemoryPolicy: 內(nèi)存緩存策略,一個(gè)枚舉類(lèi)型。
(22)NetworkPolicy: 磁盤(pán)緩存策略,一個(gè)枚舉類(lèi)型。
(23) Stats: 這個(gè)類(lèi)相當(dāng)于日志記錄,會(huì)記錄如:內(nèi)存緩存的命中次數(shù),丟失次數(shù),下載次數(shù),轉(zhuǎn)換次數(shù)等等,我們可以通過(guò)StatsSnapshot類(lèi)將日志打印出來(lái),看一下整個(gè)項(xiàng)目的圖片加載情況。
(24)StatsSnapshot :狀態(tài)快照,和上面的Stats對(duì)應(yīng),打印Stats紀(jì)錄的信息。
以上就是Picasso 的一些關(guān)鍵的類(lèi)的介紹(還有一些簡(jiǎn)單的沒(méi)有列舉)。
三、流程分析
上一節(jié)介紹了Picasso 的一些關(guān)鍵類(lèi),接下來(lái)就以加載網(wǎng)絡(luò)圖片為例,分析Picasso加載圖片的整個(gè)流程
Picasso.with(this).load(URL).placeholder(R.drawable.default_bg).error(R.drawable.error_iamge).into(mBlurImage);復(fù)制代碼1, 獲取Picasso instance
首先要獲取一個(gè)Picasso對(duì)象,采用的單例模式
//單例模式獲取Picasso 對(duì)象 public static Picasso with(Context context) {if (singleton == null) {synchronized (Picasso.class) {if (singleton == null) {singleton = new Builder(context).build();}}}return singleton;}// 真正new 的地方在build()方法里public Picasso build() {Context context = this.context;if (downloader == null) {//配置默認(rèn)的下載器,首先通過(guò)反射獲取OkhttpClient,如果獲取到了,就使用OkHttpDwownloader作為默認(rèn)下載器//如果獲取不到就使用UrlConnectionDownloader作為默認(rèn)下載器downloader = Utils.createDefaultDownloader(context);}if (cache == null) {// 配置內(nèi)存緩存,大小為手機(jī)內(nèi)存的15%cache = new LruCache(context);}if (service == null) {// 配置Picaso 線程池,核心池大小為3service = new PicassoExecutorService();}if (transformer == null) {// 配置請(qǐng)求轉(zhuǎn)換器,默認(rèn)的請(qǐng)求轉(zhuǎn)換器沒(méi)有做任何事,直接返回原請(qǐng)求transformer = RequestTransformer.IDENTITY;}Stats stats = new Stats(cache);//分發(fā)器 Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,defaultBitmapConfig, indicatorsEnabled, loggingEnabled);}復(fù)制代碼2, 通過(guò)load方法生成一個(gè)RequestCreator
通過(guò)load方法生成一個(gè)RequestCreator,用鏈?zhǔn)絘pi 來(lái)構(gòu)建一個(gè)圖片下載請(qǐng)求
//load有幾個(gè)重載方法,參數(shù)為string和File 的重載最重都會(huì)包裝成一個(gè)Uri 調(diào)用這個(gè)方法public RequestCreator load(Uri uri) {return new RequestCreator(this, uri, 0);} // 如果是加載資源id 的圖片會(huì)調(diào)用這個(gè)方法public RequestCreator load(int resourceId) {if (resourceId == 0) {throw new IllegalArgumentException("Resource ID must not be zero.");}return new RequestCreator(this, null, resourceId);}復(fù)制代碼RequestCreator提供了很多的API 來(lái)構(gòu)建請(qǐng)求,如展位圖、大小、轉(zhuǎn)換器、裁剪等等,這些API其實(shí)是為對(duì)應(yīng)的屬性賦值,最終會(huì)在into方法中構(gòu)建請(qǐng)求。
// 配置占位圖,在加載圖片的時(shí)候顯示 public RequestCreator placeholder(int placeholderResId) {if (!setPlaceholder) {throw new IllegalStateException("Already explicitly declared as no placeholder.");}if (placeholderResId == 0) {throw new IllegalArgumentException("Placeholder image resource invalid.");}if (placeholderDrawable != null) {throw new IllegalStateException("Placeholder image already set.");}this.placeholderResId = placeholderResId;return this;}// 配置真正顯示的大小public RequestCreator resize(int targetWidth, int targetHeight) {data.resize(targetWidth, targetHeight);return this;}復(fù)制代碼3,into 添加顯示的View,并且提交下載請(qǐng)求
into方法里面干了3件事情:
1, 判斷是否設(shè)置了fit 屬性,如果設(shè)置了,再看是否能夠獲取ImageView 的寬高,如果獲取不到,生成一個(gè)DeferredRequestCreator(延遲的請(qǐng)求管理器),然后直接return,在DeferredRequestCreator中當(dāng)監(jiān)聽(tīng)到可以獲取ImageView 的寬高的時(shí)候,再執(zhí)行into方法。
2, 判斷是否從內(nèi)存緩存獲取圖片,如果沒(méi)有設(shè)置NO_CACHE,則從內(nèi)存獲取,命中直接回調(diào)CallBack 并且顯示圖片。
3, 如果緩存未命中,則生成一個(gè)Action,并提交Action。
public void into(ImageView target, Callback callback) {long started = System.nanoTime();// 檢查是否在主線程checkMain();if (target == null) {throw new IllegalArgumentException("Target must not be null.");}//如果沒(méi)有url或者resourceId 則取消請(qǐng)求if (!data.hasImage()) {picasso.cancelRequest(target);if (setPlaceholder) {setPlaceholder(target, getPlaceholderDrawable());}return;}//判斷是否設(shè)置了fit屬性if (deferred) {if (data.hasSize()) {throw new IllegalStateException("Fit cannot be used with resize.");}int width = target.getWidth();int height = target.getHeight();if (width == 0 || height == 0) {if (setPlaceholder) {setPlaceholder(target, getPlaceholderDrawable());}//如果獲取不到寬高,生成一個(gè)DeferredRequestCreator(延遲的請(qǐng)求管理器),然后直接return,//在DeferredRequestCreator中當(dāng)監(jiān)聽(tīng)到可以獲取ImageView 的寬高的時(shí)候,再執(zhí)行into方法。picasso.defer(target, new DeferredRequestCreator(this, target, callback));return;}data.resize(width, height);}Request request = createRequest(started);String requestKey = createKey(request);//是否從內(nèi)存緩存中獲取if (shouldReadFromMemoryCache(memoryPolicy)) {Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);if (bitmap != null) {//緩存命中,取消請(qǐng)求,并顯示圖片picasso.cancelRequest(target);setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);if (picasso.loggingEnabled) {log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);}if (callback != null) {callback.onSuccess();}return;}}if (setPlaceholder) {setPlaceholder(target, getPlaceholderDrawable());}//內(nèi)存緩存未命中或者設(shè)置了不從內(nèi)存緩存獲取,則生成一個(gè)Action ,提交執(zhí)行。Action action =new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,errorDrawable, requestKey, tag, callback, noFade);picasso.enqueueAndSubmit(action);// 提交請(qǐng)求}復(fù)制代碼4, 提交、分發(fā)、執(zhí)行請(qǐng)求。
會(huì)經(jīng)過(guò)下面這一系列的操作,最重將Action 交給BitmapHunter 執(zhí)行。
enqueueAndSubmit -> submit -> dispatchSubmit -> performSubmit:
5,指定對(duì)應(yīng)的處理器(RequestHandler)
在上面執(zhí)行的請(qǐng)求的performSubmit 方法里,調(diào)用了forRequest 方法為對(duì)應(yīng)的Action 生成一個(gè)BitmapHunter,里面有一個(gè)重要的步驟,指定請(qǐng)求處理器(在上面一節(jié)介紹Picasso有7種請(qǐng)求處理器,看一下對(duì)應(yīng)的代碼:
static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,Action action) {Request request = action.getRequest();List<RequestHandler> requestHandlers = picasso.getRequestHandlers();// Index-based loop to avoid allocating an iterator.//noinspection ForLoopReplaceableByForEachfor (int i = 0, count = requestHandlers.size(); i < count; i++) {RequestHandler requestHandler = requestHandlers.get(i);// 循環(huán)請(qǐng)求處理器列表,如果找到有能處理這個(gè)請(qǐng)求的請(qǐng)求處理器// 則生成BitmapHunterif (requestHandler.canHandleRequest(request)) {return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);}}return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);}復(fù)制代碼從Picasso里獲取一個(gè)處理器列表,然后循環(huán)列表,看是否有能處理該請(qǐng)求的處理器,如果有,則生成BitmapHunter,那么這個(gè)請(qǐng)求處理器的列表在哪兒初始化的呢?請(qǐng)看源碼:
// 1,首先調(diào)用了getRequestHandlersList<RequestHandler> getRequestHandlers() {return requestHandlers;}// 2 requestHandlers 列表是在Picasso 構(gòu)造函數(shù)里出實(shí)話的Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {....//前面代碼省略// 添加了7個(gè)內(nèi)置的請(qǐng)求處理器// 如果你自己通過(guò)Builder添了額外的處理器,也會(huì)添加在這個(gè)列表里面int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);List<RequestHandler> allRequestHandlers =new ArrayList<RequestHandler>(builtInHandlers + extraCount);// ResourceRequestHandler needs to be the first in the list to avoid// forcing other RequestHandlers to perform null checks on request.uri// to cover the (request.resourceId != 0) case.allRequestHandlers.add(new ResourceRequestHandler(context));if (extraRequestHandlers != null) {allRequestHandlers.addAll(extraRequestHandlers);}allRequestHandlers.add(new ContactsPhotoRequestHandler(context));allRequestHandlers.add(new MediaStoreRequestHandler(context));allRequestHandlers.add(new ContentStreamRequestHandler(context));allRequestHandlers.add(new AssetRequestHandler(context));allRequestHandlers.add(new FileRequestHandler(context));allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));requestHandlers = Collections.unmodifiableList(allRequestHandlers);//后面代碼省略 ...}復(fù)制代碼小結(jié): 在Picasso 的構(gòu)造函數(shù)里 初始化了內(nèi)置的7中請(qǐng)求處理器,然后在生成BitmapHunter的時(shí)候,循環(huán)列表,找到可以處理對(duì)應(yīng)請(qǐng)求的處理器。
6, 重點(diǎn):BitmapHunter (圖片捕獲器)
上一節(jié)重要類(lèi)介紹的時(shí)候介紹過(guò)BitmapHunter,BitmapHunter繼承Runnable,其實(shí)就是開(kāi)啟一個(gè)線程執(zhí)行最終的下載。看一下源碼:
1, run() 方法
當(dāng)將一個(gè)bitmapHunter submit 給一個(gè)線程池執(zhí)行的時(shí)候,就會(huì)執(zhí)行run() 方法,run里面調(diào)用的是hunt方法來(lái)獲取結(jié)果,看一下hunt方法:
Bitmap hunt() throws IOException {Bitmap bitmap = null;// 是否從內(nèi)存緩存獲取Bitmapif (shouldReadFromMemoryCache(memoryPolicy)) {bitmap = cache.get(key);if (bitmap != null) {stats.dispatchCacheHit();loadedFrom = MEMORY;if (picasso.loggingEnabled) {log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache");}return bitmap;}}data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;// 請(qǐng)求處理器處理請(qǐng)求,獲取結(jié)果,Result里可能是Bitmap,可能是StreamRequestHandler.Result result = requestHandler.load(data, networkPolicy);if (result != null) {loadedFrom = result.getLoadedFrom();exifRotation = result.getExifOrientation();bitmap = result.getBitmap();// If there was no Bitmap then we need to decode it from the stream.if (bitmap == null) {InputStream is = result.getStream();try {bitmap = decodeStream(is, data);} finally {Utils.closeQuietly(is);}}}if (bitmap != null) {if (picasso.loggingEnabled) {log(OWNER_HUNTER, VERB_DECODED, data.logId());}stats.dispatchBitmapDecoded(bitmap);if (data.needsTransformation() || exifRotation != 0) {synchronized (DECODE_LOCK) {if (data.needsMatrixTransform() || exifRotation != 0) {//如果需要做轉(zhuǎn)換,則在這里做轉(zhuǎn)換處理,如角度旋轉(zhuǎn),裁剪等。bitmap = transformResult(data, bitmap, exifRotation);if (picasso.loggingEnabled) {log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());}}if (data.hasCustomTransformations()) {// 如果配置了自定義轉(zhuǎn)換器,則在這里做轉(zhuǎn)換處理。bitmap = applyCustomTransformations(data.transformations, bitmap);if (picasso.loggingEnabled) {log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), "from custom transformations");}}}if (bitmap != null) {stats.dispatchBitmapTransformed(bitmap);}}}return bitmap;}復(fù)制代碼7,Downloader 下載器下載圖片
上面的hunt方法獲取結(jié)果的時(shí)候,最終調(diào)用的是配置的處理器的load方法,如下:
RequestHandler.Result result = requestHandler.load(data, networkPolicy);復(fù)制代碼加載網(wǎng)絡(luò)圖片用的是NetworkRequestHandler,匹配處理器,有個(gè)canHandleRequest 方法:
public boolean canHandleRequest(Request data) {String scheme = data.uri.getScheme();return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));}復(fù)制代碼判斷的條件是,Uri帶有"http://" 或者 https:// 前綴則可以處理
我們接下來(lái)看一下NetworkRequestHandler的load方法:
public Result load(Request request, int networkPolicy) throws IOException {//最終調(diào)用downloader的load方法獲取結(jié)果Response response = downloader.load(request.uri, request.networkPolicy);if (response == null) {return null;}Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;Bitmap bitmap = response.getBitmap();if (bitmap != null) {return new Result(bitmap, loadedFrom);}InputStream is = response.getInputStream();if (is == null) {return null;}// Sometimes response content length is zero when requests are being replayed. Haven't found// root cause to this but retrying the request seems safe to do so.if (loadedFrom == DISK && response.getContentLength() == 0) {Utils.closeQuietly(is);throw new ContentLengthException("Received response with 0 content-length header.");}if (loadedFrom == NETWORK && response.getContentLength() > 0) {stats.dispatchDownloadFinished(response.getContentLength());}return new Result(is, loadedFrom);}復(fù)制代碼NetworkRequestHandler最終是調(diào)用的downloader 的load方法下載圖片。內(nèi)置了2個(gè)Downloader,OkhttpDownloader和UrlConnectionDownloader 。我們以UrlConnectionDownloader為例,來(lái)看一下load方法:
public Response load(Uri uri, int networkPolicy) throws IOException {// 如果SDK 版本大于等于14,安裝磁盤(pán)緩存,用的是HttpResponseCache(緩存http或者h(yuǎn)ttps的response到文件系統(tǒng))if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {installCacheIfNeeded(context);}HttpURLConnection connection = openConnection(uri);//設(shè)置使用緩存connection.setUseCaches(true);if (networkPolicy != 0) {String headerValue;// 下面一段代碼是設(shè)置緩存策略if (NetworkPolicy.isOfflineOnly(networkPolicy)) {headerValue = FORCE_CACHE;} else {StringBuilder builder = CACHE_HEADER_BUILDER.get();builder.setLength(0);if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {builder.append("no-cache");}if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {if (builder.length() > 0) {builder.append(',');}builder.append("no-store");}headerValue = builder.toString();}connection.setRequestProperty("Cache-Control", headerValue);}int responseCode = connection.getResponseCode();if (responseCode >= 300) {connection.disconnect();throw new ResponseException(responseCode + " " + connection.getResponseMessage(),networkPolicy, responseCode);}long contentLength = connection.getHeaderFieldInt("Content-Length", -1);boolean fromCache = parseResponseSourceHeader(connection.getHeaderField(RESPONSE_SOURCE));// 最后獲取InputStream流包裝成Response返回return new Response(connection.getInputStream(), fromCache, contentLength);}復(fù)制代碼小結(jié):梳理一下調(diào)用鏈, BitmapHunter -> NetworkRequestHandler -> UrlConnectionDownloader(也有可能是OkHttpDownloader),經(jīng)過(guò)這一系列的調(diào)用,最后在BitmapHunter 的run 方法中就可以獲取到我們最終要的Bitmap。
8,返回結(jié)果并顯示在Target上
在BitmapHunter獲取結(jié)果后,分發(fā)器分發(fā)結(jié)果,通過(guò)Hander處理后,執(zhí)行performComplete方法:
//1, void performComplete(BitmapHunter hunter) {// 這里將結(jié)果緩存到內(nèi)存if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {cache.set(hunter.getKey(), hunter.getResult());}hunterMap.remove(hunter.getKey());// 請(qǐng)求完畢,將hunter從表中移除batch(hunter);if (hunter.getPicasso().loggingEnabled) {log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), "for completion");}}// 2,然后將BitmapHunter添加到一個(gè)批處理列表,通過(guò)Hander發(fā)送一個(gè)批處理消息 private void batch(BitmapHunter hunter) {if (hunter.isCancelled()) {return;}batch.add(hunter);if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);}} // 3,最后執(zhí)行performBatchComplete 方法,通過(guò)主線程的Handler送處理完成的消息 void performBatchComplete() {List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);batch.clear();mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));logBatch(copy);}// 4,最后在Picasso 中handleMessage,顯示圖片static final Handler HANDLER = new Handler(Looper.getMainLooper()) { public void handleMessage(Message msg) {switch (msg.what) {case HUNTER_BATCH_COMPLETE: {("unchecked") List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;//noinspection ForLoopReplaceableByForEachfor (int i = 0, n = batch.size(); i < n; i++) {BitmapHunter hunter = batch.get(i);hunter.picasso.complete(hunter);}break;}//后面代碼省略...}; // 5,最后回調(diào)到ImageViewAction 的complete方法顯示圖片 public void complete(Bitmap result, Picasso.LoadedFrom from) {if (result == null) {throw new AssertionError(String.format("Attempted to complete action with no result!\n%s", this));}ImageView target = this.target.get();if (target == null) {return;}Context context = picasso.context;boolean indicatorsEnabled = picasso.indicatorsEnabled;//將結(jié)果包裝成一個(gè)PicassoDrawable 并顯示PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);if (callback != null) {callback.onSuccess(); 回調(diào)callback}}復(fù)制代碼小結(jié):通過(guò)上面一系列的方法調(diào)用, performComplete -> batch —> performBatchComplete -> handleMessage -> complete 把BitmapHunter中獲取到的結(jié)果回調(diào)到主線程,并且顯示在Target上。
通過(guò)以上的8個(gè)步驟,就把圖片從加載到顯示的整個(gè)過(guò)程分析完了。
四,緩存特別說(shuō)明
內(nèi)存緩存很簡(jiǎn)單,用的是LRUCache,大小為 手機(jī)內(nèi)存的15% ,上面代碼中已經(jīng)分析過(guò)了,這里不過(guò)多說(shuō)明,這里重點(diǎn)說(shuō)一下Disk Cahce。Picasso內(nèi)存了2個(gè)默認(rèn)的下載器,UrlConnectionDownloader和OkHttpDownloader,它們的磁盤(pán)緩存實(shí)現(xiàn)還是有一些差異的,看一下代碼:
public OkHttpDownloader(final File cacheDir, final long maxSize) {this(defaultOkHttpClient());try {client.setCache(new com.squareup.okhttp.Cache(cacheDir, maxSize));} catch (IOException ignored) {}}復(fù)制代碼在OkHttpDownloader 的構(gòu)造方法里設(shè)置了磁盤(pán)緩存,使用的okHttp 的 DiskLruCache 實(shí)現(xiàn)的。
然后看一下UrlConnectionDownloader的磁盤(pán)緩存實(shí)現(xiàn),代碼:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {installCacheIfNeeded(context);}復(fù)制代碼private static void installCacheIfNeeded(Context context) {// DCL + volatile should be safe after Java 5.if (cache == null) {try {synchronized (lock) {if (cache == null) {cache = ResponseCacheIcs.install(context);}}} catch (IOException ignored) {}}}private static class ResponseCacheIcs {static Object install(Context context) throws IOException {File cacheDir = Utils.createDefaultCacheDir(context);HttpResponseCache cache = HttpResponseCache.getInstalled();if (cache == null) {long maxSize = Utils.calculateDiskCacheSize(cacheDir);cache = HttpResponseCache.install(cacheDir, maxSize);}return cache;}static void close(Object cache) {try {((HttpResponseCache) cache).close();} catch (IOException ignored) {}}}復(fù)制代碼UrlConnectionDownloader 的磁盤(pán)緩存是用HttpResponseCache實(shí)現(xiàn)的
盡管2種磁盤(pán)緩存實(shí)現(xiàn)的方式不一樣,但是它們的最后結(jié)果都是一樣的:
1,磁盤(pán)緩存的地址: 磁盤(pán)緩存的地址在:data/data/your package name/cache/picasso-cache /
2,磁盤(pán)緩存的大小:磁盤(pán)緩存的大小為 手機(jī)磁盤(pán)大小的2% ,不超過(guò)50M不小于5M。
3, 緩存的控制方式一樣:都是在請(qǐng)求的header設(shè)置Cache-Control的值來(lái)控制是否緩存。
緩存清除:
有同學(xué)在前一篇文章(圖片加載框架-Picasso最詳細(xì)的使用指南)下面留言問(wèn)怎么清除緩存,這里統(tǒng)一說(shuō)一下:
1, 清除內(nèi)存緩存:調(diào)用invalidate方法,如:
清除指定url 的內(nèi)存緩存。
但是Picasso沒(méi)有提供清除全部?jī)?nèi)存緩存的方法,那就沒(méi)有辦法了嗎?辦法還是有的,LRUCahce 提供了clear方法的,只是Picasso沒(méi)有向外部提供這個(gè)接口,因此可以通過(guò)反射獲取到Picasso的cache字段,然后調(diào)用clear方法清除。
2, 清除磁盤(pán)緩存
很遺憾Picasso沒(méi)有提供清除磁盤(pán)緩存的方法。它沒(méi)有提供方法我們就自己想辦法唄。
思路:很簡(jiǎn)單,既然我們知道磁盤(pán)緩存是存在:data/data/your package name/cache/picasso-cache 這個(gè)路徑下的,那我們把這個(gè)文件夾下面的所有文件清除不就行了。
實(shí)現(xiàn):
private void clearDiskCache(){File cache = new File(this.getApplicationContext().getCacheDir(), "picasso-cache");deleteFileOrDirectory(cache.getPath());}public static void deleteFileOrDirectory(String filePath){if(TextUtils.isEmpty(filePath)){return;}try {File file = new File(filePath);if(!file.exists()){return;}if(file.isDirectory()){File files[] = file.listFiles();for(int i=0;i<files.length;i++){deleteFileOrDirectory(files[i].getAbsolutePath());}}else{file.delete();Log.e("zhouwei","delete cache...");}}catch (Exception e){e.printStackTrace();}}復(fù)制代碼好了,就用上面一段代碼就可以實(shí)現(xiàn)刪除磁盤(pán)緩存了。
最后
以上就是對(duì)Picasso的源碼分析,代碼中的關(guān)鍵部分也有添加注釋,到此,Picasso的使用和源碼分析就講完了,還沒(méi)有看前一篇文章(圖片加載框架-Picasso最詳細(xì)的使用指南)的可以去看一下,如有問(wèn)題,歡迎留言交流。
總結(jié)
以上是生活随笔為你收集整理的图片加载框架Picasso - 源码分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Mac Generating Pods
- 下一篇: 如何为APK签名?