欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入剖析Android的Volley庫(kù)中的圖片加載功能

 更新時(shí)間:2016年04月08日 14:27:20   作者:鴻洋_  
這篇文章主要介紹了Android的Volley框架中的圖片加載功能,從源碼剖析了Volley加載圖片時(shí)的請(qǐng)求隊(duì)列處理等方面,需要的朋友可以參考下

一、基本使用要點(diǎn)回顧

Volley框架在請(qǐng)求網(wǎng)絡(luò)圖片方面也做了很多工作,提供了好幾種方法.本文介紹使用ImageLoader來(lái)進(jìn)行網(wǎng)絡(luò)圖片的加載.
ImageLoader的內(nèi)部使用ImageRequest來(lái)實(shí)現(xiàn),它的構(gòu)造器可以傳入一個(gè)ImageCache緩存形參,實(shí)現(xiàn)了圖片緩存的功能,同時(shí)還可以過(guò)濾重復(fù)鏈接,避免重復(fù)發(fā)送請(qǐng)求。
下面是ImageLoader加載圖片的實(shí)現(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)建一個(gè)ImageListener實(shí)例后,在imageLoader.get()方法中加入此監(jiān)聽(tīng)器和圖片的url,即可加載網(wǎng)絡(luò)圖片.

下面是使用LruCache實(shí)現(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èn)網(wǎng)絡(luò)的權(quán)限

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

二、源碼分析
(一) 初始化Volley請(qǐng)求隊(duì)列

mReqQueue = Volley.newRequestQueue(mCtx);

主要就是這一行了:

#Volley

public 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,對(duì)于HttpStack在API大于等于9的時(shí)候選擇HttpUrlConnetcion,反之則選擇HttpClient,這里我們并不關(guān)注Http相關(guān)代碼。

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

接下來(lái)看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個(gè)參數(shù):mCache、mNetwork、mDispatchers、mDelivery。第一個(gè)是硬盤(pán)緩存;第二個(gè)主要用于Http相關(guān)操作;第三個(gè)用于轉(zhuǎn)發(fā)請(qǐng)求的;第四個(gè)參數(shù)用于把結(jié)果轉(zhuǎn)發(fā)到UI線程(ps:你可以看到new Handler(Looper.getMainLooper()))。

接下來(lái)看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ā)器退出,其實(shí)就是內(nèi)部的幾個(gè)線程退出,這里大家如果有興趣可以看眼源碼,參考下Volley中是怎么處理線程退出的(幾個(gè)線程都是while(true){//doSomething})。

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

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

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

(二) 初始化ImageLoader

#VolleyHelper
mImageLoader = 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);
   }
  });

#ImageLoader

public ImageLoader(RequestQueue queue, ImageCache imageCache) {
  mRequestQueue = queue;
  mCache = imageCache;
 }

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

(三) 加載圖片

我們?cè)诩虞d圖片時(shí),調(diào)用的是:

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

接下來(lái)看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方法,首先通過(guò)throwIfNotOnMainThread()方法限制必須在UI線程調(diào)用;

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

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

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

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

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

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

#RequestQueue
public <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;
  }
 }

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

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

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

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

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

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

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

(四)CacheDispatcher

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

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


這是一個(gè)線程,那么主要的代碼肯定在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,首先要明確這個(gè)緩存指的是硬盤(pán)緩存(目錄為context.getCacheDir()/volley),內(nèi)存緩存在ImageLoader那里已經(jīng)判斷過(guò)了。

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

接下來(lái)從緩存中獲?。?/p>

=>如果沒(méi)有取到,則加入mNetworkQueue

=>如果緩存過(guò)期,則加入mNetworkQueue

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

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

接下來(lái)我們看NetworkDispatcher。

(五)NetworkDispatcher

與CacheDispatcher類似,依然是個(gè)線程,核心代碼依然在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);
   }
  }
 }

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

那么看下是不是:

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

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

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

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

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

看起來(lái)還是蠻簡(jiǎn)單的,不過(guò)看完以后,對(duì)于如何更好的時(shí)候該庫(kù)以及如何去設(shè)計(jì)圖片加載庫(kù)還是有很大的幫助的;

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

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

201648142342794.jpg (1062×747)

相關(guān)文章

最新評(píng)論