现在的应用中内存的管理是一个大的问题,如何保证不因内存问题影响程序的使用和用户体验效果,这里是我看到的网络上的一篇不错的文章,对其进行了简单的整理。让我们来共同学习
内存的重要性: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),造成内存超出限制。