Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android-----帶你一步一步優化ListView(三)

android-----帶你一步一步優化ListView(三)

編輯:關於Android編程

前兩篇我們介紹了一般的優化ListView方法以及DiskLruCache優化ListView,見android-----帶你一步一步優化ListView(一)和android-----帶你一步一步優化ListView(二),這一篇我們將從內存緩存的角度來完成ListView的優化,使用的是LruCache,它的主要算法原理是把最近使用的對象用強引用存儲在 LinkedHashMap 中,並且把最近最少使用的對象在緩存值達到預設定值之前從內存中移除,並沒有一個固定的緩存大小是符合所有應用程序的,我們應該根據自己應用程序的狀況設置出合理的緩存大小,下面我們通過實例來學習一下LruCache具體怎麼使用:

首先定義顯示ListView的布局文件:listview.xml

 



    
    

接著定義每個item顯示的樣式item.xml

 



    
    
接下來就是最重要的ListViewAdapter適配器內容了,代碼有點長度,先貼出來,接下來慢慢解釋:

 

public class ListViewAdapter extends BaseAdapter{

	//存儲所有將要訪問的路徑
	public List list;
	public LruCache lruCache;
	public LayoutInflater inflater;
	public ListView listView;
	public int reqWidth;
	public int reqHeight;
	public Set tasks = new HashSet();

	
	public ListViewAdapter(Context context,List list,LruCache lruCache,ListView listView,ImageView imageView)
	{
		this.list = list;
		this.lruCache = lruCache;
		this.inflater = LayoutInflater.from(context);
		this.listView = listView;
		LayoutParams params = imageView.getLayoutParams();
		reqWidth = params.width;
		reqHeight = params.height;
	}
	@Override
	public int getCount() {
		return list.size();
	}

	@Override
	public String getItem(int position) {
		return list.get(position);
	}

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

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		View view = null;
		ViewHolder holder = null;
		if(convertView == null)
		{
			view = inflater.inflate(R.layout.item, null);
			holder = new ViewHolder();
			holder.imageView = (ImageView) view.findViewById(R.id.imageView);
			holder.textView = (TextView) view.findViewById(R.id.textView);
			view.setTag(holder);
		}else
		{
			view = convertView;
			holder = (ViewHolder) view.getTag();
		}
		//為ImageView以及TextView設置Tag防止出現亂序
		holder.imageView.setTag(position);
		holder.textView.setTag(position+"#");
		return view;
	}
	
	/**
	 * 添加當前key值對應的Bitmap到LruCache中
	 * @param key
	 * @param bitmap
	 */
	public void addBitmapToMemoryCache(String key,Bitmap bitmap)
	{
		if(getBitmapFromMemoryCache(key) == null)
		{
			lruCache.put(key, bitmap);
		}
	}
	
	/**
	 * 從LruCache中獲取對應於key的Bitmap對象
	 * @param key
	 * @return
	 */
	public Bitmap getBitmapFromMemoryCache(String key)
	{
		return lruCache.get(key);
	}
	
	/**
	 * 加載某一位置上的圖片
	 * @param url
	 * @param position
	 */
	public void loadImage(int position)
	{
		//獲取到position位置對應的url
		String url = getItem(position);
		Bitmap bitmap = null;
		bitmap = getBitmapFromMemoryCache(url);
		if(bitmap != null)
		{
			//表示緩存中存在對應的於此url的Bitmap,則直接獲得該Bitmap並且顯示到ListView上面
			ImageView imageView = (ImageView) listView.findViewWithTag(position);
			TextView textView = (TextView) listView.findViewWithTag(position+"#");
			if(imageView != null)
				imageView.setImageBitmap(bitmap);
			if(textView != null)
				textView.setText("從緩存中獲取的");
		}else
		{
			//開啟線程從網絡中加載圖片
			ImageAsyncTask task = new ImageAsyncTask(listView, position);
			task.setOnImageLoadListener(new OnImageLoadListener() {
				
				@Override
				public void onSuccessLoad(Bitmap bitmap) {
					System.out.println("加載圖片成功");
				}
				
				@Override
				public void onFailureLoad() {
					System.out.println("加載圖片失敗");
				}
			});
			tasks.add(task);
			task.execute(url);//開啟線程加載圖片
		}
	}
	
	/**
	 * 暫停所有任務(為了防止在滑動的時候仍然有線程處於請求狀態)
	 */
	public void cancelTask()
	{
		if(tasks != null)
		{
			for(ImageAsyncTask task: tasks)
				task.cancel(false);//暫停任務
		}
	}
	
	/**
	 * 對圖片進行壓縮處理
	 * @param in
	 * @param reqWidth
	 * @param reqHeight
	 * @return
	 */
	public static Bitmap decodeSampleBitmapFromStream(InputStream in,int reqWidth,int reqHeight)
	{
		//設置BitmapFactory.Options的inJustDecodeBounds屬性為true表示禁止為bitmap分配內存
		BitmapFactory.Options options = new BitmapFactory.Options();
		options.inJustDecodeBounds = true;
		byte[] data = inputStreamToByteArray(in);
		//這次調用的目的是獲取到原始圖片的寬、高,但是這次操作是沒有寫內存操作的
		Bitmap beforeBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
		options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
		//設置這次加載圖片需要加載到內存中
		options.inJustDecodeBounds = false;
		Bitmap afterBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
		return afterBitmap;
	}
	
	/**
	 * 計算出壓縮比
	 * @param options
	 * @param reqWith
	 * @param reqHeight
	 * @return
	 */
	public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight)
	{
		//通過參數options來獲取真實圖片的寬、高
		int width = options.outWidth;
		int height = options.outHeight;
		int inSampleSize = 1;//初始值是沒有壓縮的
		if(width > reqWidth || height > reqHeight)
		{
			//計算出原始寬與現有寬,原始高與現有高的比率
			int widthRatio = Math.round((float)width/(float)reqWidth);
			int heightRatio = Math.round((float)height/(float)reqHeight);
			//選出兩個比率中的較小值,這樣的話能夠保證圖片顯示完全 
			inSampleSize = widthRatio < heightRatio ? widthRatio:heightRatio;
		}
		return inSampleSize;
	}
	
	/**
	 * 將InputStream轉換為Byte數組
	 * @param in
	 * @return
	 */
	public static byte[] inputStreamToByteArray(InputStream in)
	{
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		byte[] buffer = new byte[1024];
		int len;
		try {
			while((len = in.read(buffer)) != -1)
			{
				outputStream.write(buffer, 0, len);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			try {
				in.close();
				outputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return outputStream.toByteArray();
	}
	static class ViewHolder
	{
		ImageView imageView;
		TextView textView;
	}
	
	class ImageAsyncTask extends AsyncTask
	{
		public ListView listView;
		public OnImageLoadListener listener;
		public int position;
		
		public ImageAsyncTask(ListView listView,int position)
		{
			this.listView = listView;
			this.position = position;
		}
		
		public void setOnImageLoadListener(OnImageLoadListener listener)
		{
			this.listener = listener;
		}
		@Override
		protected Bitmap doInBackground(String... params) {
			String urlString = params[0];
			URL url = null;
			InputStream in = null;
			HttpURLConnection connection = null;
			Bitmap bitmap = null;
			try {
				url = new URL(urlString);
				connection = (HttpURLConnection) url.openConnection();
				in = connection.getInputStream();
				bitmap = decodeSampleBitmapFromStream(in, reqWidth, reqHeight);
				//將當前Bitmap添加到緩存中
				if(bitmap != null)
					lruCache.put(urlString, bitmap);
			} catch (Exception e) {
				e.printStackTrace();
			}
			return bitmap;
		}
		
		@Override
		protected void onPostExecute(Bitmap result) {
			if(result != null)
			{
				listener.onSuccessLoad(result);
				ImageView imageView = (ImageView) listView.findViewWithTag(position);
				TextView textView = (TextView)listView.findViewWithTag(position+"#");
				if(imageView != null)
					imageView.setImageBitmap(result);
				if(textView != null)
					textView.setText("從網絡中獲取的");
			}else
				listener.onFailureLoad();
		}
	}
}

其中39行的getView方法代碼就是我們平常使用ListView的常規寫法,復用convertView以及對每個view設置viewHolder,為防止圖片加載出現亂序,第55、56行我們分別對ImageView以及TextView設置了Tag標志;

第65行的addBitmapToMemoryCache用於向LruCache中添加當前Bitmap位圖,因為LruCache本身實際上是由LinkedHashMap實現的,所有調用的是put方法;

第78行的getBitmapFromMemoryCache方法用於從LruCache緩存中讀取指定key值的Bitmap位圖;

第88行的loadImage就是我們的核心代碼,首先第91行會通過調用getItem方法來獲得當前item條目所要加載圖片的url,隨後93行調用getBitmapFromMemoryCache查看緩存中是否存在指定key值的Bitmap,第94行進行判斷,如果存在Bitmap的話,則進入if語句塊中,97、98行通過findViewWithTag來獲得對應條目的ImageView以及TextView,並且將當前獲取到的緩存圖片顯示到當前item;如果當前緩存中不存在的話,則進入103行的else語句塊中,106行定義一個ImageAsyncTask圖片加載線程,並在119行將當前線程加入到Set類型的tasks集合中,便於我們隨後對加載圖片線程進行控制,第120行調用execute方法,將當前需要加載圖片的url傳入;

execute接下會調用ImageAsyncTask的doInBackground方法來加載圖片,這裡的圖片加載方法和之前android-----帶你一步一步優化ListView(二) 是一致的,不清楚的朋友可以看看上一篇博客,另外這裡面也用到了圖片壓縮技術,不太懂的可以看看android-----解決Bitmap內存溢出的一種方法(圖片壓縮技術),在圖片壓縮完成之後會在第245行將當前Bitmap添加到LruCache中,隨後在onPostExecute中進行更新UI的操作即可啦;

最後就是MainActivity方法了:

 

public class MainActivity extends Activity implements OnScrollListener {

	public LruCache memoryCache;
	public int start_index;
	public int end_index;
	public ListViewAdapter adapter;
	public boolean isInit = true;
	public String[] images =  { "http://img.my.csdn.net/uploads/201407/26/1406383299_1976.jpg",  
	        					"http://img.my.csdn.net/uploads/201407/26/1406383291_6518.jpg",  
	        					"http://img.my.csdn.net/uploads/201407/26/1406383291_8239.jpg",  
	        					"http://img.my.csdn.net/uploads/201407/26/1406383290_9329.jpg",  
	        					"http://img.my.csdn.net/uploads/201407/26/1406383290_1042.jpg",  
	        					"http://img.my.csdn.net/uploads/201407/26/1406383275_3977.jpg",  
	        					"http://img.my.csdn.net/uploads/201407/26/1406383265_8550.jpg",  
	        					"http://img.my.csdn.net/uploads/201407/26/1406383264_3954.jpg",  
	        					"http://img.my.csdn.net/uploads/201407/26/1406383264_4787.jpg",  
	        					"http://img.my.csdn.net/uploads/201407/26/1406383264_8243.jpg",  
	        					"http://img.my.csdn.net/uploads/201407/26/1406383248_3693.jpg",  
	        					"http://img.my.csdn.net/uploads/201407/26/1406383243_5120.jpg",  
	        					"http://img.my.csdn.net/uploads/201407/26/1406383242_3127.jpg",  
	        					"http://img.my.csdn.net/uploads/201407/26/1406383242_9576.jpg",  
	        					"http://img.my.csdn.net/uploads/201407/26/1406383242_1721.jpg",  
	        					"http://img.my.csdn.net/uploads/201407/26/1406383219_5806.jpg",  
	        					"http://img.my.csdn.net/uploads/201407/26/1406383214_7794.jpg",  
	        					"http://img.my.csdn.net/uploads/201407/26/1406383213_4418.jpg",  
	        					"http://img.my.csdn.net/uploads/201407/26/1406383213_3557.jpg",  
							    "http://img.my.csdn.net/uploads/201407/26/1406383210_8779.jpg"};
	public List list = new ArrayList();
	public ListView listView;
	public ImageView imageView;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.listview);
		memoryCache = getLruCache();
		for(int i = 0;i < 50;i++)
		{
			int index = i %(images.length);
			list.add(images[index]);
		}
		listView = (ListView) findViewById(R.id.listView);
		LayoutInflater inflater = LayoutInflater.from(this);
		View view = inflater.inflate(R.layout.item, null);
		imageView = (ImageView) view.findViewById(R.id.imageView);
		adapter = new ListViewAdapter(this, list, memoryCache, listView, imageView);
		listView.setOnScrollListener(this);
		listView.setAdapter(adapter);
	}
	
	/**
	 * 獲得LruCache對象
	 * @return
	 */
	public LruCache getLruCache()
	{
		LruCache cache = null;
		//獲得可用的內存大小
		int maxMemory = (int)(Runtime.getRuntime().maxMemory()/1024);
		//將其1/8設置成我們的內存緩存大小
		int cacheSize = maxMemory/8;
		cache = new LruCache(cacheSize){
			@Override
			protected int sizeOf(String key, Bitmap value) {
				//返回當前圖片的大小(getByteCount用於返回當前位圖所占用的內存字節數)
				return value.getByteCount() / 1024;
			}
		};
		return cache;
	}

	@Override
	public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
		start_index = firstVisibleItem;
		end_index = start_index + visibleItemCount;
		if(isInit == true && visibleItemCount > 0)
		{
			for(int i = start_index;i < end_index;i++)
			{
				adapter.loadImage(i);
		    }
			isInit = false;
		}
	}
	@Override
	public void onScrollStateChanged(AbsListView view, int scrollState) {
		if(scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE)
		{
			//表示停止滑動,這時候就可以加載圖片
			for(int i = start_index;i < end_index;i++)
			{
				adapter.loadImage(i);
		    }
		}else
		{
			adapter.cancelTask();
		}
	}
}

這裡比較關鍵的代碼就是第55行的getLruCache方法了,首先會通過Runtime.getRuntime().maxMemory()獲得當前可用的內存大小,之後的cacheSize就是我們自己設置的LruCache所用的內存大小,第66行的sizeof方法用於返回當前圖片的大小;

同樣,類似於上一篇中DiskLruCache的使用,這裡我們也設置了ListView的滑動事件,保證其在滑動的過程中不會進行加載圖片的請求操作,滑動停止再去加載圖片;

另外提示一句,不要忘記添加訪問網絡的權限

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved