Volley該框架使用了大量的請求圖片

尊重原創 http://write.blog.csdn.net/postedit/26142025
html

代碼下載:http://download.csdn.net/detail/yuanzeyao2008/7363999java


假設你但願瞭解Volley框架的原理,歡迎閱讀android

volley源代碼框架淺析(一)ios

volley源代碼框架淺析(二)
算法

volley源代碼框架淺析(三)
緩存


前面已經說過,Volley框架可以用來請求String,XML,Json,Image以及一些本身定義的類型,這篇文章主要解說使用Volley請求大量圖片。並使用GridView展現出來。這個功能在很是多應用中都會用到,如PPS等類型的播放器,淘寶等等。框架

像這類應用無非就是要解決一下問題:ide

一、避免OOM,在使用GridView等控件顯示大量圖片時,假設對緩存處理不當,是很easy出現OOM的。
二、GridView錯位顯示,比方GridView中的某個ImageView等待某一個圖片(沒有返回)。然而你此時又滑動了GridView,那麼此時的GridView中的ImageView需要的是另一張圖片,假設你以前的圖片已經返回回來是不能顯示出來的。
把這兩個問題攻克了,此類問題也就差點兒相同了,剩下的就是效率問題了,這點Volley已經處理好了,咱們需要處理的就是以上兩點

函數


一、解決OOM
我就順便講解說決OOM的常用策略吧
OOM出現的狀況多是顯示的圖片很是大但是數量不是很是多。也有多是顯示的圖片很是多但是圖片不是很是大,咱們先看第一種狀況
http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
咱們直接看官網怎麼處理這個問題。我翻譯了一下


對於某些圖片。分辨率每每比咱們需要的高,比方咱們使用Galley控件展現咱們使用手機拍攝的照片時,這些照片的分辨率都比咱們的屏幕分辨率高,因此在顯示的時候咱們應該減小這些圖片的分辨率,因爲此時高分辨率的圖片在視覺上不會給咱們帶來不論什麼區別。反而會佔用咱們移動設備的珍貴內存。如下咱們就學習怎樣將高分辨率的圖片減小它的分辨率
工具

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

咱們在使用BitmapFactory讀取圖片數據的時候,假設咱們把options.inJustDecodeBounds設置爲true,那麼BitmapFactory僅僅會讀取圖片的高度和寬度等信息。不會將圖片數據讀入內存

options另外一個屬性options.inSampleSize,BitmapFactory在解析圖片數據的時候,會將圖片的寬度和高度變爲原來的1/inSampleSize,這樣圖片大大小就會變爲1/(inSampleSize*inSampleSize),比方inSampleSize=2 那麼圖片大小會變爲原來的1/4
那麼inSampleSize這個值怎麼獲取呢。經過下面算法就能夠

/**
	optiosns 就是上面我獲得的options
	reqWidth 就是咱們需要圖片的寬度
	reqHeight 就是咱們需要圖片的高度
*/
public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

解析圖片至需要的寬度和高度

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
	//首先將這個值設置爲true, 因此僅僅會獲取高度和寬度
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // 依據需要的寬度和高度  圖片的實際高度和寬度  計算縮小比例
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // 而後將該值設置爲false,真正解析圖片
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

經過以上的步驟遍可以將圖片縮小至某一大小,從而避免OOM


再來說講另一種OOM,這樣的OOM是因爲有太多的圖片,因此解決方式就是使用內存緩存和磁盤緩存
在Andorid中有兩個類
LruCache和DiskLruCache這個兩個類分別相應內存緩存和磁盤緩存
詳細使用見
http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
到此爲止OOM的問題就攻克了。如下看看錯位問題
方案一:
對於每一個請求的URL就設置到ImageView的tag中如imageView.setTag(url).當某一個請求成功返回一張圖片時,推斷ImageView的tag是不是當前請求的url
假設是的。那麼就顯示出來。並增長緩存。假設不是的,那麼只增長緩存
方案二:
一旦GridView滑動。那麼取消所有的請求,當GridView中止時。裏面開始請求可以看見的ImageView需要的圖片,今天的樣例我就是使用另一種方案,需要設置OnScrollListener

好了 咱們現在開始寫代碼吧

先總結一下Volley框架的使用步驟吧
(1) 初始化RequestQueue 
(2) 建立本身的Request
(3) 將Request增長到RequestQueue中。
但是對於圖片的請求步驟略微有點不一樣,圖片的請求涉及到ImageLoader實現的,事實上ImageLoader也是經過以上三步實現圖片請求的
如下我就在Appliaction中初始化Volley使用的環境吧


public class VolleyApplication extends Application 
{
  private static final String TAG = "VolleyApplication";
  //請求隊列
  private RequestQueue mRequestQueue;
  private static  VolleyApplication instance;
  //用於圖片請求
  private ImageLoader mImageLoader;
  //使用單例模式
  public static VolleyApplication getInstance()
  {
    return instance;
  }
  
  public RequestQueue getRequestQueue()
  {
    return mRequestQueue;
  }
  
  public ImageLoader getImageLoader()
  {
    return mImageLoader;
  }
  @Override
  public void onCreate() 
  {
    super.onCreate();
	//初始化內存緩存文件夾
    File cacheDir = new File(this.getCacheDir(), "volley");
	/**
	初始化RequestQueue,事實上這裏你可以使用Volley.newRequestQueue來建立一個RequestQueue,直接使用構造函數可以定製咱們需要的RequestQueue,比方線程池的大小等等
	*/
    mRequestQueue=new RequestQueue(new DiskBasedCache(cacheDir), new BasicNetwork(new HurlStack()), 3);
    
    instance=this;
    
	//初始化圖片內存緩存
    MemoryCache mCache=new MemoryCache();
	//初始化ImageLoader
    mImageLoader =new ImageLoader(mRequestQueue,mCache);
	//假設調用Volley.newRequestQueue,那麼如下這句可以不用調用
    mRequestQueue.start();
  }
}

使用LrcCache實現的一個圖片緩存,這個基本上可以通用

public class MemoryCache implements ImageCache 
{
  private static final String TAG = "MemoryCache";
  private LruCache<String, Bitmap> mCache;
  
  public MemoryCache()
  {
	//這個取單個應用最大使用內存的1/8
    int maxSize=(int)Runtime.getRuntime().maxMemory()/8;
    mCache=new LruCache<String, Bitmap>(maxSize){
      @Override
      protected int sizeOf(String key, Bitmap value) {
	  //這種方法必定要重寫,否則緩存沒有效果
        return value.getHeight()*value.getRowBytes();
      }
    };
  }

  @Override
  public Bitmap getBitmap(String key) {
    return mCache.get(key);
  }

  @Override
  public void putBitmap(String key, Bitmap value) {
    mCache.put(key, value);
  }
}

那麼開始寫代碼吧

(1) 首先給出Activity的佈局吧

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
     <GridView   
        android:id="@+id/grid_image"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:columnWidth="128dip"  
        android:stretchMode="columnWidth"  
        android:numColumns="2"  
        android:verticalSpacing="1dip"  
        android:horizontalSpacing="1dip"
        android:gravity="center"  
        ></GridView> 
</LinearLayout>

(2) 而後每個Item的佈局

<?

xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content" > <ImageView android:id="@+id/photo" android:layout_width="128dip" android:layout_height="128dip" android:src="@drawable/empty_photo" android:layout_centerInParent="true" /> </RelativeLayout>


(3) Activity代碼

public class ImageActivity extends Activity 
{
  private static final String TAG = "ImageActivity";
  private GridView mGridView;
  private ImageAdapter adapter;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    this.setContentView(R.layout.imagelayout);
    initViews();
  }
  
  private void initViews()
  {
    Log.i(TAG,"initViews");
    mGridView=(GridView)this.findViewById(R.id.grid_image);
    adapter=new ImageAdapter(this,mGridView);
    mGridView.setAdapter(adapter);
    
  }
}

(4) 最重要部分代碼

public class ImageAdapter extends BaseAdapter implements OnScrollListener
{
  private static final String TAG = "ImageAdapter";
  private Context context;
  private String[] items=Images.imageThumbUrls;
  private GridView mGridView;
  
  /**
   * 標識是不是第一次運行,假設是第一次運行 onScrollStateChanged是不調用的
   */
  private boolean isFirstEnter;
  /**
   * 第一個可以看見的item
   */
  private int firstSeeItem;
  
  /**
   * 記錄上一次可以看見的第一個,因爲假設已經到頂部。向下滑動GridView也會運行onScrollStateChanged 因此第一個可以見的沒有變化,那麼就不運行
   */
  private int orifirstItem;
  /**
   * 可以看見item的總數
   */
  private int totalSeeItem;
  
  public ImageAdapter(Context context,GridView mGridView)
  {
    this.context=context;
    this.mGridView=mGridView;
    //註冊這個是爲了在滑動的時候中止下載圖片,否則很是卡
    mGridView.setOnScrollListener(this);
    isFirstEnter=true;
  }

  @Override
  public int getCount() {
    return items.length;
  }

  @Override
  public Object getItem(int position) {
    return items[position];
  }

  @Override
  public long getItemId(int position) {
    return position;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    Log.v(TAG, "imagedown--->getView");
    ImageView imgView=null;
    if(convertView==null)
    {
      convertView=LayoutInflater.from(context).inflate(R.layout.imageitems, null);
    }
    imgView=(ImageView)convertView.findViewById(R.id.photo);
    imgView.setImageResource(R.drawable.empty_photo);
    //經過GridView的findViewByTag方法找到該View,防止錯位發生
    imgView.setTag(items[position]);
    
    return convertView;
  }

  @Override
  public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) 
  {
    Log.v(TAG, "imagedown--->onScroll");
    firstSeeItem=firstVisibleItem;
    totalSeeItem=visibleItemCount;
    
    if(isFirstEnter && visibleItemCount>0)
    {
      orifirstItem=firstVisibleItem;
      startLoadImages(firstSeeItem,totalSeeItem);
      isFirstEnter=false;
    }
  }

  @Override
  public void onScrollStateChanged(AbsListView view, int scrollState) 
  {
    Log.v(TAG, "imagedown--->onScrollStateChanged");
    if(orifirstItem!=firstSeeItem)
    {
      if(scrollState==SCROLL_STATE_IDLE)
      {
        startLoadImages(firstSeeItem,totalSeeItem);
        orifirstItem=firstSeeItem;
      }else
      {
        ImageUtils.cancelAllImageRequests();
      }
    }
   
    
  }
  /**
  開始下載圖片
  first就是第一個可以看見的position
  total就是可以看見個Item個數
  */
  private void startLoadImages(int first,int total)
  {
    Log.v(TAG, "imagedown--->startLoadImages,first-->"+first+",total-->"+total);
    for(int i=first;i<first+total;i++)
    {
      ImageUtils.loadImage(items[i], mGridView);
    }
  }
}

(5) 請求圖片工具類

public class ImageUtils
{
  private static final String TAG = "ImageUtils";
  
  public static void loadImage(final String url,final GridView mGridView)
  {
      
      ImageLoader imageLoader=VolleyApplication.getInstance().getImageLoader();
      ImageListener listener=new ImageListener() {
        ImageView tmpImg=(ImageView)mGridView.findViewWithTag(url);
        @Override
        public void onErrorResponse(VolleyError arg0) {
          //假設出錯。則說明都不顯示(簡單處理),最好準備一張出錯圖片
          tmpImg.setImageBitmap(null);
        }
        
        @Override
        public void onResponse(ImageContainer container, boolean arg1) {
          
          if(container!=null)
          {
             tmpImg=(ImageView)mGridView.findViewWithTag(url);
            if(tmpImg!=null)
            {
              if(container.getBitmap()==null)
              {
                tmpImg.setImageResource(R.drawable.empty_photo);
              }else
              {
                tmpImg.setImageBitmap(container.getBitmap());
              }
            }
          }
        }
      };
      ImageContainer newContainer=imageLoader.get(url, listener,128,128);
  }
  
  /**
   *  取消圖片請求
   */
  public static void cancelAllImageRequests() {
    ImageLoader imageLoader = VolleyApplication.getInstance().getImageLoader();
    RequestQueue requestQueue =   VolleyApplication.getInstance().getRequestQueue();
    if(imageLoader != null && requestQueue!=null){
      int num = requestQueue.getSequenceNumber();
	  //這種方法是我本身寫的,Volley裏面是沒有的。因此僅僅能使用我給的Volley.jar纔有這個函數
      imageLoader.drain(num);
    }
  }

}

好了 今天就寫到這裏吧,有什麼問題歡迎留言討論。代碼在前面已經提供下載了。。

相關文章
相關標籤/搜索