现在的应用中内存的管理是一个大的问题,如何保证不因内存问题影响程序的使用和用户体验效果,这里是我看到的网络上的一篇不错的文章,对其进行了简单的整理。让我们来共同学习

内存的重要性:Android主要应用在嵌入式设备当中,而嵌入式设备由于一些众所周知的条件限制,通常都不会有很高的配置,特别是内存是比较有限的。如果我们编写的代码当中有太多的对内存使用不当的地方,难免会使得我们的设备运行缓慢,甚至是死机。每个应用程序都是在属于自己的进程中运行的。一方面,如果程序在运行过程中出现了内存泄漏的问题,仅仅会使得自己的进程被kill掉,而不会影响其他进程(如果是system_process 等系统进程出问题的话,则会引起系统重启)。另一方面Android为不同类型的进程分配了不同的内存使用上限,如果应用进程使用的内存超过了这个上限,则会被系统视为内存泄漏,从而被kill掉。

 

内存泄漏:进程中某些对象(垃圾对象)已经没有价值了,但是它们却可以直接或间接地引用到gc  roots导致无法被GC回收。无用的对象占据着内存空间,使得实际可使用内存变小,这就造成了内存泄漏。

造成内存泄漏的可能原因:

(1)     查询数据库没有关闭游标:当我们查询数据库时拿到的游标对象,处理完数据后,不在使用后要及时的关闭游标。

(2)    构造Adapter时,没有使用缓存的 convertView:以构造ListView的BaseAdapter为例,在BaseAdapter中提高了方法:
public View getView(int position, ViewconvertView, ViewGroup parent)
来 向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的 view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参 View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。
  由此可以看出,如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。ListView回收list item的view对象的过程可以查看:

android.widget.AbsListView.java --> voidaddScrapView(View scrap) 方法。
  示例代码:
 public View getView(int position, ViewconvertView, ViewGroup parent) {
   View view = new Xxx(...);
   ... ...
   return view;
 }
  修正示例代码:
 public View getView(int position, ViewconvertView, ViewGroup parent) {
   View view = null;
   if (convertView != null) {
   view = convertView;
   populate(view, getItem(position));
   ...
   } else {
   view = new Xxx(...);
   ...
   }
   return view;
 }

(3)     Bitmap对象不在使用时调用recycle()释放内存

(4)     释放对象的引用:当我们定义一个成员对象后,并实例化后,使用完毕该对象后,并没有将该对象的引用去除,则该对象会一直占用内存空间,使内存空间减少,这也是内存泄漏。

修改前

public class MainActivity extends Activity {
private TextView num;
private Integer number;
 
    @Override
    protected voidonCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        num= (TextView) findViewById(R.id.num);
        new Thread(new Runnable(){
                
                @SuppressLint("UseValueOf")@Override
                public void run(){
number=new Integer(1);//实例化number
                       android.os.Message msg=newandroid.os.Message();
msg.obj=number;//到这里number就使用完毕了,而该对象仍然一直占用着空间
                       handler.sendMessage(msg);
                }
         }).start();
    }
    private Handler handler=new Handler(){
       publicvoid handleMessage(android.os.Message msg) {
              Toast.makeText(MainActivity.this,msg.obj+"", Toast.LENGTH_LONG).show();
       };
    };
}
修改后:
public class MainActivity extends Activity {
private TextView num;
private Integer number;
 
    @Override
    protected voidonCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        num= (TextView) findViewById(R.id.num);
        new Thread(new Runnable(){
                
                @SuppressLint("UseValueOf")@Override
                public void run(){
number=new Integer(1);//实例化number
                       Integero=null;
                       o=number;
                       number=null;//这里就将该对象的引用释放了,保存该对象数据的空间则没有引用指向了,则成为垃圾,会被java的垃圾回收机制回收
                       android.os.Message msg=newandroid.os.Message();
msg.obj=o;
                       handler.sendMessage(msg);
                }
         }).start();
    }
    private Handler handler=new Handler(){
       publicvoid handleMessage(android.os.Message msg) {
              Toast.makeText(MainActivity.this,msg.obj+"", Toast.LENGTH_LONG).show();
       };
    };
}

(5)     Android应用程序中最典型的需要注意释放资源的情况是在Activity的生命周期中,在onPause()、onStop()、 onDestroy()方法中需要适当的释放资源的情况。

(6)     静态变量的使用:

 static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context的情况最多),这时就要谨慎对待了。

1.         public class ClassName {  
2.              private static Context mContext;  
3.         }  
以上的代码是很危险的,如果将Activity赋值到mContext的话。那么即使该Activity已经onDestroy,但是由于仍有对象保存它的引用,因此该Activity依然不会被释放。
我们举Android文档中的一个例子。
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.       }  
    sBackground, 是一个静态的变量,但是我们发现,我们并没有显式的保存Contex的引用,但是,当Drawable与View连接之后,Drawable就将View设置为一个回调,由于View中是包含Context的引用的,所以,实际上我们依然保存了Context的引用。这个引用链如下:
    Drawable->TextView->Context
    所以,最终该Context也没有得到释放,发生了内存泄露。
    如何才能有效的避免这种引用的发生呢?
    第一,应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。
    第二、Context尽量使用Application Context,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题。
    第三、使用WeakReference代替强引用。比如可以使用WeakReference<Context> mContextRef;
 
(7)     线程的使用造成的内存泄漏
第一种:线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。
1.         public class MyActivity extends Activity {  
2.             @Override  
3.             public void onCreate(Bundle savedInstanceState) {  
4.                 super.onCreate(savedInstanceState);  
5.                 setContentView(R.layout.main);  
6.                 new MyThread().start();  
7.             }  
8.           
9.             private class MyThread extends Thread{  
10.              @Override  
11.              public void run() {  
12.                  super.run();  
13.                  //do somthing  
14.              }  
15.          }  
16.      }

    这段代码很平常也很简单,是我们经常使用的形式。我们思考一个问题:假设MyThread的run函数是一个很费时的操作,当我们开启该线程后,将设备的横屏变为了竖屏,一般情况下当屏幕转换时会重新创建Activity,按照我们的想法,老的Activity应该会被销毁才对,然而事实上并非如此。

    由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。

第二种:异步任务类的使用

AsyncTask内部的实现机制是运用了ThreadPoolExcutor,该类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露的问题。

这种线程导致的内存泄露问题应该如何解决呢?

    第一、将线程的内部类,改为静态内部类。

    第二、在线程内部采用弱引用保存Context引用。

    解决的模型如下:

1.         public abstract class WeakAsyncTask<Params, Progress, Result, WeakTarget> extends  
2.                 AsyncTask<Params, Progress, Result> {  
3.             protected WeakReference<WeakTarget> mTarget;  
4.           
5.             public WeakAsyncTask(WeakTarget target) {  
6.                 mTarget = new WeakReference<WeakTarget>(target);  
7.             }  
8.           
9.             /** {@inheritDoc} */  
10.          @Override  
11.          protected final void onPreExecute() {  
12.              final WeakTarget target = mTarget.get();  
13.              if (target != null) {  
14.                  this.onPreExecute(target);  
15.              }  
16.          }  
17.        
18.          /** {@inheritDoc} */  
19.          @Override  
20.          protected final Result doInBackground(Params... params) {  
21.              final WeakTarget target = mTarget.get();  
22.              if (target != null) {  
23.                  return this.doInBackground(target, params);  
24.              } else {  
25.                  return null;  
26.              }  
27.          }  
28.        
29.          /** {@inheritDoc} */  
30.          @Override  
31.          protected final void onPostExecute(Result result) {  
32.              final WeakTarget target = mTarget.get();  
33.              if (target != null) {  
34.                  this.onPostExecute(target, result);  
35.              }  
36.          }  
37.        
38.          protected void onPreExecute(WeakTarget target) {  
39.              // No default action  
40.          }  
41.        
42.          protected abstract Result doInBackground(WeakTarget target, Params... params);  
43.        
44.          protected void onPostExecute(WeakTarget target, Result result) {  
45.              // No default action  
46.          }  
47.      }

内存溢出:由于内存占用较大,超出设备的内存大小,就会造成OutOfMemory(OOM),通常手机的内存不会很大,(手机内存分为两部分1.ROM指的是手机本身的内存,跟电脑的硬盘一个道理。2.RAM指的是运行内存,跟电脑内存条石一个道理),代码的不规范很容易造成内存溢出,造成程序被kill。造成内存溢出的原因有:

内存泄漏,长时间占用内存,随着程序的运行,内存占用不断增加,就会造成内存溢出,从而使程序崩溃。

保存了多个耗用内存过大的对象(如Bitmap),造成内存超出限制。