最近在做android平板上的开发,其中涉及到高分辨率之下使用GridView的性能问题。在Android手机软件开发中,如果在ListView或者GridView上使用大数量Item,很多人都会想到ViewHolder......没错,ViewHolder非常适合用在ListView或者每行小于4个Item的GridView。但是如果是高分辨率的设备(android平板甚至android电视),每行包含4个以上Item的话,即使用了ViewHolder也依然卡。

      如下图,每行9个Item,而且每个Item的图片都是从网络动态下载的,这时就比较考验GridView视图的优化了。

使用这种方法有2个好处:

1.快速读取过去的Item;

2.直接保存View而不是Bitmap,避免了ImageView.setImageBitmaps()带来的延时。

当然坏处就是浪费内存,所以要设定一个上限,超过了就删掉最老的Item。
先来看看这种方法与ViewHolder的性能对比:

100个Item往下滚到的三组数据对比,如上图:
“CacheAdapter 缓存50个Item”跟ViewHolderAdapter的速度很接近,由于CacheAdapter有缓存,所以会有1~2次快速读取Item(10~20个)的情况,而ViewHolder的每次读取Item速度比较平均。
“CacheAdapter 缓存75个Item”只在第一次往下滚动时消耗较长时间,第二次用了缓存的Item,所以速度快了很多。

100个Item往上滚到的三组数据对比,如上图:

“CacheAdapter 缓存50个Item”比ViewHolderAdapter的速度略快,“CacheAdapter 缓存75个Item”依然是最快的。
总结:“CacheAdapter 缓存50个Item”速度与HolderView略快,读取最近的Item速度最快,缓存的Item越多速度越快。“CacheAdapter 缓存75个Item”占用内存最少,这是由于一部分图片下载失败,保存的Item的图片为空,实际上是缓存越多Item占用的内存越多。

PS:这里用到异步读取网络图片,成功下载的就占用较多内存,下载失败就占用较少内存,所以内存占用情况并不是一个时刻的绝对值,占用内存只用于参考.....

本文程序源码可以到http://www.rayfile.com/zh-cn/files/5ebf5666-958a-11e0-99ec-0015c55db73d/这里下载。

CacheAdapter.java是实现缓存Item的自定义Adapter,源码如下:


[java] view plain copy print ?



1. /**
2. * 使用列表缓存过去的Item
3. * @author hellogv
4. * 
5. */
6. public class CacheAdapterextends
7.   
8. public class
9. public
10. public
11. public
12. this.itemImageURL = itemImageURL; 
13. this.itemTitle = itemTitle; 
14.         }  
15.     }  
16.   
17. private
18. private ArrayList<Item> mItems =new
19.     LayoutInflater inflater;  
20. public
21.         mContext = c;  
22.         inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
23.     }  
24.   
25. public void
26. new
27.     }  
28.   
29. public int
30. return
31.     }  
32.   
33. public Item getItem(int
34. return
35.     }  
36.   
37. public long getItemId(int
38. return
39.     }  
40.       
41. new
42. new
43.       
44. new
45. long startTime=0; 
46. public View getView(int
47.         startTime=System.nanoTime();  
48.           
49. if (lstPosition.contains(position) ==false) {  
50. if(lstPosition.size()>75)//这里设置缓存的Item数量
51.             {  
52. 0);//删除第一项
53. 0);//删除第一项
54.             }  
55.  null);  
56.             TextView text = (TextView) convertView.findViewById(.itemText); 
57.             ImageView icon = (ImageView) convertView.findViewById(.itemImage); 
58.             text.setText(mItems.get(position).itemTitle);  
59. new AsyncLoadImage().execute(new
60.               
61. //添加最新项
62. //添加最新项
63. else
64.         {  
65.             convertView = lstView.get(lstPosition.indexOf(position)); 
66.         }  
67.           
68. int endTime=(int) (System.nanoTime()-startTime); 
69.         lstTimes.add(endTime);  
70. if(lstTimes.size()==10) 
71.         {  
72. int total=0; 
73. for(int i=0;i<lstTimes.size();i++) 
74.                 total=total+lstTimes.get(i);  
75.       
76. "10个所花的时间:" +total/1000 +" μs", 
77. "所用内存:"+Runtime.getRuntime().totalMemory()/1024 +" KB"); 
78.             lstTimes.clear();  
79.         }  
80.           
81. return
82.     }  
83.   
84. /**
85.      * 异步读取网络图片
86.      * @author hellogv
87.      */
88. class AsyncLoadImage extends
89. @Override
90. protected
91.   
92. try
93. 0]; 
94. 1]; 
95.                 Bitmap bitmap = getBitmapByUrl(url);   
96. new
97. catch
98. "error",e.getMessage()); 
99.                 e.printStackTrace();  
100. catch
101. "error",e.getMessage()); 
102.                 e.printStackTrace();  
103.             }  
104. return null; 
105.         }  
106.   
107. protected  void
108. 0]; 
109. 1]);          
110.         }  
111.     }  
112.   
113. static public
114. throws
115. new
116.         URLConnection connection = url.openConnection();  
117. 25000); 
118. 90000); 
119.         Bitmap bitmap = BitmapFactory.decodeStream(connection.getInputStream()); 
120. return
121.     }  
122. }


/**
 * 使用列表缓存过去的Item
 * @author hellogv
 * 
 */
public class CacheAdapter extends BaseAdapter {

	public class Item {
		public String itemImageURL;
		public String itemTitle;
		public Item(String itemImageURL, String itemTitle) {
			this.itemImageURL = itemImageURL;
			this.itemTitle = itemTitle;
		}
	}

	private Context mContext;
	private ArrayList<Item> mItems = new ArrayList<Item>();
	LayoutInflater inflater;
	public CacheAdapter(Context c) {
		mContext = c;
		inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
	}

	public void addItem(String itemImageURL, String itemTitle) {
		mItems.add(new Item(itemImageURL, itemTitle));
	}

	public int getCount() {
		return mItems.size();
	}

	public Item getItem(int position) {
		return mItems.get(position);
	}

	public long getItemId(int position) {
		return position;
	}
	
	List<Integer> lstPosition=new ArrayList<Integer>();
	List<View> lstView=new ArrayList<View>();
	
	List<Integer> lstTimes= new ArrayList<Integer>();
	long startTime=0;
	public View getView(int position, View convertView, ViewGroup parent) {
		startTime=System.nanoTime();
		
		if (lstPosition.contains(position) == false) {
			if(lstPosition.size()>75)//这里设置缓存的Item数量
			{
				lstPosition.remove(0);//删除第一项
				lstView.remove(0);//删除第一项
			}
			convertView = inflater.inflate(R.layout.item, null);
			TextView text = (TextView) convertView.findViewById(.itemText);
			ImageView icon = (ImageView) convertView.findViewById(.itemImage);
			text.setText(mItems.get(position).itemTitle);
			new AsyncLoadImage().execute(new Object[] { icon,mItems.get(position).itemImageURL });
			
			lstPosition.add(position);//添加最新项
			lstView.add(convertView);//添加最新项
		} else
		{
			convertView = lstView.get(lstPosition.indexOf(position));
		}
		
		int endTime=(int) (System.nanoTime()-startTime);
		lstTimes.add(endTime);
		if(lstTimes.size()==10)
		{
			int total=0;
			for(int i=0;i<lstTimes.size();i++)
				total=total+lstTimes.get(i);
	
			Log.e("10个所花的时间:" +total/1000 +" μs",
					"所用内存:"+Runtime.getRuntime().totalMemory()/1024 +" KB");
		    lstTimes.clear();
		}
		
		return convertView;
	}

	/**
	 * 异步读取网络图片
	 * @author hellogv
	 */
	class AsyncLoadImage extends AsyncTask<Object, Object, Void> {
		@Override
		protected Void doInBackground(Object... params) {

			try {
				ImageView imageView=(ImageView) params[0];
				String url=(String) params[1];
				Bitmap bitmap = getBitmapByUrl(url);
				publishProgress(new Object[] {imageView, bitmap});
			} catch (MalformedURLException e) {
				Log.e("error",e.getMessage());
				e.printStackTrace();
			} catch (IOException e) {
				Log.e("error",e.getMessage());
				e.printStackTrace();
			}
			return null;
		}

		protected void onProgressUpdate(Object... progress) {
			ImageView imageView = (ImageView) progress[0];
			imageView.setImageBitmap((Bitmap) progress[1]);			
		}
	}

	static public Bitmap getBitmapByUrl(String urlString)
			throws MalformedURLException, IOException {
		URL url = new URL(urlString);
		URLConnection connection = url.openConnection();
		connection.setConnectTimeout(25000);
		connection.setReadTimeout(90000);
		Bitmap bitmap = BitmapFactory.decodeStream(connection.getInputStream());
		return bitmap;
	}
}

其中if(lstPosition.size()>75)是设置缓存的Item数量的关键地方,这里缓存75个Item。

ViewHolderAdapter.java是实现ViewHolder加载Item的自定义Adapter,源码如下:


[java] view plain copy print ?


1. /**
2. * 使用ViewHolder加载Item
3. * @author hellogv
4. * 
5. */
6. public class ViewHolderAdapterextends
7.   
8. public class
9. public
10. public
11.   
12. public
13. this.itemImageURL = itemImageURL; 
14. this.itemTitle = itemTitle; 
15.         }  
16.     }  
17.   
18. private
19. private ArrayList<Item> mItems =new
20.     LayoutInflater inflater;  
21. public
22.         mContext = c;  
23.         inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
24.     }  
25.   
26. public void
27. new
28.     }  
29.       
30. public int
31. return
32.     }  
33.   
34. public Item getItem(int
35. return
36.     }  
37.   
38. public long getItemId(int
39. return
40.     }  
41.       
42. static class
43.         TextView text;  
44.         ImageView icon;  
45.     }  
46.       
47. new
48. long startTime=0; 
49. public View getView(int
50.         startTime=System.nanoTime();  
51.           
52.         ViewHolder holder;  
53.   
54. if (convertView == null) { 
55.  null);  
56. new
57.             holder.text = (TextView) convertView.findViewById(.itemText); 
58.             holder.icon = (ImageView) convertView.findViewById(.itemImage); 
59.             convertView.setTag(holder);  
60. else
61.             holder = (ViewHolder) convertView.getTag();   
62.         }  
63.         holder.text.setText(mItems.get(position).itemTitle); 
64. new AsyncLoadImage().execute(new
65.           
66. int endTime=(int) (System.nanoTime()-startTime); 
67.         lstTimes.add(endTime);  
68. if(lstTimes.size()==10) 
69.         {  
70. int total=0; 
71. for(int i=0;i<lstTimes.size();i++) 
72.                 total=total+lstTimes.get(i);  
73.       
74. "10个所花的时间:" +total/1000 +" μs", 
75. "所用内存:"+Runtime.getRuntime().totalMemory()/1024 +" KB"); 
76.             lstTimes.clear();  
77.         }  
78.           
79. return
80.     }  
81.   
82. /**
83.      * 异步读取网络图片
84.      * @author hellogv
85.      */
86. class AsyncLoadImage extends
87. @Override
88. protected
89.   
90. try
91. 0]; 
92. 1]; 
93.                 Bitmap bitmap = CacheAdapter.getBitmapByUrl(url); 
94. new
95. catch
96.                 e.printStackTrace();  
97. catch
98.                 e.printStackTrace();  
99.             }  
100. return null; 
101.         }  
102.   
103. protected  void
104. 0]; 
105. 1]); 
106.         }  
107.     }  
108.   
109. }  
 
/**
 * 使用ViewHolder加载Item
 * @author hellogv
 * 
 */
public class ViewHolderAdapter extends BaseAdapter {

	public class Item {
		public String itemImageURL;
		public String itemTitle;

		public Item(String itemImageURL, String itemTitle) {
			this.itemImageURL = itemImageURL;
			this.itemTitle = itemTitle;
		}
	}

	private Context mContext;
	private ArrayList<Item> mItems = new ArrayList<Item>();
	LayoutInflater inflater;
	public ViewHolderAdapter(Context c) {
		mContext = c;
		inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
	}

	public void addItem(String itemImageURL, String itemTitle) {
		mItems.add(new Item(itemImageURL, itemTitle));
	}
	
	public int getCount() {
		return mItems.size();
	}

	public Item getItem(int position) {
		return mItems.get(position);
	}

	public long getItemId(int position) {
		return position;
	}
	
	static class ViewHolder {
		TextView text;
		ImageView icon;
	}
	
	List<Integer> lstTimes= new ArrayList<Integer>();
	long startTime=0;
	public View getView(int position, View convertView, ViewGroup parent) {
		startTime=System.nanoTime();
		
		ViewHolder holder;

		if (convertView == null) {
			convertView = inflater.inflate(R.layout.item, null);
			holder = new ViewHolder();
			holder.text = (TextView) convertView.findViewById(.itemText);
			holder.icon = (ImageView) convertView.findViewById(.itemImage);
			convertView.setTag(holder);
		} else {
			holder = (ViewHolder) convertView.getTag();
		}
		holder.text.setText(mItems.get(position).itemTitle);
		new AsyncLoadImage().execute(new Object[]{holder.icon,mItems.get(position).itemImageURL });
		
		int endTime=(int) (System.nanoTime()-startTime);
		lstTimes.add(endTime);
		if(lstTimes.size()==10)
		{
			int total=0;
			for(int i=0;i<lstTimes.size();i++)
				total=total+lstTimes.get(i);
	
			Log.e("10个所花的时间:" +total/1000 +" μs",
					"所用内存:"+Runtime.getRuntime().totalMemory()/1024 +" KB");
		    lstTimes.clear();
		}
		
		return convertView;
	}

	/**
	 * 异步读取网络图片
	 * @author hellogv
	 */
	class AsyncLoadImage extends AsyncTask<Object, Object, Void> {
		@Override
		protected Void doInBackground(Object... params) {

			try {
				ImageView imageView=(ImageView) params[0];
				String url=(String) params[1];
				Bitmap bitmap = CacheAdapter.getBitmapByUrl(url);
				publishProgress(new Object[] {imageView, bitmap});
			} catch (MalformedURLException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			return null;
		}

		protected void onProgressUpdate(Object... progress) {
			ImageView imageView = (ImageView) progress[0];
			imageView.setImageBitmap((Bitmap) progress[1]);
		}
	}

}

testPerformance.java是主程序,通过注释符就可以分别测试CacheAdapter与ViewHolderAdapter的性能,源码如下:


[java] view plain copy print ?



    1. public class testPerformanceextends
    2. /** Called when the activity is first created. */
    3. @Override
    4. public void
    5. super.onCreate(savedInstanceState); 
    6.         setContentView(R.layout.main);  
    7. this.setTitle("android平板上的GridView视图缓存优化-----hellogv"); 
    8.         GridView gridview = (GridView) findViewById(.gridview);   
    9. new CacheAdapter(this); 
    10. // ViewHolderAdapter adapter=new ViewHolderAdapter(this);
    11.          
    12.         gridview.setAdapter(adapter);  
    13. "";//请自己选择网络上的静态图片
    14.           
    15. for(int i=0;i<100;i++) 
    16.         {  
    17.  "第"+i+"项");  
    18.         }  
    19.           
    20.     }  
    21. }