最近在做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. }