内存优化说了下UI,就说说内存吧。
**1,内存泄漏memory leak:**是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
**2,内存溢出 out of memory:**指程序申请内存时,没有足够的内存供申请者使用,内存不够用,此时就会报错OOM,即所谓的内存溢出。
移动开发和web开发的最大的区别是设备资源受限,对一般手机应用,这个资源是相当有限的,当应用程序处理大资源的资源,如图片或视频等媒体资源时 ,数量一多,时间一长,OOM是很容易出现的,不同手机不同机型也会有不同的效果,**你开发使用的测试机没有问题,但是有一些手机就会出现。**虽然JAVA有垃圾回收机制,但也存在内存泄露。为什么呢?
因为垃圾回收机制,没能回收,出现内存泄露,有内存泄漏,所以内存被占用越来越多,那么GC会更容易被触发,GC会越来越频发,但是当GC的时候所有的线程都是暂停状态的,需要处理的对象数量越多耗时越长,所以这也会造成卡顿。
如果有效避免这些,首先要养成良好的编程习惯,开启记得关闭,注册记得取消注册,集合不用记得清空,另外还有java的四大引用等等。
android中常见的原因主要有以下几个:
- 数据库的cursor没有关闭。
- 构造adapter没有使用缓存contentview。
- 调用registerReceiver()后未调用unregisterReceiver().
- 未关闭InputStream/OutputStream。
- Bitmap使用后未调用recycle()。
- Context泄漏。
- static关键字等。
资源未关闭造成的内存泄漏,这个不必多说,记得关闭。
- 网络、文件等流忘记关闭
- Service 执行完后忘记 stopSelf()
- EventBus 等观察者模式的框架忘记手动解除注册等
比较容易疏忽忘记的,是static静态,导致的Context引用无法回收。
static修饰符是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。我们看看google文档中的一个例子。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 1 private static Drawable sBackground;
2
3 @Override
4 protected void onCreate(Bundle state) {
5 super.onCreate(state);
6
7 TextView label = new TextView(this);
8 label.setText("Leaks are bad");
9
10 if (sBackground == null) {
11 sBackground = getDrawable(R.drawable.large_bitmap);
12 }
13 label.setBackgroundDrawable(sBackground);
14
15 setContentView(label);
16 }
17
静态变量sBackground持有该Activity的Context,当Drawable与View连接之后,Drawable就将View 设置为一个回调,由于View中是包含Context的引用的,所以,实际上我们依然保存了Context的引用。最终该Context也没有得到释放,也发生了内存泄露。
再举个小例子,非常常用的单例模式,写一个SingleInstanceDemo的单例引用,这里将对象静态化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 1public class SingleInstanceDemo {
2
3 private static SingleInstanceDemo mInstance;
4 private Context mContext;
5
6 private SingleInstanceDemo(Context context){
7 this.mContext = context;
8 }
9
10 public static SingleInstanceDemo newInstance(Context context){
11 if(mInstance == null){
12 mInstance = new SingleInstanceDemo(context);
13 }
14 return sInstance;
15 }
16}
17
当我们在Activity里面使用这个的时候,把我们Acitivty的context传进去,那么,这个单例就持有这个Activity的引用,当这个Activity没有用了,需要销毁的时候,因为这个单例还持有Activity的引用,所以无法GC回收,所以就出现了内存泄漏。解决方案也简单,改变构造方法:
1
2
3
4 1 private SingleInstanceDemo(Context context){
2 this.mContext = context.getApplicationContext();
3 }
4
总结一下,静态变量的使用和Context引用的情况:
第一,应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。
第二、Context尽量使用Application Context,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题。
第三、使用WeakReference代替强引用。比如可以使用WeakReference<Context> mContextRef;
最后说一下,我们的大胖子,bitmap内存泄露
Bitmap占用的内存实在是太多了,特别是分辨率大的图片,如果要显示多张那问题就更显著了。
第一:在用完Bitmap时,要 及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:“该图片可以释放了”, 还有就是, 虽然recycle()从源码上看,调用它应该能立即释放Bitmap的主要内存,但是测试表明它并没能立即释放内存。故我们还需手动设置为NULL这样 还能大大的加速Bitmap的主要内存的释放。
1
2
3
4
5 1 if(!bitmap.isRecycled()){
2 bitmap.recycle();
3 bitmap = null;
4 }
5
第二:压缩图片,加载一个缩小过的图片,可以设置一定的采样率,那么就可以大大减小占用的内存
1
2
3
4
5 1BitmapFactory.Options options = new BitmapFactory.Options();
2options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一
3Bitmap bitmap = BitmapFactory.decodeStream(cr.openInputStream(uri), null, options);
4preview.setImageBitmap(bitmap);
5
对图片质量进行压缩
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 1 ByteArrayOutputStream baos = new ByteArrayOutputStream();
2 //质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
3 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
4 int options = 100;
5 //循环判断如果压缩后图片是否大于50kb,大于继续压缩
6 while ( baos.toByteArray().length / 1024>50) {
7 //清空baos
8 baos.reset();
9 bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);
10 options -= 10;//每次都减少10
11 }
12 //把压缩后的数据baos存放到ByteArrayInputStream中
13 ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
14 //把ByteArrayInputStream数据生成图片
15 Bitmap newBitmap = BitmapFactory.decodeStream(isBm, null, null);
16
对图片尺寸进行压缩
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65 1 /**
2 * 按图片尺寸压缩 参数是bitmap
3 * @param bitmap
4 * @param pixelW
5 * @param pixelH
6 * @return
7 */
8 public static Bitmap compressImageFromBitmap(Bitmap bitmap, int pixelW, int pixelH) {
9 ByteArrayOutputStream os = new ByteArrayOutputStream();
10 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
11 if( os.toByteArray().length / 1024>512) {//判断如果图片大于0.5M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出
12 os.reset();
13 bitmap.compress(Bitmap.CompressFormat.JPEG, 50, os);//这里压缩50%,把压缩后的数据存放到baos中
14 }
15 ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
16 BitmapFactory.Options options = new BitmapFactory.Options();
17 options.inJustDecodeBounds = true;
18 options.inPreferredConfig = Bitmap.Config.RGB_565;
19 BitmapFactory.decodeStream(is, null, options);
20 options.inJustDecodeBounds = false;
21 options.inSampleSize = computeSampleSize(options , pixelH > pixelW ? pixelW : pixelH ,pixelW * pixelH );
22 is = new ByteArrayInputStream(os.toByteArray());
23 Bitmap newBitmap = BitmapFactory.decodeStream(is, null, options);
24 return newBitmap;
25 }
26
27
28 /**
29 * 动态计算出图片的inSampleSize
30 * @param options
31 * @param minSideLength
32 * @param maxNumOfPixels
33 * @return
34 */
35 public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
36 int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);
37 int roundedSize;
38 if (initialSize <= 8) {
39 roundedSize = 1;
40 while (roundedSize < initialSize) {
41 roundedSize <<= 1;
42 }
43 } else {
44 roundedSize = (initialSize + 7) / 8 * 8;
45 }
46 return roundedSize;
47 }
48
49 private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
50 double w = options.outWidth;
51 double h = options.outHeight;
52 int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
53 int upperBound = (minSideLength == -1) ? 128 :(int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength));
54 if (upperBound < lowerBound) {
55 return lowerBound;
56 }
57 if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
58 return 1;
59 } else if (minSideLength == -1) {
60 return lowerBound;
61 } else {
62 return upperBound;
63 }
64 }
65
第三:巧妙的运用软引用(SoftRefrence)
1
2 1 mBitmapRefs.add(new SoftReference(bitmap));
2
第四:加载图片尽量使用缓存机制,缓存框架也很多,这里就不一一举列了。