Out of Memory(内存溢出) 几乎是每个Android程序员都会遇到的事。在网上也能找到一大堆的解决方案,之前写过一篇《Android 内存溢出管理与测试》的博文。但感觉写得不是很好,今天整理一下打算重新写一篇。

 

首先什么是OOM?为什么会出现OOM?

Out Of Memory,一般是由于程序编写者对内存使用不当,如对该释放的内存资源没有释放,导致其一直不能被再次使用而使计算机内存被耗尽的现象。重启计算机即可,但根本解决办法还是对代码进行优化。(摘自百度百科)

 

那么解决OOM的方法有哪些呢?或者说常见的导致OOM的错误有哪些?

1、动态回收内存。

2、为应用分配更多的内存。

3、自定义内存大小。

4、如果是因为图片引起的OOM,其实就可以从图片下手。(使图片体积大小变小)

5、加载图片时在内存中做处理。(图片的边界压缩)

6、Context泄漏。

7、使用eclipse DDMS中的heap查看内存。

8、构造Adapter时,没有使用缓存的convertView。

9、资源对象没关闭造成的内存泄漏。

10、注册没取消造成的内存泄漏。

11、集合中对象没清理造成的内存泄漏。

12、使用缓存技术。

 

 

1、动态回收内存算是最简单的解决方法吧,就是手动的调用System.gc();

例如:bit为Bitmap对象

if (bit != null && !bit.isRecycled()) {
         bit.recycle();
         bit = null;
}
System.gc();

bitmap.recycle()方法用于回收该bitmap所占用的内存,用System.gc()调用一下系统的垃圾回收器。

需要注意的是:回收内存要及时,比如说SurfaceView,就应该在onSurfaceDestroyed这个方法中回收。如果Activity使用了bitmap,就可以在onStop或者onDestroy方法中回收等等。

 

2、为应用分配更多的内存。

在清单文件中的< application >节点下,添加如下代码:android:largeHeap="true"。

android:largeHeap应用程序的进程是否会用较大的 Dalvik 堆来创建。 这将作用于所有为该应用程序创建的进程,但只对第一个被装入进程的应用程序生效。 如果通过共享用户 ID 的方式让多个应用程序公用一个进程,那么这些应用程序必须全部指定本选项,否则将会导致不可预知的后果。

大部分应用程序不需要用到本属性,而是应该关注如何减少内存消耗以提高性能。 使用本属性并不能确保一定会增加可用的内存,因为某些设备可用的内存本来就很有限。

要在运行时查询可用的内存大小,请使用 getMemoryClass() 或getLargeMemoryClass() 方法。

 

除上述方法外,还有一个方法。

使用 dalvik.system.VMRuntime类提供的setTargetHeapUtilization方法可以增强程序堆内存的处理效率。

具体的使用如下:

private final static floatTARGET_HEAP_UTILIZATION = 0.75f;  
//在程序onCreate时就可以调用 
VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION); 
//即可 

 

3、一直感觉自定义的这种方法实在是太暴力了。

强制定义自己软件的对内存大小,我们使用Dalvik提供的 dalvik.system.VMRuntime类来设置最小堆内存为例:

private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;
VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE); //设置最小heap内存为6MB大小。当然对于内存吃紧来说还可以通过手动干涉GC去处理

 

4、这也分为两个方面:

1、分辨率不变,图片大小减小。  2、分辨率改变,图片减小。(用PS都很容易的)

需要注意的是:不要减小得太小而影响了人眼看上去的美感。

 

5、这里给出一个简单的操作和一个封装后的操作,可以对比看看。

简单的操作:

//压缩,用于节省BITMAP内存空间--解决BUG的关键步骤 
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 2;//这个的值压缩的倍数(2的整数倍),数值越小,压缩率越小,图片越清晰 
//返回原图解码之后的bitmap对象
Bitmap  bitmap = BitmapFactory.decodeResource(getResources(),
                    R.drawable.begin_background, opts);

这里的bitmap就是压缩后得到的图片。

封装后的操作:

private Bitmap imgUtis(Resources res, int img, int reqWidth, int reqHeight) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;// 让解析方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是null。
        BitmapFactory.decodeResource(getResources(), img, options);
        // 在加载图片之前就获取到图片的长宽值和MIME类型,并返回压缩的尺寸
        options.inSampleSize = calculateInSampleSize(options, reqWidth,
                reqHeight);
        // 使用获取到的inSampleSize值再次解析图片
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(getResources(), img, options);

    }
    /**
     * 
     * @param options 操作对象
     * @param reqWidth 目标宽
     * @param reqHeight 目标高
     * @return
     */
    public static int calculateInSampleSize(BitmapFactory.Options options,
            int reqWidth, int reqHeight) {
        // 源图片的高度和宽度
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;
        if (height > reqHeight || width > reqWidth) {
            // 计算出实际宽高和目标宽高的比率
            final int heightRatio = Math.round((float) height
                    / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);
            // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
            // 一定都会大于等于目标的宽和高。
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }
        return inSampleSize;
    }

这里得到的bitmap是按照规定的尺度比例来进行压缩的。

 

 

6.

所谓的Context泄漏,其实更多的是指Activity泄露,这是一个很隐晦的OutOfMemoryError的情况。
先看一个Android官网提供的例子: 
private static Drawable sBackground;  
@Override  
protected void onCreate(Bundle state) {  
  super.onCreate(state);  
  
  TextView label = new TextView(this);  
  label.setText("Leaks are bad");  
  
  if (sBackground == null) {  
    sBackground = getDrawable(R.drawable.large_bitmap);  
  }  
  label.setBackgroundDrawable(sBackground);  
  
  setContentView(label);  
}  
这段代码效率很快,但同时又是极其错误的; 在第一次屏幕方向切换时它泄露了一开始创建的Activity。当一个Drawable附加到一个 View上时, View会将其作为一个callback设定到Drawable上。上述的代码片段,意味着这个静态的Drawable拥有一个TextView的引用, 
而TextView又拥有Activity(Context类型)的引用,换句话说,Drawable拥有了更多的对象引用。即使Activity被 销毁,内存仍然不会被释放。 
另外,对Context的引用超过它本身的生命周期,也会导致该Context无法回收,从而导致内存泄漏。所以尽量使用Application这种Context类型。 
这种Context拥有和应用程序一样长的生命周期,并且不依赖Activity的生命周期。如果你打算保存一个长时间的对象, 
并且其需要一个 Context,记得使用Application对象。你可以通过调用Context.getApplicationContext()或 Activity.getApplication()轻松得到Application对象。 
最近遇到一种情况引起了Context泄漏,就是在Activity销毁时,里面有其他线程没有停。 
 
总结一下避免Context泄漏应该注意的问题: 
尽量使用Application这种Context类型。 
注意对Context的引用不要超过它本身的生命周期。 
慎重的对Context使用“static”关键字。 
Context里如果有线程,一定要在onDestroy()里及时停掉。

 

 

 

7.Android 开发工具eclipse中的DDMS带有一个内存监测工具Heap,可以检测一个进程的内存变化,根据这个工具我们大致可以测试某个应用的内存变化。

具体的操作方法如下:

1、打开eclipse,切换到DDMS,并确认Devices视图、Heap视图都是打开的。

2、将手机通过USB链接至电脑,链接时,选择 “USB调试”模式。

3、链接成功后,在DDMS的Devices视图中将会显示手机设备的序列号,以及设备中正在运行的部分进程信息。

4、在Devices 中,点击要监控的程序。

5、点击Devices视图界面中最上方一排图标中的“Update Heap”。

6、点击Heap视图。

7、点击Heap视图中的“Cause GC”按钮。

8、到此为止需检测的进程就可以被监视。

操作如图所示:

Android OOM 解决方案

说明:

1、点击Cause GC按钮相当于向虚拟机请求了一次垃圾回收操作; 

2、当内存使用信息第一次显示以后,无须再不断的点击Cause GC,Heap视图界面会定时刷新,在对应用的不断的操作过程中就可以看到内存使用的变化; 

3、内存使用信息的各项参数根据名称即可知道其意思,在此不再赘述。 

Heap视图中有一个Type叫做data object,即数据对象,也就是我们的程序中大量存在的类类型的对象。在data object一行中有一列是“Total Size”,其值就是当前进程中所有Java数据对象的内存总量,一般情况下,这个值的大小决定了是否会存在内存泄漏。判断方法如下:

进入某应用,不断的操作该应用,同时注意观察data object的Total Size值。

正常情况下Total Size值都会稳定在一个有限的范围内。

反之如果代码中存在没有释放对象引用的情况,则data object的Total Size值在每次GC后不会有明显的回落,随着操作次数的增多Total Size的值会越来越大,直到到达一个上限后导致进程被kill掉。

 

 

 

8.例如:ListView的工作原理简而言之是针对List中每个item, adapter都会调用一个getView的方法获得布局视图。我们一般会Inflate一个新的View,填充数据并返回显示。当然如果我们的Item很多话(比如上万个),都会新建一个View吗?很明显这样内存是接受不了的。因此优化就开始了,我们在getView()方法中使用了convertView == null的判断,这是Android已经给我们提供了Recycler机制了,我们就应该利用此机制,而不是每次都去inflate一个View。除此之外,我们还是从getView中的每一个方法调用去查看,发现其实我们拿到convertView的时候,每次都会根据这个布局去findViewById。因此,应使用一个静态类,保存xml中的各个子View的引用关系,这样就不必要每次都去解析xml了,而这个静态类就是代码中的ViewText。

其示例代码:

public class ExamDataAdapter extends BaseAdapter
{
    private List<Exam> exams = null;
    private LayoutInflater inflater;
    private int resource;// 绑定的条目界面

    public ExamDataAdapter(List<Exam> exam, Context context, int id)
    {
        this.resource = id;
        this.exams = exam;
        this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

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

    public Object getItem(int position)
    {
        return exams.get(position);
    }

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

    public View getView(int position, View convertView, ViewGroup parent)
    {
        TextView examCount = null;
        TextView examName = null;
        TextView examNumber = null;
        if (convertView == null)
        {
            convertView = inflater.inflate(resource, null);
            examName = (TextView) convertView.findViewById(R.id.test_list_name);
            examCount = (TextView) convertView.findViewById(R.id.test_list_count);
            examNumber = (TextView) convertView.findViewById(R.id.test_list_id);
            ViewText viewText = new ViewText();
            viewText.examName = examName;
            viewText.examCount = examCount;
            viewText.examNumber = examNumber;
            convertView.setTag(viewText);
        } else
        {
            ViewText viewText = (ViewText) convertView.getTag();
            examName = viewText.examName;
            examCount = viewText.examCount;
            examNumber = viewText.examNumber;
        }
        Exam exam = exams.get(position);
        examName.setText(exam.getExam_Name().trim());
        examCount.setText(exam.getExam_Count() + "".trim());
        examNumber.setText(exam.getExam_ID() + "".trim());
        return convertView;
    }

    public final class ViewText
    {
        public TextView examCount;
        public TextView examName;
        public TextView examNumber;
    }
}
View Code

相关文章:

  • 2021-11-23
  • 2021-06-13
  • 2022-01-10
  • 2021-07-14
  • 2021-08-08
  • 2021-12-07
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2022-12-23
  • 2021-12-10
  • 2021-05-15
  • 2021-06-23
  • 2021-12-02
相关资源
相似解决方案