圖片加載框架Picasso - 源碼分析

前一篇文章講了Picasso的詳細(xì)用法,Picasso 是一個強(qiáng)大的圖片加載緩存框架,一個非常優(yōu)秀的開源庫,學(xué)習(xí)一個優(yōu)秀的開源庫,,我們不僅僅是學(xué)習(xí)它的用法,停留在使用API層面,我們也要試著去閱讀源碼,有兩個方面的原因,第一,熟悉了源碼我們才能更好的駕馭,項(xiàng)目中做我們需要的定制。第二,學(xué)習(xí)它的設(shè)計(jì)思想、編碼風(fēng)格、代碼的架構(gòu),然后在項(xiàng)目中對這些好的思想和架構(gòu)加以實(shí)踐,變成自己的知識,這樣才會對我們有更多的提升和幫助。這也是我們學(xué)習(xí)的目的,因此這篇文章對Picasso 的源碼和流程做一個分析。

一、Picasso 加載圖片流程圖

Picasso-圖片加載流程.png

上面就是Picasso加載圖片的流程,圖畫的丑,各位見諒。

二、重要的類介紹

(0)Picasso: 圖片加載、轉(zhuǎn)換、緩存的管理類。單列模式 ,通過with方法獲取實(shí)例,也是加載圖片的入口。
(1)RequestCreator: Request構(gòu)建類,Builder 模式,采用鏈?zhǔn)皆O(shè)置該Request的屬性(如占位圖、緩存策略、裁剪規(guī)則、顯示大小、優(yōu)先級等等)。最后調(diào)用build()方法生成一個請求(Request)。
(2)DeferredRequestCreator:RequestCreator的包裝類,當(dāng)創(chuàng)建請求的時(shí)候還不能獲取ImageView的寬和高的時(shí)候,則創(chuàng)建一個DeferredRequestCreator,DeferredRequestCreator里對 target 設(shè)置監(jiān)聽,直到可以獲取到寬和高的時(shí)候重新執(zhí)行請求創(chuàng)建。
(3) Action: 請求包裝類,存儲了該請求和RequestCreator設(shè)置的這些屬性,最終提交給線程執(zhí)行下載。
(4)Dispatcher:分發(fā)器,分發(fā)執(zhí)行各種請求、分發(fā)結(jié)果等等。
(5)PicassoExecutorService:Picasso使用的線程池,默認(rèn)池大小為3。
(6)LruCache:一個使用最近最少使用策略的內(nèi)存緩存。
(7)BitmapHunter:這是Picasso的一個核心的類,開啟線程執(zhí)行下載,獲取結(jié)果后解碼成Bitmap,然后做一些轉(zhuǎn)換操作如圖片旋轉(zhuǎn)、裁剪等,如果請求設(shè)置了轉(zhuǎn)換器Transformation,也會在BitmapHunter里執(zhí)行這些轉(zhuǎn)換操作。
(8)NetworkRequestHandler:網(wǎng)絡(luò)請求處理器,如果圖片需要從網(wǎng)絡(luò)下載,則用這個處理器處理。
(9)FileRequestHandler:文件請求處理器,如果請求的是一張存在文件中的圖片,則用這個處理器處理。
(10)AssetRequestHandler: Asset 資源圖片處理器,如果是加載asset目錄下的圖片,則用這個處理器處理。
(11)ResourceRequestHandler:Resource資源圖片處理器,如果是加載res下的圖片,則用這個處理器處理。
(12)ContentStreamRequestHandler: ContentProvider 處理器,如果是ContentProvider提供的圖片,則用這個處理器處理
(13)MediaStoreRequestHandler: MediaStore 請求處理器,如果圖片是存在MediaStore上的則用這個處理器處理。
(14)ContactsPhotoRequestHandler:ContactsPhoto 請求處理器,如果加載com.android.contacts/ 下的tu圖片用這個處理器處理。如:

// e.g. content://com.android.contacts/contacts/38)
//匹配的路徑如下:
static {
    matcher = new UriMatcher(UriMatcher.NO_MATCH);
    matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*/#", ID_LOOKUP);
    matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*", ID_LOOKUP);
    matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/photo", ID_THUMBNAIL);
    matcher.addURI(ContactsContract.AUTHORITY, "contacts/#", ID_CONTACT);
    matcher.addURI(ContactsContract.AUTHORITY, "display_photo/#", ID_DISPLAY_PHOTO);
  }

上面8-14 是默認(rèn)的提供的幾個處理器,分別處理不同來源的請求。

(15)Response: 返回的結(jié)果信息,Stream流或者Bitmap。
(16)Request: 請求實(shí)體類,存儲了應(yīng)用在圖片上的信息。
(17)Target:圖片加載的監(jiān)聽器接口,有3個回調(diào)方法,onPrepareLoad 在請求提交前回調(diào),onBitmapLoaded 請求成功回調(diào),并返回Bitmap,onBitmapFailed請求失敗回調(diào)。
(18)PicassoDrawable:繼承BitmapDrawable,實(shí)現(xiàn)了過渡動畫和圖片來源的標(biāo)識(就是圖片來源的指示器,要調(diào)用 setIndicatorsEnabled(true)方法才生效),請求成功后都會包裝成BitmapDrawable顯示到ImageView 上。
(19)OkHttpDownloader:用OkHttp實(shí)現(xiàn)的圖片下載器,默認(rèn)就是用的這個下載器。
(20)UrlConnectionDownloader:使用HttpURLConnection 實(shí)現(xiàn)的下載器。
(21)MemoryPolicy: 內(nèi)存緩存策略,一個枚舉類型。
(22)NetworkPolicy: 磁盤緩存策略,一個枚舉類型。
(23) Stats: 這個類相當(dāng)于日志記錄,會記錄如:內(nèi)存緩存的命中次數(shù),丟失次數(shù),下載次數(shù),轉(zhuǎn)換次數(shù)等等,我們可以通過StatsSnapshot類將日志打印出來,看一下整個項(xiàng)目的圖片加載情況。
(24)StatsSnapshot :狀態(tài)快照,和上面的Stats對應(yīng),打印Stats紀(jì)錄的信息。

以上就是Picasso 的一些關(guān)鍵的類的介紹(還有一些簡單的沒有列舉)。

三、流程分析

上一節(jié)介紹了Picasso 的一些關(guān)鍵類,接下來就以加載網(wǎng)絡(luò)圖片為例,分析Picasso加載圖片的整個流程

Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .into(mBlurImage);

1, 獲取Picasso instance

首先要獲取一個Picasso對象,采用的單例模式

//單例模式獲取Picasso 對象
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)的下載器,首先通過反射獲取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 線程池,核心池大小為3
        service = new PicassoExecutorService();
      }
      if (transformer == null) {
       // 配置請求轉(zhuǎn)換器,默認(rèn)的請求轉(zhuǎn)換器沒有做任何事,直接返回原請求
        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);
    }

2, 通過load方法生成一個RequestCreator

通過load方法生成一個RequestCreator,用鏈?zhǔn)絘pi 來構(gòu)建一個圖片下載請求

//load有幾個重載方法,參數(shù)為string和File 的重載最重都會包裝成一個Uri 調(diào)用這個方法
 public RequestCreator load(Uri uri) {
    return new RequestCreator(this, uri, 0);
  }
// 如果是加載資源id 的圖片會調(diào)用這個方法
 public RequestCreator load(int resourceId) {
    if (resourceId == 0) {
      throw new IllegalArgumentException("Resource ID must not be zero.");
    }
    return new RequestCreator(this, null, resourceId);
  }

RequestCreator提供了很多的API 來構(gòu)建請求,如展位圖、大小、轉(zhuǎn)換器、裁剪等等,這些API其實(shí)是為對應(yīng)的屬性賦值,最終會在into方法中構(gòu)建請求。

// 配置占位圖,在加載圖片的時(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;
  }

3,into 添加顯示的View,并且提交下載請求

into方法里面干了3件事情:

1, 判斷是否設(shè)置了fit 屬性,如果設(shè)置了,再看是否能夠獲取ImageView 的寬高,如果獲取不到,生成一個DeferredRequestCreator(延遲的請求管理器),然后直接return,在DeferredRequestCreator中當(dāng)監(jiān)聽到可以獲取ImageView 的寬高的時(shí)候,再執(zhí)行into方法。

2, 判斷是否從內(nèi)存緩存獲取圖片,如果沒有設(shè)置NO_CACHE,則從內(nèi)存獲取,命中直接回調(diào)CallBack 并且顯示圖片。

3, 如果緩存未命中,則生成一個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.");
    }
   //如果沒有url或者resourceId 則取消請求
    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());
        }
       //如果獲取不到寬高,生成一個DeferredRequestCreator(延遲的請求管理器),然后直接return,
      //在DeferredRequestCreator中當(dāng)監(jiān)聽到可以獲取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) {
       //緩存命中,取消請求,并顯示圖片
        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)存緩存獲取,則生成一個Action ,提交執(zhí)行。
    Action action =
        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
            errorDrawable, requestKey, tag, callback, noFade);

    picasso.enqueueAndSubmit(action);// 提交請求
  }

4, 提交、分發(fā)、執(zhí)行請求。

會經(jīng)過下面這一系列的操作,最重將Action 交給BitmapHunter 執(zhí)行。
enqueueAndSubmit -> submit -> dispatchSubmit -> performSubmit:

//將action 保存到了一個Map 中,目標(biāo)View作為key
 void enqueueAndSubmit(Action action) {
    Object target = action.getTarget();
    if (target != null && targetToAction.get(target) != action) {
      // This will also check we are on the main thread.
      cancelExistingRequest(target);
      targetToAction.put(target, action);
    }
    submit(action);
  }

// 交給分發(fā)器分發(fā)提交請求
void submit(Action action) {
    dispatcher.dispatchSubmit(action);
  }

 
//執(zhí)行請求提交
//1, 先查看保存暫停tag表里面沒有包含Action的tag,如果包含,則將Action 存到暫停Action表里
//2,從BitmapHunter表里查找有沒有對應(yīng)action的hunter,如果有直接attach
//3, 為這個請求生成一個BitmapHunter,提交給線程池執(zhí)行
 void performSubmit(Action action, boolean dismissFailed) {
    // 先查看保存暫停tag表里面沒有包含Action的tag,如果包含,則將Action 存到暫停Action表里
    if (pausedTags.contains(action.getTag())) {
      pausedActions.put(action.getTarget(), action);
      if (action.getPicasso().loggingEnabled) {
        log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
            "because tag '" + action.getTag() + "' is paused");
      }
      return;
    }

    BitmapHunter hunter = hunterMap.get(action.getKey());
    if (hunter != null) {
      hunter.attach(action);
      return;
    }
   // 如果線程池北shutDown,直接return
    if (service.isShutdown()) {
      if (action.getPicasso().loggingEnabled) {
        log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down");
      }
      return;
    }
    // 為請求生成一個BitmapHunter
    hunter = forRequest(action.getPicasso(), this, cache, stats, action);
    hunter.future = service.submit(hunter);//提交執(zhí)行
    hunterMap.put(action.getKey(), hunter);
    if (dismissFailed) {
      failedActions.remove(action.getTarget());
    }

    if (action.getPicasso().loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
    }
  }

5,指定對應(yīng)的處理器(RequestHandler)

在上面執(zhí)行的請求的performSubmit 方法里,調(diào)用了forRequest 方法為對應(yīng)的Action 生成一個BitmapHunter,里面有一個重要的步驟,指定請求處理器(在上面一節(jié)介紹Picasso有7種請求處理器,看一下對應(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 ForLoopReplaceableByForEach
    for (int i = 0, count = requestHandlers.size(); i < count; i++) {
      RequestHandler requestHandler = requestHandlers.get(i);
     //  循環(huán)請求處理器列表,如果找到有能處理這個請求的請求處理器
     // 則生成BitmapHunter
      if (requestHandler.canHandleRequest(request)) {
        return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
      }
    }

    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
  }

從Picasso里獲取一個處理器列表,然后循環(huán)列表,看是否有能處理該請求的處理器,如果有,則生成BitmapHunter,那么這個請求處理器的列表在哪兒初始化的呢?請看源碼:

// 1,首先調(diào)用了getRequestHandlers
 List<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個內(nèi)置的請求處理器
  // 如果你自己通過Builder添了額外的處理器,也會添加在這個列表里面

    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);
   
  //后面代碼省略 
    ...

  }

小結(jié): 在Picasso 的構(gòu)造函數(shù)里 初始化了內(nèi)置的7中請求處理器,然后在生成BitmapHunter的時(shí)候,循環(huán)列表,找到可以處理對應(yīng)請求的處理器。

6, 重點(diǎn):BitmapHunter (圖片捕獲器)

上一節(jié)重要類介紹的時(shí)候介紹過BitmapHunter,BitmapHunter繼承Runnable,其實(shí)就是開啟一個線程執(zhí)行最終的下載??匆幌略创a:
1, run() 方法

 @Override public void run() {
    try {
      updateThreadName(data);

      if (picasso.loggingEnabled) {
        log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
      }
     //  調(diào)用hunt() 方法獲取最終結(jié)果
      result = hunt();

      if (result == null) {
        dispatcher.dispatchFailed(this);//如果為null,分發(fā)失敗的消息
      } else {
        dispatcher.dispatchComplete(this);//如果不為null,分發(fā)成功的消息
      }
    } catch (Downloader.ResponseException e) {
      if (!e.localCacheOnly || e.responseCode != 504) {
        exception = e;
      }
      dispatcher.dispatchFailed(this);
    } catch (NetworkRequestHandler.ContentLengthException e) {
      exception = e;
      dispatcher.dispatchRetry(this);
    } catch (IOException e) {
      exception = e;
      dispatcher.dispatchRetry(this);
    } catch (OutOfMemoryError e) {
      StringWriter writer = new StringWriter();
      stats.createSnapshot().dump(new PrintWriter(writer));
      exception = new RuntimeException(writer.toString(), e);
      dispatcher.dispatchFailed(this);
    } catch (Exception e) {
      exception = e;
      dispatcher.dispatchFailed(this);
    } finally {
      Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);
    }
  }

當(dāng)將一個bitmapHunter submit 給一個線程池執(zhí)行的時(shí)候,就會執(zhí)行run() 方法,run里面調(diào)用的是hunt方法來獲取結(jié)果,看一下hunt方法:

 Bitmap hunt() throws IOException {
    Bitmap bitmap = null;
   // 是否從內(nèi)存緩存獲取Bitmap
    if (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;
   // 請求處理器處理請求,獲取結(jié)果,Result里可能是Bitmap,可能是Stream
    RequestHandler.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;
  }

7,Downloader 下載器下載圖片

上面的hunt方法獲取結(jié)果的時(shí)候,最終調(diào)用的是配置的處理器的load方法,如下:

RequestHandler.Result result = requestHandler.load(data, networkPolicy);

加載網(wǎng)絡(luò)圖片用的是NetworkRequestHandler,匹配處理器,有個canHandleRequest 方法:

 @Override public boolean canHandleRequest(Request data) {
    String scheme = data.uri.getScheme();
    return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
  }

判斷的條件是,Uri帶有"http://" 或者 https:// 前綴則可以處理

我們接下來看一下NetworkRequestHandler的load方法:

 @Override 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);
  }

NetworkRequestHandler最終是調(diào)用的downloader 的load方法下載圖片。內(nèi)置了2個Downloader,OkhttpDownloader和UrlConnectionDownloader 。我們以UrlConnectionDownloader為例,來看一下load方法:

 @Override public Response load(Uri uri, int networkPolicy) throws IOException {
   // 如果SDK 版本大于等于14,安裝磁盤緩存,用的是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);
  }

小結(jié):梳理一下調(diào)用鏈, BitmapHunter -> NetworkRequestHandler -> UrlConnectionDownloader(也有可能是OkHttpDownloader),經(jīng)過這一系列的調(diào)用,最后在BitmapHunter 的run 方法中就可以獲取到我們最終要的Bitmap。

8,返回結(jié)果并顯示在Target上

在BitmapHunter獲取結(jié)果后,分發(fā)器分發(fā)結(jié)果,通過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());// 請求完畢,將hunter從表中移除
    batch(hunter);
    if (hunter.getPicasso().loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), "for completion");
    }
  }

// 2,然后將BitmapHunter添加到一個批處理列表,通過Hander發(fā)送一個批處理消息
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 方法,通過主線程的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()) {
    @Override public void handleMessage(Message msg) {
      switch (msg.what) {
        case HUNTER_BATCH_COMPLETE: {
          @SuppressWarnings("unchecked") List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
          //noinspection ForLoopReplaceableByForEach
          for (int i = 0, n = batch.size(); i < n; i++) {
            BitmapHunter hunter = batch.get(i);
            hunter.picasso.complete(hunter);
          }
          break;
        }
      //后面代碼省略
      ...
  };
// 5,最后回調(diào)到ImageViewAction 的complete方法顯示圖片
@Override 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é)果包裝成一個PicassoDrawable 并顯示
    PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);

    if (callback != null) {
      callback.onSuccess(); 回調(diào)callback
    }
  }

小結(jié):通過上面一系列的方法調(diào)用, performComplete -> batch —> performBatchComplete -> handleMessage -> complete 把BitmapHunter中獲取到的結(jié)果回調(diào)到主線程,并且顯示在Target上。

通過以上的8個步驟,就把圖片從加載到顯示的整個過程分析完了。

四,緩存特別說明

內(nèi)存緩存很簡單,用的是LRUCache,大小為 手機(jī)內(nèi)存的15% ,上面代碼中已經(jīng)分析過了,這里不過多說明,這里重點(diǎn)說一下Disk Cahce。Picasso內(nèi)存了2個默認(rèn)的下載器,UrlConnectionDownloader和OkHttpDownloader,它們的磁盤緩存實(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) {
    }
  }

在OkHttpDownloader 的構(gòu)造方法里設(shè)置了磁盤緩存,使用的okHttp 的 DiskLruCache 實(shí)現(xiàn)的。

然后看一下UrlConnectionDownloader的磁盤緩存實(shí)現(xiàn),代碼:

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
      installCacheIfNeeded(context);
    }
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) {
      }
    }
  }

** UrlConnectionDownloader 的磁盤緩存是用HttpResponseCache實(shí)現(xiàn)的**

盡管2種磁盤緩存實(shí)現(xiàn)的方式不一樣,但是它們的最后結(jié)果都是一樣的:

1,磁盤緩存的地址: 磁盤緩存的地址在:data/data/your package name/cache/picasso-cache /
2,磁盤緩存的大?。?/strong>磁盤緩存的大小為 手機(jī)磁盤大小的2% ,不超過50M不小于5M。
3, 緩存的控制方式一樣:都是在請求的header設(shè)置Cache-Control的值來控制是否緩存。

緩存清除:
有同學(xué)在前一篇文章(圖片加載框架-Picasso最詳細(xì)的使用指南)下面留言問怎么清除緩存,這里統(tǒng)一說一下:
1, 清除內(nèi)存緩存:調(diào)用invalidate方法,如:

 Picasso.with(this)
                .invalidate("http://ww3.sinaimg.cn/large/610dc034jw1fasakfvqe1j20u00mhgn2.jpg");

清除指定url 的內(nèi)存緩存。

但是Picasso沒有提供清除全部內(nèi)存緩存的方法,那就沒有辦法了嗎?辦法還是有的,LRUCahce 提供了clear方法的,只是Picasso沒有向外部提供這個接口,因此可以通過反射獲取到Picasso的cache字段,然后調(diào)用clear方法清除。

2, 清除磁盤緩存
很遺憾Picasso沒有提供清除磁盤緩存的方法。它沒有提供方法我們就自己想辦法唄。

思路:很簡單,既然我們知道磁盤緩存是存在:data/data/your package name/cache/picasso-cache 這個路徑下的,那我們把這個文件夾下面的所有文件清除不就行了。

實(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();
        }

    }

好了,就用上面一段代碼就可以實(shí)現(xiàn)刪除磁盤緩存了。

最后

以上就是對Picasso的源碼分析,代碼中的關(guān)鍵部分也有添加注釋,到此,Picasso的使用和源碼分析就講完了,還沒有看前一篇文章(圖片加載框架-Picasso最詳細(xì)的使用指南)的可以去看一下,如有問題,歡迎留言交流。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容