編輯:關於Android編程
對於android的圖片加載庫,之前用過UIL、volley、Picasso、Glide、Fresco,都是很好的圖片加載庫,之前對於Picasso這個庫研究的比較多一點,網絡上對於Picasso的源碼分析也很多,但是還是覺得自己從頭開始跟一下源碼,自己才會真正有收獲,所以就有了這篇博客。另外,關於Picasso的使用介紹,本篇先不做介紹,稍後會專門寫一篇使用詳情的博客。
本文可能比較長,有興趣的童鞋要耐心點看完~~( ̄▽ ̄)”
整體架構

Picasso中的核心類包括Picasso、Dispatcher、BitmapHunter、RequestHandler、Request、Action、Cache 等.Picasso類是一個負責圖片下載、變換、緩存的管理器,當它收到一個圖片下載請求的時候,它會創建Request並提交給Dispatcher, Dispatcher會尋找對應的處理器RequestHandler,並將請求與該處理器一起提交給線程池執行,圖片獲取成功後,最終會交給 PicassoDrawable顯示到Target上。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPtK71cXNvMasvNPU2L/J0tS31s6q0tTPwry4sr06PC9wPg0KPHByZSBjbGFzcz0="brush:java;"> 創建->入隊->執行->解碼->變換->批處理->完成->分發->顯示(可選)
先看下Picasso的最簡單用法
Picasso.with(this).load("url").into(imageView);
Picasso類是整個圖片加載器的入口,負責初始化各個模塊,配置相關參數等等。 使用了單例模式。
public static Picasso with(Context context) {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
singleton = new Builder(context).build();
}
}
}
return singleton;
}
維護一個Picasso的單例,如果還未實例化就通過new Builder(context).build()創建一個singleton並返回,我們繼續看Builder類的實現。
/** Create the {@link Picasso} instance. */
public Picasso build() {
Context context = this.context;
if (downloader == null) {
downloader = Utils.createDefaultDownloader(context);
}
if (cache == null) {
cache = new LruCache(context);
}
if (service == null) {
service = new PicassoExecutorService();
}
if (transformer == null) {
transformer = RequestTransformer.IDENTITY;
}
Stats stats = new Stats(cache);
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}
}
此方法做了如下基本配置:
1. 使用默認的緩存策略,內存緩存基於LruCache,磁盤緩存基於http緩存,HttpResponseCache
2. 創建默認的下載器
3. 創建默認的線程池(3個worker線程)
4. 創建默認的Transformer,這個Transformer什麼事情也不干,只負責轉發請求
5. 創建默認的監控器(Stats),用於統計緩存命中率、下載時長等等
6. 創建默認的處理器集合,即RequestHandlers.它們分別會處理不同的加載請求
7. Picasso的構造器
下面開始介紹builder初始化的一些內容。
如果用戶沒有自定義的話,那將使用默認downloader
Picasso#Builder#build()
downloader = Utils.createDefaultDownloader(context);
Utils#createDefaultDownloader
static Downloader createDefaultDownloader(Context context) {
try {
Class.forName("com.squareup.okhttp.OkHttpClient");
return OkHttpLoaderCreator.create(context);
} catch (ClassNotFoundException ignored) {
}
return new UrlConnectionDownloader(context);
}
首先反射下,看有沒有依賴okhttp,如果依賴的話,那就使用OkHttpClient喽,否則就使用默認的HttpUrlConnection了。
注:其實從4.4開始,okhttp已經作為HttpUrlConnection的實現引擎了。
Utils#createDefaultCacheDir
private static final String PICASSO_CACHE = "picasso-cache";
static File createDefaultCacheDir(Context context) {
File cache = new File(context.getApplicationContext().getCacheDir(), PICASSO_CACHE);
if (!cache.exists()) {
//noinspection ResultOfMethodCallIgnored
cache.mkdirs();
}
return cache;
}
通過getCacheDir方法獲取緩存路徑建立文件名為“picasso-cache”的Picasso的緩存文件。
緩存默認使用LRU算法,即least-recently used,近期最少使用算法。
使用可用內存堆的1/7(15%)作為圖片緩存。
static int calculateMemoryCacheSize(Context context) {
ActivityManager am = getService(context, ACTIVITY_SERVICE);
boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0;
int memoryClass = am.getMemoryClass();
if (largeHeap && SDK_INT >= HONEYCOMB) {
memoryClass = ActivityManagerHoneycomb.getLargeMemoryClass(am);
}
// Target ~15% of the available heap.
return 1024 * 1024 * memoryClass / 7;
}
PicassoExecutorService實現Picasso線程池,構造函數中實例化工作隊列和線程工廠。
默認的線程數是3條
private static final int DEFAULT_THREAD_COUNT = 3;
同時也可以根據不同網絡進行修改,wifi下是4個線程,4g下是3個,3g下是2個,而2g網只有一個線程,具體是通過在Dispatcher中注冊了監聽網絡變化的廣播接收者。(這個方法介紹dispatcher時候講)
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);
}
}
主要是對RequestCreator創建的Request進行轉換,默認對Request對象不做處理。
通過Stat標記緩存的狀態(命中數、未命中數、總大小、平均大小、下載次數等)
每一個Dispatcher都需要關聯線程池(service)、下載器(downloader)、主線程的Handler(HANDLER)、緩存(cache)、 監控器(stats).
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
關於dispatcher內同涉及到下面的知識點,所有dispatcher的講解會穿插在下面的內容中。
Picasso的構造方法裡除了對這些對象的賦值以及創建一些新的對象,例如清理線程等等.最重要的是初始化了requestHandlers
int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
List allRequestHandlers =
new ArrayList(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);
可以看到除了添加我們可以自定義的extraRequestHandlers,另外添加了7個RequestHandler分別用來處理加載不同來源的資源,可以是網絡、file、assert、contactsphoto等地方加載圖片.這裡使用了一個ArrayList來存放這些RequestHandler。
Picasso的load方法支持以下4種:
public RequestCreator load(Uri uri) {
return new RequestCreator(this, uri, 0);
}
public RequestCreator load(String path) {
if (path == null) {
return new RequestCreator(this, null, 0);
}
if (path.trim().length() == 0) {
throw new IllegalArgumentException("Path must not be empty.");
}
return load(Uri.parse(path));
}
public RequestCreator load(File file) {
if (file == null) {
return new RequestCreator(this, null, 0);
}
return load(Uri.fromFile(file));
}
public RequestCreator load(int resourceId) {
if (resourceId == 0) {
throw new IllegalArgumentException("Resource ID must not be zero.");
}
return new RequestCreator(this, null, resourceId);
}
在Picasso的load()方法裡我們可以傳入String,Uri或者File對象,但是其最終都是返回一個RequestCreator對象。
再來看看RequestCreator的構造方法:
RequestCreator(Picasso picasso, Uri uri, int resourceId) {
if (picasso.shutdown) {
throw new IllegalStateException(
"Picasso instance already shut down. Cannot submit new requests.");
}
this.picasso = picasso;
this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}
RequestCreator從名字就可以知道這是一個封裝請求的類,請求在Picasso中被抽象成Request。RequestCreator類提供了很多方法,比較常用的比如placeholder、error、tag、fit、resize、centerCrop、centerInside、rotate、fetch、transform等。
由於可配置項太多,所以Request也使用了Builder模式。
當然RequestCreator也提供了into這個最重要的方法。
into方法有多種重載,因為Picasso不僅僅可以將圖片加載到ImageView上,還可以加載到Target或者RemoteView上.
這裡選取imageView作為分析對象,該方法代碼如下:
public void into(ImageView target, Callback callback) {
long started = System.nanoTime();
//檢查是否在主線程中執行
/*
*用了這個判斷Looper.getMainLooper().getThread() ==Thread.currentThread()
*/
checkMain();
if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}
//檢查uri或者resID是否等於null
if (!data.hasImage()) {
//如果沒有設置當然取消請求
picasso.cancelRequest(target);
//是否需要設置placeholder
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
return;
}
//是否調用了fit()
if (deferred) {
if (data.hasSize()) {
throw new IllegalStateException("Fit cannot be used with resize.");
}
//既然要適應ImageView,肯定需要拿到ImageView大小
int width = target.getWidth();
int height = target.getHeight();
//如果圖片的寬高等於0,則用placeholder圖片
if (width == 0 || height == 0) {
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
picasso.defer(target, new DeferredRequestCreator(this, target, callback));
return;
}
data.resize(width, height);
}
//創建request
Request request = createRequest(started);
String requestKey = createKey(request);
//是否需要在緩存裡面先查找
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;
}
}
//無緩存,那就創建Action,將任務交給dispatcher
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
Action action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
picasso.enqueueAndSubmit(action);
}
注釋寫的很清楚了,into方法會先從緩存裡面查找圖片,如果找不到的話,則會創建Action即一個加載任務,交給Dispatcher執行。
那我們就來看看picasso.enqueueAndSubmit方法做了什麼。
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);
}
void submit(Action action) {
dispatcher.dispatchSubmit(action);
}
它會先從action任務上拿到對應target,也就是imageView,然後從weakHashMap中通過這個imageView索引到對應的action,如果 發現這個action跟傳進來的action不一樣的話,那就取消掉之前的加載任務。最後將當前加載任務submit。
submit的方法調用的是dispatcher的dispatchSubmit方法。這個dispatcher就是上文中在Picasso的Builder()裡面初始化的那個Dispatcher對象。
那又要回到Dispatcher這個類裡面看dispatchSubmit這個方法了。
void dispatchSubmit(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}
這裡是發了一個消息給Dispatcher的handler,這個handler是DispatcherHandler的對象,
this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
而dispatcherThread則是一個HandlerThread,從代碼中可以看出,這個handler的消息處理是在子線程進行的!這樣就可以避免阻塞主線程的消息隊列了!
好,接著上面的話題handler收到這個REQUEST_SUBMIT之後,調用了方法 dispatcher.performSubmit(action);
dispatcher.performSubmit
void performSubmit(Action action, boolean dismissFailed) {
//此任務是否被暫停
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,它繼承自Runnable,可以被線程池調用
BitmapHunter hunter = hunterMap.get(action.getKey());
if (hunter != null) {
hunter.attach(action);
return;
}
//線程池是否關閉
if (service.isShutdown()) {
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down");
}
return;
}
//還記得在Picasso的構造器中創建了若干RequestHandler嗎,
//在這裡,forRequest方法會遍歷這些requestHandler,看誰可以處理當前請求,
//如果發現了,那就創建BitmapHandler,並把這個requestHandler傳進去
hunter = forRequest(action.getPicasso(), this, cache, stats, action);
//通過service執行hunter並返回一個future對象
hunter.future = service.submit(hunter);
//將hunter添加到hunterMap中
hunterMap.put(action.getKey(), hunter);
if (dismissFailed) {
failedActions.remove(action.getTarget());
}
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
}
}
上面代碼裡面已經加過了注釋,但是forRequest這個方法還是要講一下。它依次調用requestHandlers裡RequestHandler的canHandleRequest()方法來確定這個request能被哪個RequestHandler執行,找到對應的RequestHandler後就創建BitmapHunter對象並返回.再回到performSubmit()方法裡,通過service.submit(hunter)執行了hunter,hunter實現了Runnable接口,所以run()方法就會被執行。
下面又要跟一下hunter的run方法
BitmapHunter的run()方法
@Override public void run() {
try {
updateThreadName(data);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
}
result = hunt();
if (result == null) {
dispatcher.dispatchFailed(this);
} else {
dispatcher.dispatchComplete(this);
}
} 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);
}
}
一堆catch語句分別捕捉不同的異常然後上報給dispatcher進行處理,主要代碼當然是 hunt()這個方法。
hunt()方法
Bitmap hunt() throws IOException {
Bitmap bitmap = null;
//依然先從緩存拿
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;
}
}
//緩存沒有的話,再調用requestHandler.load
data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
//拿到結果
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
if (result != null) {
loadedFrom = result.getLoadedFrom();
exifRotation = result.getExifOrientation();
//從結果中拿bitmap
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);
//如果需要圖片Transformation
if (data.needsTransformation() || exifRotation != 0) {
//這裡使用了一個全局鎖DECODE_LOCK來保證同一個時刻僅僅有一個圖片正在處理
synchronized (DECODE_LOCK) {
if (data.needsMatrixTransform() || exifRotation != 0) {
bitmap = transformResult(data, bitmap, exifRotation);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());
}
}
if (data.hasCustomTransformations()) {
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;
}
這個裡面要分析的當然是requestHandler的load方法了。還記得Picasso的構造方法裡面的那7中RequestHandler嗎?這裡的load方法也要看現在選擇的是那個RequestHandler對象。
這裡我們就拿網絡請求這個NetworkRequestHandler來作介紹。
RequestHandler的load方法
@Override public Result load(Request request, int networkPolicy) throws IOException {
//這個download一開始介紹過了,是否依賴okhttp
//如果依賴的話,那就使用OkHttpClient,否則就使用默認的HttpUrlConnection了
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);
}
好了,這裡已經獲取到結果了,現在我們再回到BitmapHunter的run()方法,在獲取到result之後,
result = hunt();
if (result == null) {
dispatcher.dispatchFailed(this);
} else {
dispatcher.dispatchComplete(this);
}
接下來是dispatcher裡面的方法調用了,dispatchComplete–>performComplete–>batch–>performBatchComplete–>發送信息給主線程(Picasso這個類)。
這裡有一點要注意的,就是performComplete這個函數裡面,對於load下來的文件,有一個寫入cache的操作。
if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
cache.set(hunter.getKey(), hunter.getResult());
}
主線程mainThreadHandler處理:
case HUNTER_BATCH_COMPLETE: {
@SuppressWarnings("unchecked") List batch = (List) msg.obj;
//noinspection ForLoopReplaceableByForEach
for (int i = 0, n = batch.size(); i < n; i++) {
BitmapHunter hunter = batch.get(i);
hunter.picasso.complete(hunter);
}
break;
}
下面的流程是這樣的:
hunter.picasso.complete(hunter)–>deliverAction–>action.complete(result, from);
這裡,如果是ImageView的話,那就是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;
PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
if (callback != null) {
callback.onSuccess();
}
}
圖片最終通過PicassoDrawable.setBitmap()方法被設置到ImageView上.
這個PicassoDrawable提供了fade動畫.
最終以一張時序圖收尾

文章同步到github:Picasso源碼分析
Android開發--利用Matrix進行圖片操作
今天和大家分享一下Android中Matrix的簡單用法,Matrix其實就是一個3*3的矩陣,利用這個矩陣對圖像操作。在Android中,為我們提供一些封裝好的方法可以
Android--數據庫操作輔助類:SQLiteOpenHelper
1.MyDatabaseHelper.java代碼如下: package org.lxh.demo; import android.content.Context; i
Android studio常用快捷鍵
ctrl+shift+N 查找文件,以懸浮窗口的形式搜索 contrl+N 查找類,與ctrl+shift+N相似,但是只能查找類 ctrl + E 最近打開的文件,可
Android 有效的解決內存洩漏的問題實例詳解
Android 有效的解決內存洩漏的問題Android內存洩漏,我想做Android 應用的時候遇到的話很是頭疼,這裡是我在網上找的不錯的資料,實例詳解這個問題的解決方案