Picasso原始碼分析(二):預設的下載器、快取、執行緒池和轉換器
Picasso原始碼分析(一):單例模式、建造者模式、面向介面程式設計
Picasso原始碼分析(二):預設的下載器、快取、執行緒池和轉換器
Picasso原始碼分析(三):快照功能實現和HandlerThread的使用
Picasso原始碼分析(四):不變模式、建造者模式和Request的預處理
Picasso原始碼分析(五):into方法追本溯源和責任鏈模式建立BitmapHunter
Picasso原始碼分析(六):BitmapHunter與請求結果的處理
下載器
當使用者沒有為Picasso指定下載器的時候Picasso會通過Utils.createDefaultDownloader(context)方法建立一個預設的下載器
static Downloader createDefaultDownloader(Context context) {
try {
Class.forName("com.squareup.okhttp.OkHttpClient");
return OkHttpLoaderCreator.create(context);
} catch (ClassNotFoundException ignored) {
}
return new UrlConnectionDownloader(context);
}
可見如果反射發現應用已經整合了okhttp,那麼使用okhttp建立一個下載器,否則使用HttpURLConnection建立下載器。兩種方式建立的下載器需要實現Downloader介面,使用者也可以實現自己的下載器,實現Downloader介面即可。下邊只分析OkHttpDownloader,UrlConnectionDownloader道理類似。
@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);
}
com.squareup.okhttp.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());
}
由於OkHttpDownloader實現了Downloader介面,因此終點關注覆寫DownLoader的兩個方法。
load方法的註釋如下:
/**
* Download the specified image url from the internet.
*
* @param uri Remote image URL.
* @param networkPolicy The NetworkPolicy used for this request.
* @return Response containing either a Bitmap representation of the request or an
* InputStream for the image data. null can be returned to indicate a problem
* loading the bitmap.
* @throws IOException if the requested URL cannot successfully be loaded.
*/
Response load(Uri uri, int networkPolicy) throws IOException;
load方法傳入圖片的url和網路策略,返回一個請求到的Bitmap或者InputStream,出錯就返回null。
接下來是一點我覺得有一點小瑕疵,以為出現 了魔數0,方法呼叫者傳入0表示沒有設定快取策略
if (networkPolicy != 0) {
...
}
這乍一看還真弄不明白0表示什麼意思,牽扯到列舉和數字的轉換
/** Designates the policy to use for network requests. */
public enum NetworkPolicy {
/** Skips checking the disk cache and forces loading through the network. */
NO_CACHE(1 << 0),
/**
* Skips storing the result into the disk cache.
* Note: At this time this is only supported if you are using OkHttp.
*/
NO_STORE(1 << 1),
/** Forces the request through the disk cache only, skipping network. */
OFFLINE(1 << 2);
...
原來NetworkPolicy是一個列舉類,1表示不快取,強制從網路獲取圖片,2表示不儲存快取,4表示不做網路請求強制讀快取。
所以如果load方法如果傳入的networkPolicy不是0,那麼就解析該networkPolicy對應的NetworkPolicy,根據策略不同做不同的處理。
isOfflineOnly方法通過位運算判斷網路策略是否為離線模式
public static boolean shouldReadFromDiskCache(int networkPolicy) {
return (networkPolicy & NetworkPolicy.NO_CACHE.index) == 0;
}
public static boolean shouldWriteToDiskCache(int networkPolicy) {
return (networkPolicy & NetworkPolicy.NO_STORE.index) == 0;
}
public static boolean isOfflineOnly(int networkPolicy) {
return (networkPolicy & NetworkPolicy.OFFLINE.index) != 0;
}
同理shouldReadFromDiskCache方法判斷是否可以讀寫快取,shouldWriteToDiskCache方法判斷是否可以儲存快取。
如果為離線模式就設定cacheControl為CacheControl.FORCE_CACHE,表示強制okhttp讀取快取。CacheControl為okhttp的快取控制類。
否則的話通過建造者模式構建CacheControl物件
CacheControl.Builder builder = new CacheControl.Builder();
然後設定相應的快取策略,最終通過CacheControl的內部類Builder的build方法建立CacheControl物件。
接著還是通過建造者模式建立okhttp的Request物件,先通過Reqest的內部類Builder構造一個builder,然後給builder設定相應的屬性
Request.Builder builder = new Request.Builder().url(uri.toString());
if (cacheControl != null) {
builder.cacheControl(cacheControl);
}
最後呼叫builder的build方法就獲取到一個Request物件
有了Request,就可以通過okhttp的newCall方法進行非同步網路請求
com.squareup.okhttp.Response response = client.newCall(builder.build()).execute();
這樣獲取到了okhttp的Response,需要判斷此Response的請求狀態碼是否大於300,200表示請求正常,大於300表示請求出現問題,比如常見的狀態嗎及表達的意思如下:
300表示 伺服器根據請求可執行多種操作
301表示永久重定向
302表示臨時重定向
400表示請求語義或者引數有誤,伺服器不能理解
403表示伺服器理解請求但是拒絕執行
404表示請求的資源在伺服器上沒有找到
5XX表示伺服器出現了問題
接下來需要將okhttp的Response轉化為Downloader的Response
boolean fromCache = response.cacheResponse() != null;
ResponseBody responseBody = response.body();
return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength());
OkHttpDownloader覆寫Downloader的shutdown方法比較簡單,關閉okhttp的快取即可
@Override
public void shutdown() {
com.squareup.okhttp.Cache cache = client.getCache();
if (cache != null) {
try {
cache.close();
} catch (IOException ignored) {
}
}
}
快取
如果使用者沒有為Picasso設定快取的話,Picasso會預設建立一個LruCache快取
if (cache == null) {
cache = new LruCache(context);
}
而LruCache實際上是一個通過LinkedHashMap實現的記憶體快取,實現了Cache介面
/** A memory cache which uses a least-recently used eviction policy. */
public class LruCache implements Cache {
final LinkedHashMap<String, Bitmap> map;
...
LruCache還有一些其他的統計量屬性,如下,良好的命名規則讓讀者顧名思義
private final int maxSize;
private int size;
private int putCount;
private int evictionCount;
private int hitCount;
private int missCount;
由於java集合框架提供的LinkedHashMap可以按照LRU最近最少使用原則維護列表的訪問順序,因此天然適合做Lru快取。只需要將LinkedHashMap建構函式的第二個引數傳遞為true接可以按照訪問順序而不是插入順序維護列表的元素了。
/**
* ...
* @param accessOrder
* true if the ordering should be done based on the last
* access (from least-recently accessed to most-recently
* accessed), and false if the ordering should be the
* order in which the entries were inserted.
* ...
*/
this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);
所以LruCache只需實現很少的程式碼就可以了。以get和set方法為例分析。
@Override
public Bitmap get(String key) {
if (key == null) {
throw new NullPointerException("key == null");
}
Bitmap mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
return null;
}
LruCache不支援null的鍵,所以需要首先做引數合法性檢查
接著同步鎖定LruCache物件,從LinkedHashMap中獲取對應key的值,獲取成功增加hitCount,返回value,否則增加missCount,返回null。
@Override
public void set(String key, Bitmap bitmap) {
if (key == null || bitmap == null) {
throw new NullPointerException("key == null || bitmap == null");
}
Bitmap previous;
synchronized (this) {
putCount++;
size += Utils.getBitmapBytes(bitmap);
previous = map.put(key, bitmap);
if (previous != null) {
size -= Utils.getBitmapBytes(previous);
}
}
trimToSize(maxSize);
}
LruCache不支援null的鍵和null的值,因此set方法首先檢查傳入引數的合法性
接著同樣的同步鎖定LruCache物件,增加putCount和size
putCount++;
size += Utils.getBitmapBytes(bitmap);
可見size表示的並不是LinkedHashMap中儲存鍵值對的個數,而是所有bitmap快取佔據的儲存空間的大小
接著獲取舊的快取,如果之前儲存的有對應key的舊的快取,那麼因為快取替換的原因,需要減去舊快取佔據的儲存空間
if (previous != null) {
size -= Utils.getBitmapBytes(previous);
}
不管是新增快取還是替換快取,都改變了儲存空間的大小
所以需要重新調整
private void trimToSize(int maxSize) {
while (true) {
String key;
Bitmap value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(
getClass().getName() + ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize || map.isEmpty()) {
break;
}
Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= Utils.getBitmapBytes(value);
evictionCount++;
}
}
}
調整大小的思路也比較簡單,只要size大於了maxSize,就不停的根據LRU原則刪除最近最少使用的快取。
Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= Utils.getBitmapBytes(value);
evictionCount++;
直到size不大於maxSize或者LinkedHashMap物件空了就不需要繼續刪除快取了。
if (size <= maxSize || map.isEmpty()) {
break;
}
執行緒池
Picasso提供了一個預設的執行緒池
if (service == null) {
service = new PicassoExecutorService();
}
PicassoExecutorService繼承自ThreadPoolExecutor,定製了一個執行緒池
class PicassoExecutorService extends ThreadPoolExecutor {
private static final int DEFAULT_THREAD_COUNT = 3;
PicassoExecutorService() {
super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,
new PriorityBlockingQueue<Runnable>(), new Utils.PicassoThreadFactory());
}
....
核心執行緒數和最大執行緒數都是3,超時設定為0,所以超時單位無意義,設定為毫秒,阻塞佇列設定為一個優先順序佇列,傳入自定義的一個執行緒工廠。ThreadPoolExecutor建構函式引數比較多,每個引數的意義如下注釋所示。
/**
* Creates a new ThreadPoolExecutor with the given initial
* parameters and default thread factory and rejected execution handler.
* It may be more convenient to use one of the Executors factory
* methods instead of this general purpose constructor.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless allowCoreThreadTimeOut is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the keepAliveTime argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the Runnable
* tasks submitted by the execute method.
* @throws IllegalArgumentException if one of the following holds:
* corePoolSize < 0
* keepAliveTime < 0
* maximumPoolSize <= 0
* maximumPoolSize < corePoolSize
* @throws NullPointerException if workQueue is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
PicassoExecutorService設計最好的地方在於可以根據不同的網路情況設定不同的執行緒數,這也是解決弱網路的一個思路,網路好的情況下建立較多的請求執行緒,提高併發度,網路差的時候設定較少的請求執行緒節約資源。
void adjustThreadCount(NetworkInfo info) {
if (info == null || !info.isConnectedOrConnecting()) {
setThreadCount(DEFAULT_THREAD_COUNT);
return;
}
switch (info.getType()) {
case ConnectivityManager.TYPE_WIFI:
case ConnectivityManager.TYPE_WIMAX:
case ConnectivityManager.TYPE_ETHERNET:
setThreadCount(4);
break;
case ConnectivityManager.TYPE_MOBILE:
switch (info.getSubtype()) {
case TelephonyManager.NETWORK_TYPE_LTE: // 4G
case TelephonyManager.NETWORK_TYPE_HSPAP:
case TelephonyManager.NETWORK_TYPE_EHRPD:
setThreadCount(3);
break;
case TelephonyManager.NETWORK_TYPE_UMTS: // 3G
case TelephonyManager.NETWORK_TYPE_CDMA:
case TelephonyManager.NETWORK_TYPE_EVDO_0:
case TelephonyManager.NETWORK_TYPE_EVDO_A:
case TelephonyManager.NETWORK_TYPE_EVDO_B:
setThreadCount(2);
break;
case TelephonyManager.NETWORK_TYPE_GPRS: // 2G
case TelephonyManager.NETWORK_TYPE_EDGE:
setThreadCount(1);
break;
default:
setThreadCount(DEFAULT_THREAD_COUNT);
}
break;
default:
setThreadCount(DEFAULT_THREAD_COUNT);
}
}
可以看到在wifi網路連線的情況下設定併發執行緒數為4,4G網路併發執行緒數為3,3G網路併發執行緒數為2,2G網路併發執行緒數為1。
setThreadCount方法實際上呼叫了ThreadPoolService的setCorePoolSize方法和setMaxinumPoolSize方法
private void setThreadCount(int threadCount) {
setCorePoolSize(threadCount);
setMaximumPoolSize(threadCount);
}
轉換器
Picasso提供的預設的轉換器實際上什麼也沒有做,因此需要改變圖片大小等操作需要專門處理,先看看預設的實現。
if (transformer == null) {
transformer = RequestTransformer.IDENTITY;
}
轉換器就是一個請求被提交前對請求進行的轉換處理,可以在提交請求之前對該請求進行一些變換處理操作。
/**
* A transformer that is called immediately before every request is submitted. This can be used to
* modify any information about a request.
*/
public interface RequestTransformer {
/**
* Transform a request before it is submitted to be processed.
* @return The original request or a new request to replace it. Must not be null.
*/
Request transformRequest(Request request);
/** A RequestTransformer which returns the original request. */
RequestTransformer IDENTITY = new RequestTransformer() {
@Override public Request transformRequest(Request request) {
return request;
}
};
}
可見IDENTITY直接返回了原來的request,並沒有做額外的變換處理。
相關文章
- 執行緒池原始碼分析執行緒原始碼
- dubbo原始碼-執行緒池分析原始碼執行緒
- 執行緒池之ThreadPoolExecutor執行緒池原始碼分析筆記執行緒thread原始碼筆記
- 執行緒池之ScheduledThreadPoolExecutor執行緒池原始碼分析筆記執行緒thread原始碼筆記
- netty原始碼分析-執行緒池Netty原始碼執行緒
- 執行緒池的建立和使用,執行緒池原始碼初探(篇一)執行緒原始碼
- 詳解Java執行緒池的ctl(執行緒池控制狀態)【原始碼分析】Java執行緒原始碼
- Java 併發:執行緒、執行緒池和執行器全面教程Java執行緒
- Java中命名執行器服務執行緒和執行緒池Java執行緒
- 執行緒池原始碼探究執行緒原始碼
- Android高併發問題處理和執行緒池ThreadPool執行緒池原始碼分析Android執行緒thread原始碼
- Python執行緒池ThreadPoolExecutor原始碼分析Python執行緒thread原始碼
- jdk1.8 執行緒池部分原始碼分析JDK執行緒原始碼
- slab原始碼分析--快取器的建立原始碼快取
- RxJava原始碼解析(二)—執行緒排程器SchedulerRxJava原始碼執行緒
- Java執行緒池二:執行緒池原理Java執行緒
- 熔斷器 Hystrix 原始碼解析 —— 執行結果快取原始碼快取
- JAVA併發程式設計:執行緒池ThreadPoolExecutor原始碼分析Java程式設計執行緒thread原始碼
- JUC(4)---java執行緒池原理及原始碼分析Java執行緒原始碼
- Java排程執行緒池ScheduledThreadPoolExecutor原始碼分析Java執行緒thread原始碼
- 原始碼|從序列執行緒封閉到物件池、執行緒池原始碼執行緒物件
- JDK執行緒池原始碼研究JDK執行緒原始碼
- 執行緒池執行模型原始碼全解析執行緒模型原始碼
- [10]elasticsearch原始碼深入分析——執行緒池的封裝Elasticsearch原始碼執行緒封裝
- Netty原始碼解析一——執行緒池模型之執行緒池NioEventLoopGroupNetty原始碼執行緒模型OOP
- 執行緒和執行緒池執行緒
- 從原始碼的角度解析執行緒池執行原理原始碼執行緒
- mybatis原始碼學習:一級快取和二級快取分析MyBatis原始碼快取
- 使用 Executors,ThreadPoolExecutor,建立執行緒池,原始碼分析理解thread執行緒原始碼
- 探討阻塞佇列和執行緒池原始碼佇列執行緒原始碼
- 二. 執行緒管理之執行緒池執行緒
- 執行緒池的實現程式碼分析執行緒
- Java執行緒池原始碼及原理Java執行緒原始碼
- java執行緒池原始碼一窺Java執行緒原始碼
- YYWebImage 原始碼剖析:執行緒處理與快取策略Web原始碼執行緒快取
- 多執行緒程式設計基礎(二)-- 執行緒池的使用執行緒程式設計
- Mybatis的快取——一級快取和原始碼分析MyBatis快取原始碼
- 圖片載入框架Picasso - 原始碼分析框架原始碼