国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Android > 正文

深入剖析Android的Volley庫中的圖片加載功能

2019-12-12 06:39:21
字體:
供稿:網(wǎng)友

一、基本使用要點回顧

Volley框架在請求網(wǎng)絡(luò)圖片方面也做了很多工作,提供了好幾種方法.本文介紹使用ImageLoader來進行網(wǎng)絡(luò)圖片的加載.
ImageLoader的內(nèi)部使用ImageRequest來實現(xiàn),它的構(gòu)造器可以傳入一個ImageCache緩存形參,實現(xiàn)了圖片緩存的功能,同時還可以過濾重復(fù)鏈接,避免重復(fù)發(fā)送請求。
下面是ImageLoader加載圖片的實現(xiàn)方法:

public void displayImg(View view){  ImageView imageView = (ImageView)this.findViewById(R.id.image_view);  RequestQueue mQueue = Volley.newRequestQueue(getApplicationContext());     ImageLoader imageLoader = new ImageLoader(mQueue, new BitmapCache());   ImageListener listener = ImageLoader.getImageListener(imageView,R.drawable.default_image, R.drawable.default_image);  imageLoader.get("http://developer.android.com/images/home/aw_dac.png", listener);  //指定圖片允許的最大寬度和高度  //imageLoader.get("http://developer.android.com/images/home/aw_dac.png",listener, 200, 200); } 

使用ImageLoader.getImageListener()方法創(chuàng)建一個ImageListener實例后,在imageLoader.get()方法中加入此監(jiān)聽器和圖片的url,即可加載網(wǎng)絡(luò)圖片.

下面是使用LruCache實現(xiàn)的緩存類

public class BitmapCache implements ImageCache {   private LruCache<String, Bitmap> cache;   public BitmapCache() {   cache = new LruCache<String, Bitmap>(8 * 1024 * 1024) {    @Override    protected int sizeOf(String key, Bitmap bitmap) {     return bitmap.getRowBytes() * bitmap.getHeight();    }   };  }   @Override  public Bitmap getBitmap(String url) {   return cache.get(url);  }   @Override  public void putBitmap(String url, Bitmap bitmap) {   cache.put(url, bitmap);  } } 

最后,別忘記在AndroidManifest.xml文件中加入訪問網(wǎng)絡(luò)的權(quán)限

<uses-permission android:name="android.permission.INTERNET"/> 

二、源碼分析
(一) 初始化Volley請求隊列

mReqQueue = Volley.newRequestQueue(mCtx);

主要就是這一行了:

#Volleypublic static RequestQueue newRequestQueue(Context context) {  return newRequestQueue(context, null); }public static RequestQueue newRequestQueue(Context context, HttpStack stack) {  return newRequestQueue(context, stack, -1); }public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {  File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);  String userAgent = "volley/0";  try {   String packageName = context.getPackageName();   PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);   userAgent = packageName + "/" + info.versionCode;  } catch (NameNotFoundException e) {  }  if (stack == null) {   if (Build.VERSION.SDK_INT >= 9) {    stack = new HurlStack();   } else {    // Prior to Gingerbread, HttpUrlConnection was unreliable.    // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html    stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));   }  }  Network network = new BasicNetwork(stack);  RequestQueue queue;  if (maxDiskCacheBytes <= -1)  {   // No maximum size specified   queue = new RequestQueue(new DiskBasedCache(cacheDir), network);  }  else  {   // Disk cache size specified   queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);  }  queue.start();  return queue; }

這里主要就是初始化HttpStack,對于HttpStack在API大于等于9的時候選擇HttpUrlConnetcion,反之則選擇HttpClient,這里我們并不關(guān)注Http相關(guān)代碼。

接下來初始化了RequestQueue,然后調(diào)用了start()方法。

接下來看RequestQueue的構(gòu)造:

public RequestQueue(Cache cache, Network network) {  this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE); }public RequestQueue(Cache cache, Network network, int threadPoolSize) {  this(cache, network, threadPoolSize,    new ExecutorDelivery(new Handler(Looper.getMainLooper()))); }public RequestQueue(Cache cache, Network network, int threadPoolSize,   ResponseDelivery delivery) {  mCache = cache;  mNetwork = network;  mDispatchers = new NetworkDispatcher[threadPoolSize];  mDelivery = delivery; }

初始化主要就是4個參數(shù):mCache、mNetwork、mDispatchers、mDelivery。第一個是硬盤緩存;第二個主要用于Http相關(guān)操作;第三個用于轉(zhuǎn)發(fā)請求的;第四個參數(shù)用于把結(jié)果轉(zhuǎn)發(fā)到UI線程(ps:你可以看到new Handler(Looper.getMainLooper()))。

接下來看start方法

#RequestQueue /**  * Starts the dispatchers in this queue.  */ public void start() {  stop(); // Make sure any currently running dispatchers are stopped.  // Create the cache dispatcher and start it.  mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);  mCacheDispatcher.start();  // Create network dispatchers (and corresponding threads) up to the pool size.  for (int i = 0; i < mDispatchers.length; i++) {   NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,     mCache, mDelivery);   mDispatchers[i] = networkDispatcher;   networkDispatcher.start();  } }

首先是stop,確保轉(zhuǎn)發(fā)器退出,其實就是內(nèi)部的幾個線程退出,這里大家如果有興趣可以看眼源碼,參考下Volley中是怎么處理線程退出的(幾個線程都是while(true){//doSomething})。

接下來初始化CacheDispatcher,然后調(diào)用start();初始化NetworkDispatcher,然后調(diào)用start();

上面的轉(zhuǎn)發(fā)器呢,都是線程,可以看到,這里開了幾個線程在幫助我們工作,具體的源碼,我們一會在看。

好了,到這里,就完成了Volley的初始化的相關(guān)代碼,那么接下來看初始化ImageLoader相關(guān)源碼。

(二) 初始化ImageLoader

#VolleyHelpermImageLoader = new ImageLoader(mReqQueue, new ImageCache()  {   private final LruCache<String, Bitmap> mLruCache = new LruCache<String, Bitmap>(     (int) (Runtime.getRuntime().maxMemory() / 10))   {    @Override    protected int sizeOf(String key, Bitmap value)    {     return value.getRowBytes() * value.getHeight();    }   };   @Override   public void putBitmap(String url, Bitmap bitmap)   {    mLruCache.put(url, bitmap);   }   @Override   public Bitmap getBitmap(String url)   {    return mLruCache.get(url);   }  });#ImageLoaderpublic ImageLoader(RequestQueue queue, ImageCache imageCache) {  mRequestQueue = queue;  mCache = imageCache; }

很簡單,就是根據(jù)我們初始化的RequestQueue和LruCache初始化了一個ImageLoader。

(三) 加載圖片

我們在加載圖片時,調(diào)用的是:

 # VolleyHelper getInstance().getImageLoader().get(url, new ImageLoader.ImageListener());

接下來看get方法:

#ImageLoader public ImageContainer get(String requestUrl, final ImageListener listener) {  return get(requestUrl, listener, 0, 0); }public ImageContainer get(String requestUrl, ImageListener imageListener,   int maxWidth, int maxHeight) {  return get(requestUrl, imageListener, maxWidth, maxHeight, ScaleType.CENTER_INSIDE); }public ImageContainer get(String requestUrl, ImageListener imageListener,   int maxWidth, int maxHeight, ScaleType scaleType) {  // only fulfill requests that were initiated from the main thread.  throwIfNotOnMainThread();  final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);  // Try to look up the request in the cache of remote images.  Bitmap cachedBitmap = mCache.getBitmap(cacheKey);  if (cachedBitmap != null) {   // Return the cached bitmap.   ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);   imageListener.onResponse(container, true);   return container;  }  // The bitmap did not exist in the cache, fetch it!  ImageContainer imageContainer =    new ImageContainer(null, requestUrl, cacheKey, imageListener);  // Update the caller to let them know that they should use the default bitmap.  imageListener.onResponse(imageContainer, true);  // Check to see if a request is already in-flight.  BatchedImageRequest request = mInFlightRequests.get(cacheKey);  if (request != null) {   // If it is, add this request to the list of listeners.   request.addContainer(imageContainer);   return imageContainer;  }  // The request is not already in flight. Send the new request to the network and  // track it.  Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType,    cacheKey);  mRequestQueue.add(newRequest);  mInFlightRequests.put(cacheKey,    new BatchedImageRequest(newRequest, imageContainer));  return imageContainer; }

可以看到get方法,首先通過throwIfNotOnMainThread()方法限制必須在UI線程調(diào)用;

然后根據(jù)傳入的參數(shù)計算cacheKey,獲取cache;

=>如果cache存在,直接將返回結(jié)果封裝為一個ImageContainer(cachedBitmap, requestUrl),然后直接回調(diào)imageListener.onResponse(container, true);我們就可以設(shè)置圖片了。

=>如果cache不存在,初始化一個ImageContainer(沒有bitmap),然后直接回調(diào),imageListener.onResponse(imageContainer, true);,這里為了讓大家在回調(diào)中判斷,然后設(shè)置默認(rèn)圖片(所以,大家在自己實現(xiàn)listener的時候,別忘了判斷resp.getBitmap()!=null);

接下來檢查該url是否早已加入了請求對了,如果早已加入呢,則將剛初始化的ImageContainer加入BatchedImageRequest,返回結(jié)束。

如果是一個新的請求,則通過makeImageRequest創(chuàng)建一個新的請求,然后將這個請求分別加入mRequestQueue和mInFlightRequests,注意mInFlightRequests中會初始化一個BatchedImageRequest,存儲相同的請求隊列。

這里注意mRequestQueue是個對象,并不是隊列數(shù)據(jù)結(jié)構(gòu),所以我們要看下add方法

#RequestQueuepublic <T> Request<T> add(Request<T> request) {  // Tag the request as belonging to this queue and add it to the set of current requests.  request.setRequestQueue(this);  synchronized (mCurrentRequests) {   mCurrentRequests.add(request);  }  // Process requests in the order they are added.  request.setSequence(getSequenceNumber());  request.addMarker("add-to-queue");  // If the request is uncacheable, skip the cache queue and go straight to the network.  if (!request.shouldCache()) {   mNetworkQueue.add(request);   return request;  }  // Insert request into stage if there's already a request with the same cache key in flight.  synchronized (mWaitingRequests) {   String cacheKey = request.getCacheKey();   if (mWaitingRequests.containsKey(cacheKey)) {    // There is already a request in flight. Queue up.    Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);    if (stagedRequests == null) {     stagedRequests = new LinkedList<Request<?>>();    }    stagedRequests.add(request);    mWaitingRequests.put(cacheKey, stagedRequests);    if (VolleyLog.DEBUG) {     VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);    }   } else {    // Insert 'null' queue for this cacheKey, indicating there is now a request in    // flight.    mWaitingRequests.put(cacheKey, null);    mCacheQueue.add(request);   }   return request;  } }

這里首先將請求加入mCurrentRequests,這個mCurrentRequests保存了所有需要處理的Request,主要為了提供cancel的入口。

如果該請求不應(yīng)該被緩存則直接加入mNetworkQueue,然后返回。

然后判斷該請求是否有相同的請求正在被處理,如果有則加入mWaitingRequests;如果沒有,則
加入mWaitingRequests.put(cacheKey, null)和mCacheQueue.add(request)。

ok,到這里我們就分析完成了直觀的代碼,但是你可能會覺得,那么到底是在哪里觸發(fā)的網(wǎng)絡(luò)請求,加載圖片呢?

那么,首先你應(yīng)該知道,我們需要加載圖片的時候,會makeImageRequest然后將這個請求加入到各種隊列,主要包含mCurrentRequests、mCacheQueue。

然后,還記得我們初始化RequestQueue的時候,啟動了幾個轉(zhuǎn)發(fā)線程嗎?CacheDispatcher和NetworkDispatcher。

其實,網(wǎng)絡(luò)請求就是在這幾個線程中真正去加載的,我們分別看一下;

(四)CacheDispatcher

看一眼構(gòu)造方法;

#CacheDispatcher public CacheDispatcher(   BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,   Cache cache, ResponseDelivery delivery) {  mCacheQueue = cacheQueue;  mNetworkQueue = networkQueue;  mCache = cache;  mDelivery = delivery; }


這是一個線程,那么主要的代碼肯定在run里面。

#CacheDispatcher @Override public void run() {  if (DEBUG) VolleyLog.v("start new dispatcher");  Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  // Make a blocking call to initialize the cache.  mCache.initialize();  while (true) {   try {    // Get a request from the cache triage queue, blocking until    // at least one is available.    final Request<?> request = mCacheQueue.take();    request.addMarker("cache-queue-take");    // If the request has been canceled, don't bother dispatching it.    if (request.isCanceled()) {     request.finish("cache-discard-canceled");     continue;    }    // Attempt to retrieve this item from cache.    Cache.Entry entry = mCache.get(request.getCacheKey());    if (entry == null) {     request.addMarker("cache-miss");     // Cache miss; send off to the network dispatcher.     mNetworkQueue.put(request);     continue;    }    // If it is completely expired, just send it to the network.    if (entry.isExpired()) {     request.addMarker("cache-hit-expired");     request.setCacheEntry(entry);     mNetworkQueue.put(request);     continue;    }    // We have a cache hit; parse its data for delivery back to the request.    request.addMarker("cache-hit");    Response<?> response = request.parseNetworkResponse(      new NetworkResponse(entry.data, entry.responseHeaders));    request.addMarker("cache-hit-parsed");    if (!entry.refreshNeeded()) {     // Completely unexpired cache hit. Just deliver the response.     mDelivery.postResponse(request, response);    } else {     // Soft-expired cache hit. We can deliver the cached response,     // but we need to also send the request to the network for     // refreshing.     request.addMarker("cache-hit-refresh-needed");     request.setCacheEntry(entry);     // Mark the response as intermediate.     response.intermediate = true;     // Post the intermediate response back to the user and have     // the delivery then forward the request along to the network.     mDelivery.postResponse(request, response, new Runnable() {      @Override      public void run() {       try {        mNetworkQueue.put(request);       } catch (InterruptedException e) {        // Not much we can do about this.       }      }     });    }   } catch (InterruptedException e) {    // We may have been interrupted because it was time to quit.    if (mQuit) {     return;    }    continue;   }  } }

ok,首先要明確這個緩存指的是硬盤緩存(目錄為context.getCacheDir()/volley),內(nèi)存緩存在ImageLoader那里已經(jīng)判斷過了。

可以看到這里是個無限循環(huán),不斷的從mCacheQueue去取出請求,如果請求已經(jīng)被取消就直接結(jié)束;

接下來從緩存中獲取:

=>如果沒有取到,則加入mNetworkQueue

=>如果緩存過期,則加入mNetworkQueue

否則,就是取到了可用的緩存了;調(diào)用request.parseNetworkResponse解析從緩存中取出的data和responseHeaders;接下來判斷TTL(主要還是判斷是否過期),如果沒有過期則直接通過mDelivery.postResponse轉(zhuǎn)發(fā),然后回調(diào)到UI線程;如果ttl不合法,回調(diào)完成后,還會將該請求加入mNetworkQueue。

好了,這里其實就是如果拿到合法的緩存,則直接轉(zhuǎn)發(fā)到UI線程;反之,則加入到NetworkQueue.

接下來我們看NetworkDispatcher。

(五)NetworkDispatcher

與CacheDispatcher類似,依然是個線程,核心代碼依然在run中;

# NetworkDispatcher//new NetworkDispatcher(mNetworkQueue, mNetwork,mCache, mDelivery)public NetworkDispatcher(BlockingQueue<Request<?>> queue,   Network network, Cache cache,   ResponseDelivery delivery) {  mQueue = queue;  mNetwork = network;  mCache = cache;  mDelivery = delivery; }@Override public void run() {  Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  while (true) {   long startTimeMs = SystemClock.elapsedRealtime();   Request<?> request;   try {    // Take a request from the queue.    request = mQueue.take();   } catch (InterruptedException e) {    // We may have been interrupted because it was time to quit.    if (mQuit) {     return;    }    continue;   }   try {    request.addMarker("network-queue-take");    // If the request was cancelled already, do not perform the    // network request.    if (request.isCanceled()) {     request.finish("network-discard-cancelled");     continue;    }    addTrafficStatsTag(request);    // Perform the network request.    NetworkResponse networkResponse = mNetwork.performRequest(request);    request.addMarker("network-http-complete");    // If the server returned 304 AND we delivered a response already,    // we're done -- don't deliver a second identical response.    if (networkResponse.notModified && request.hasHadResponseDelivered()) {     request.finish("not-modified");     continue;    }    // Parse the response here on the worker thread.    Response<?> response = request.parseNetworkResponse(networkResponse);    request.addMarker("network-parse-complete");    // Write to cache if applicable.    // TODO: Only update cache metadata instead of entire record for 304s.    if (request.shouldCache() && response.cacheEntry != null) {     mCache.put(request.getCacheKey(), response.cacheEntry);     request.addMarker("network-cache-written");    }    // Post the response back.    request.markDelivered();    mDelivery.postResponse(request, response);   } catch (VolleyError volleyError) {    volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);    parseAndDeliverNetworkError(request, volleyError);   } catch (Exception e) {    VolleyLog.e(e, "Unhandled exception %s", e.toString());    VolleyError volleyError = new VolleyError(e);    volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);    mDelivery.postError(request, volleyError);   }  } }

看代碼前,我們首先想一下邏輯,正常情況下我們會取出請求,讓network去請求處理我們的請求,處理完成以后呢:加入緩存,然后轉(zhuǎn)發(fā)。

那么看下是不是:

首先取出請求;然后通過mNetwork.performRequest(request)處理我們的請求,拿到NetworkResponse;接下來,使用request去解析我們的NetworkResponse。
拿到Response以后,判斷是否應(yīng)該緩存,如果需要,則緩存。

最后mDelivery.postResponse(request, response);轉(zhuǎn)發(fā);

ok,和我們的預(yù)期差不多。

這樣的話,我們的Volley去加載圖片的核心邏輯就分析完成了,簡單總結(jié)下:

首先初始化RequestQueue,主要就是開啟幾個Dispatcher線程,線程會不斷讀取請求(使用的阻塞隊列,沒有消息則阻塞)
當(dāng)我們發(fā)出請求以后,會根據(jù)url,ImageView屬性等,構(gòu)造出一個cacheKey,然后首先從LruCache中獲取(這個緩存我們自己構(gòu)建的,凡是實現(xiàn)ImageCache接口的都合法);如果沒有取到,則判斷是否存在硬盤緩存,這一步是從getCacheDir里面獲取(默認(rèn)5M);如果沒有取到,則從網(wǎng)絡(luò)請求;
不過,可以發(fā)現(xiàn)的是Volley的圖片加載,并沒有LIFO這種策略;貌似對于圖片的下載,也是完整的加到內(nèi)存,然后壓縮,這么看,對于巨圖、大文件這樣的就廢了;

看起來還是蠻簡單的,不過看完以后,對于如何更好的時候該庫以及如何去設(shè)計圖片加載庫還是有很大的幫助的;

如果有興趣,大家還可以在看源碼分析的同時,想想某些細(xì)節(jié)的實現(xiàn),比如:

Dispatcher都是一些無限循環(huán)的線程,可以去看看Volley如何保證其關(guān)閉的。
對于圖片壓縮的代碼,可以在ImageRequest的parseNetworkResponse里面去看看,是如何壓縮的。
so on…
最后貼個大概的流程圖,方便記憶:

201648142342794.jpg (1062×747)

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 广汉市| 新巴尔虎左旗| 城口县| 托克逊县| 四会市| 涞水县| 汝阳县| 公安县| 噶尔县| 徐闻县| 正阳县| 榆树市| 鱼台县| 邢台市| 上犹县| 定西市| 平利县| 唐海县| 枞阳县| 丹寨县| 精河县| 外汇| 秦皇岛市| 雅安市| 柘城县| 高阳县| 茂名市| 深泽县| 衡阳市| 东城区| 新蔡县| 报价| 朝阳县| 盐源县| 三亚市| 吴旗县| 安西县| 文昌市| 武邑县| 阆中市| 株洲县|