在最近做的工程中发现加载的图片太多或图片过大时经常出现OOM问题,找网上资料也提供了很多方法,但自己感觉有点乱,特此,今天在不同型号的三款安卓手机上做了测试,因为有效果也有结果,今天小马就做个详细的总结,以供朋友们共同交流学习,也供自己以后在解决OOM问题上有所提高,提前讲下,片幅有点长,涉及的东西太多,大家耐心看,肯定有收获的,里面的很多东西小马也是学习参考网络资料使用的,先来简单讲下下:
一般我们大家在遇到内存问题的时候常用的方式网上也有相关资料,大体如下几种:
一:在内存引用上做些处理,常用的有软引用、强化引用、弱引用
二:在内存中加载图片时直接在内存中做处理,如:边界压缩
三:动态回收内存
四:优化Dalvik虚拟机的堆内存分配
五:自定义堆内存大小
可是真的有这么简单吗,就用以上方式就能解决OOM了?不是的,继续来看...
下面小马就照着上面的次序来整理下解决的几种方式,数字序号与上面对应:
1:软引用(SoftReference)、虚引用(PhantomRefrence)、弱引用(WeakReference),这三个类是对heap中java对象的应用,通过这个三个类可以和gc做简单的交互,除了这三个以外还有一个是最常用的强引用
1.1:强引用,例如下面代码:
1. Object o=new
2. Object o1=o;
上面代码中第一句是在heap堆中创建新的Object对象通过o引用这个对象,第二句是通过o建立o1到new Object()这个heap堆中的对象的引用,这两个引用都是强引用.只要存在对heap中对象的引用,gc就不会收集该对象.如果通过如下代码:
1. o=null;
2. o1=null
heap中对象有强可及对象、软可及对象、弱可及对象、虚可及对象和不可到达对象。应用的强弱顺序是强、软、弱、和虚。对于对象是属于哪种可及的对象,由他的最强的引用决定。如下:
1. String abc=new String("abc"); //1
2. SoftReference<String> abcSoftRef=new SoftReference<String>(abc); //2
3. WeakReference<String> abcWeakRef = new WeakReference<String>(abc); //3
4. abc=null; //4
5. abcSoftRef.clear();//5
上面的代码中:
第一行在heap对中创建内容为“abc”的对象,并建立abc到该对象的强引用,该对象是强可及的。第二行和第三行分别建立对heap中对象的软引用和弱引用,此时heap中的对象仍是强可及的。第四行之后heap中对象不再是强可及的,变成软可及的。同样第五行执行之后变成弱可及的。
1.2:软引用
软引用是主要用于内存敏感的高速缓存。在jvm报告内存不足之前会清除所有的软引用,这样以来gc就有可能收集软可及的对象,可能解决内存吃紧问题,避免内存溢出。什么时候会被收集取决于gc的算法和gc运行时可用内存的大小。当gc决定要收集软引用是执行以下过程,以上面的abcSoftRef为例:
1 首先将abcSoftRef的referent设置为null,不再引用heap中的new String("abc")对象。
2 将heap中的new String("abc")对象设置为可结束的(finalizable)。
3 当heap中的new String("abc")对象的finalize()方法被运行而且该对象占用的内存被释放, abcSoftRef被添加到它的ReferenceQueue中。
注:对ReferenceQueue软引用和弱引用可以有可无,但是虚引用必须有,参见:
1. Reference(T paramT, ReferenceQueue<? super
被 Soft Reference 指到的对象,即使没有任何 Direct Reference,也不会被清除。一直要到 JVM 内存不足且 没有 Direct Reference 时才会清除,SoftReference 是用来设计 object-cache 之用的。如此一来 SoftReference 不但可以把对象 cache 起来,也不会造成内存不足的错误 (OutOfMemoryError)。我觉得 Soft Reference 也适合拿来实作 pooling 的技巧。
1. new
2. Refenrence sr = new
3.
4. //引用时
5. if(sr!=null){
6. obj = sr.get();
7. }else{
8. new
9. new
10. }
1.3:弱引用
当gc碰到弱可及对象,并释放abcWeakRef的引用,收集该对象。但是gc可能需要对此运用才能找到该弱可及对象。通过如下代码可以了明了的看出它的作用:
1. String abc=new String("abc");
2. WeakReference<String> abcWeakRef = new
3. abc=null;
4. System.out.println("before gc: "+abcWeakRef.get());
5. System.gc();
6. System.out.println("after gc: "+abcWeakRef.get());
运行结果:
before gc: abc
after gc: null
gc收集弱可及对象的执行过程和软可及一样,只是gc不会根据内存情况来决定是不是收集该对象。如果你希望能随时取得某对象的信息,但又不想影响此对象的垃圾收集,那么你应该用 Weak Reference 来记住此对象,而不是用一般的 reference。
1. A obj = new
2.
3. new
4.
5. null;
6.
7. //等待一段时间,obj对象就会被垃圾回收
8. ...
9.
10. if (wr.get()==null) {
11. "obj 已经被清除了 ");
12. else
13. "obj 尚未被清除,其信息是 "+obj.toString());
14. }
15. ...
16. }
在此例中,透过 get() 可以取得此 Reference 的所指到的对象,如果返回值为 null 的话,代表此对象已经被清除。这类的技巧,在设计 Optimizer 或 Debugger 这类的程序时常会用到,因为这类程序需要取得某对象的信息,但是不可以 影响此对象的垃圾收集。
1.4:虚引用
就是没有的意思,建立虚引用之后通过get方法返回结果始终为null,通过源代码你会发现,虚引用通向会把引用的对象写进referent,只是get方法返回结果为null.先看一下和gc交互的过程在说一下他的作用.
1.4.1 不把referent设置为null, 直接把heap中的new String("abc")对象设置为可结束的(finalizable).
1.4.2 与软引用和弱引用不同, 先把PhantomRefrence对象添加到它的ReferenceQueue中.然后在释放虚可及的对象.
你会发现在收集heap中的new String("abc")对象之前,你就可以做一些其他的事情.通过以下代码可以了解他的作用.
1. import
2. import
3. import
4. import
5.
6. public class
7. public static boolean isRun = true;
8.
9. public static void main(String[] args) throws
10. new String("abc");
11. "@"
12. final ReferenceQueue referenceQueue = new
13. new
14. public void
15. while
16. Object o = referenceQueue.poll();
17. if (o != null) {
18. try
19. class
20. "referent");
21. true);
22. Object result = rereferent.get(o);
23. "gc will collect:"
24. "@"
25. + result.hashCode());
26. catch
27.
28. e.printStackTrace();
29. }
30. }
31. }
32. }
33. }.start();
34. new
35. referenceQueue);
36. null;
37. 3000);
38. System.gc();
39. 3000);
40. false;
41. }
42.
43. }
结果为
class java.lang.String@96354
gc will collect:class java.lang.String@96354 好了,关于引用就讲到这,下面看2
2:在内存中压缩小马做了下测试,对于少量不太大的图片这种方式可行,但太多而又大的图片小马用个笨的方式就是,先在内存中压缩,再用软引用避免OOM,两种方式代码如下,大家可参考下:
方式一代码如下:
1. @SuppressWarnings("unused")
2. private
3. new
4. new
5. //下面这个设置是将图片边界不可调节变为可调节
6. true;
7. 2;
8. int
9. int
10. bmap = BitmapFactory.decodeFile(picture.getAbsolutePath(),
11. bitmapFactoryOptions);
12. float imagew = 150;
13. float imageh = 150;
14. int yRatio = (int) Math.ceil(bitmapFactoryOptions.outHeight
15. / imageh);
16. int xRatio = (int) Math
17. .ceil(bitmapFactoryOptions.outWidth / imagew);
18. if (yRatio > 1 || xRatio > 1) {
19. if
20. bitmapFactoryOptions.inSampleSize = yRatio;
21. else
22. bitmapFactoryOptions.inSampleSize = xRatio;
23. }
24.
25. }
26. false;
27. bmap = BitmapFactory.decodeFile(picture.getAbsolutePath(),
28. bitmapFactoryOptions);
29. if(bmap != null){
30. //ivwCouponImage.setImageBitmap(bmap);
31. return
32. }
33. return null;
34. }
方式二代码如下:
1. package
2.
3. import
4. import
5. import
6. import
7. import
8. import
9. import
10. import
11. import
12. import
13. import
14. import
15. import
16. import
17. import
18. import
19. import
20. import
21. import
22. import
23. import
24. import
25. import
26. import
27. import
28. import
29. /**
30. * @Title: PhotoScanActivity.java
31. * @Description: 照片预览控制类
32. * @author XiaoMa
33. */
34. public class PhotoScanActivity extends
35. private
36. private
37. private
38. private
39. private
40. private
41. private HashMap<String, SoftReference<Bitmap>> imageCache = null;
42. private Bitmap bitmap = null;
43. private SoftReference<Bitmap> srf = null;
44.
45. @Override
46. public void
47. super.onCreate(savedInstanceState);
48. getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
49. WindowManager.LayoutParams.FLAG_FULLSCREEN);
50. setContentView(R.layout.photoscan);
51. this.getIntent();
52. if(intent != null){
53. if(intent.getBundleExtra("bundle") != null){
54. "bundle");
55. "path");
56. "shopType");
57. }
58. }
59. init();
60. }
61.
62. private void
63. new
64. gallery = (Gallery)findViewById(R.id.gallery);
65. ImageList = getSD();
66. if(ImageList.size() == 0){
67. "无照片,请返回拍照后再使用预览", Toast.LENGTH_SHORT).show();
68. return
69. }
70. new ImageAdapter(this, ImageList);
71. gallery.setAdapter(adapter);
72. gallery.setOnItemLongClickListener(longlistener);
73. }
74.
75.
76. /**
77. * Gallery长按事件操作实现
78. */
79. private OnItemLongClickListener longlistener = new
80.
81. @Override
82. public boolean
83. final int position, long
84. //此处添加长按事件删除照片实现
85. new AlertDialog.Builder(PhotoScanActivity.this);
86. dialog.setIcon(R.drawable.warn);
87. "删除提示");
88. "你确定要删除这张照片吗?");
89. "确定", new
90. @Override
91. public void onClick(DialogInterface dialog, int
92. new
93. boolean
94. if(file.exists()){
95. isSuccess = file.delete();
96. if(isSuccess){
97. ImageList.remove(position);
98. adapter.notifyDataSetChanged();
99. //gallery.setAdapter(adapter);
100. if(ImageList.size() == 0){
101. Toast.makeText(getApplicationContext(), getResources().getString(R.string.phoSizeZero), Toast.LENGTH_SHORT).show();
102. }
103. Toast.makeText(getApplicationContext(), getResources().getString(R.string.phoDelSuccess), Toast.LENGTH_SHORT).show();
104. }
105. }
106. }
107. });
108. "取消",new
109. @Override
110. public void onClick(DialogInterface dialog, int
111. dialog.dismiss();
112. }
113. });
114. dialog.create().show();
115. return false;
116. }
117. };
118.
119. /**
120. * 获取SD卡上的所有图片文件
121. * @return
122. */
123. private
124. /* 设定目前所在路径 */
125. File fileK ;
126. new
127. if("newadd".equals(shopType)){
128. //如果是从查看本人新增列表项或商户列表项进来时
129. new
130. else{
131. //此时为纯粹新增
132. new
133. }
134. File[] files = fileK.listFiles();
135. if(files != null && files.length>0){
136. for(File f : files ){
137. if(getImageFile(f.getName())){
138. it.add(f.getPath());
139.
140.
141. new
142.
143. //下面这个设置是将图片边界不可调节变为可调节
144. true;
145. 5;
146. int
147. int
148. float imagew = 150;
149. float imageh = 150;
150. int yRatio = (int) Math.ceil(bitmapFactoryOptions.outHeight
151. / imageh);
152. int xRatio = (int) Math
153. .ceil(bitmapFactoryOptions.outWidth / imagew);
154. if (yRatio > 1 || xRatio > 1) {
155. if
156. bitmapFactoryOptions.inSampleSize = yRatio;
157. else
158. bitmapFactoryOptions.inSampleSize = xRatio;
159. }
160.
161. }
162. false;
163.
164. bitmap = BitmapFactory.decodeFile(f.getPath(),
165. bitmapFactoryOptions);
166.
167. //bitmap = BitmapFactory.decodeFile(f.getPath());
168. new
169. imageCache.put(f.getName(), srf);
170. }
171. }
172. }
173. return
174. }
175.
176. /**
177. * 获取图片文件方法的具体实现
178. * @param fName
179. * @return
180. */
181. private boolean
182. boolean
183.
184. /* 取得扩展名 */
185. String end = fName
186. ".") + 1, fName.length())
187. .toLowerCase();
188.
189. /* 按扩展名的类型决定MimeType */
190. if (end.equals("jpg") || end.equals("gif") || end.equals("png")
191. "jpeg") || end.equals("bmp")) {
192. true;
193. else
194. false;
195. }
196. return
197. }
198.
199. public class ImageAdapter extends
200. /* 声明变量 */
201. int
202. private
203. private
204.
205. /* ImageAdapter的构造符 */
206. public
207. mContext = c;
208. lis = li;
209. TypedArray a = obtainStyledAttributes(R.styleable.Gallery);
210. 0);
211. a.recycle();
212. }
213.
214. /* 几定要重写的方法getCount,传回图片数目 */
215. public int
216. return
217. }
218.
219. /* 一定要重写的方法getItem,传回position */
220. public Object getItem(int
221. return
222. }
223.
224. /* 一定要重写的方法getItemId,传并position */
225. public long getItemId(int
226. return
227. }
228.
229. /* 几定要重写的方法getView,传并几View对象 */
230. public View getView(int
231. "lis:"+lis);
232. new
233. SoftReference<Bitmap> srf = imageCache.get(file.getName());
234. Bitmap bit = srf.get();
235. new
236. i.setImageBitmap(bit);
237. i.setScaleType(ImageView.ScaleType.FIT_XY);
238. new
239. WindowManager.LayoutParams.WRAP_CONTENT));
240. return
241. }
242. }
243. }
这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存,如果图片多且大,这种方式还是会引用OOM异常的,不着急,有的是办法解决,继续看,以下方式也大有妙用的:
1. 1. InputStream is = this.getResources().openRawResource(R.drawable.pic1);
2. options=new
3. options.inJustDecodeBounds = false;
4. options.inSampleSize = 10; //width,hight设为原来的十分一
5. btp =BitmapFactory.decodeStream(is,null,options);
6. 2. if(!bmp.isRecycle() ){
7. bmp.recycle() //回收图片所占的内存
8. system.gc() //提醒系统及时回收
9. }
上面代码与下面代码大家可分开使用,也可有效缓解内存问题哦...吼吼...
1.
2. /** 这个地方大家别搞混了,为了方便小马把两个贴一起了,使用的时候记得分开使用
3. * 以最省内存的方式读取本地资源的图片
4. */
5. public static Bitmap readBitMap(Context context, int resId){
6. opt = new
7. opt.inPreferredConfig = Bitmap.Config.RGB_565;
8. opt.inPurgeable = true;
9. opt.inInputShareable = true;
10. //获取资源图片
11. is = context.getResources().openRawResource(resId);
12. return BitmapFactory.decodeStream(is,null,opt);
13. }
3:大家可以选择在合适的地方使用以下代码动态并自行显式调用GC来回收内存:
1. if(bitmapObject.isRecycled()==false) //如果没有回收
2. bitmapObject.recycle();
4:这个就好玩了,优化Dalvik虚拟机的堆内存分配,听着很强大,来看下具体是怎么一回事
对于Android平台来说,其托管层使用的Dalvik JavaVM从目前的表现来看还有很多地方可以优化处理,比如我们在开发一些大型游戏或耗资源的应用中可能考虑手动干涉GC处理,使用 dalvik.system.VMRuntime类提供的setTargetHeapUtilization方法可以增强程序堆内存的处理效率。当然具体原理我们可以参考开源工程,这里我们仅说下使用方法: 代码如下:
1. private final static floatTARGET_HEAP_UTILIZATION = 0.75f;
2. 在程序onCreate时就可以调用
3. VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);
4. 即可
5:自定义我们的应用需要多大的内存,这个好暴力哇,强行设置最小内存大小,代码如下:
1. private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;
2. //设置最小heap内存为6MB大小
3. VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);
好了,文章写完了,片幅有点长,因为涉及到的东西太多了,其它文章小马都会贴源码,这篇文章小马是直接在项目中用三款安卓真机测试的,有效果,项目原码就不在这贴了,不然泄密了都,吼吼,但这里讲下还是会因为手机的不同而不同,大家得根据自己需求选择合适的方式来避免OOM,大家加油呀,每天都有或多或少的收获,这也算是进步,加油加油!