您的位置:首页 > 其它

activity 内存释放

2015-07-23 23:08 239 查看


from:https://github.com/CharonChui/AndroidNote/blob/master/Android%E5%9F%BA%E7%A1%80/%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F.md


内存泄露

Java
C++
一个很大的区别就是
Java
有垃圾回收
GC(Garbage
 Collection)
自动管理内存的回收。但是我们在实际的项目中仍然会遇到内存泄露的问题。
Java
中对内存对象得访问是通过引用的方式,通过一个内存对象的引用变量来访问到对应的内存地址中的对象。
GC
会从代码栈的引用变量开始追踪,从而判断哪些内存是正在使用,如果无法跟踪到某一块堆内存,那么
GC
就认为这块内存不再使用了。


定义

内存泄露是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成的内存空间的浪费称为内存泄露。


原因

长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收, 这就是
java
中内存泄露的发生场景。


危害

只有一个,那就是虚拟机占用内存过高,导致
OOM
(内存溢出)。 对于
Android
应用来说,就是你的用户打开一个
Activity
,使用完之后关闭它,内存泄露;又打开,又关闭,又泄露;几次之后,程序占用内存超过系统限制就会出现
FC


Android
手机给应用分配的内存通常是8兆左右,如果处理内存处理不当很容易造成
OutOfMemoryError
OutOfMemoryError
主要由以下几种情况造成:

数据库
Cursor
没关。

当我们操作完数据库后,一定要调用
close()
释放资源。

构造
Adapter
没有使用缓存
ContentView


@Override  
public View getView(int position, View convertView, ViewGroup parent) {  
    ViewHolder vHolder = null;  
    //如果convertView对象为空则创建新对象,不为空则复用  
    if (convertView == null) {  
        convertView = inflater.inflate(..., null);  
        // 创建 ViewHodler 对象  
        vHolder = new ViewHolder();  
        vHolder.img= (ImageView) convertView.findViewById(...);  
        vHolder.tv= (TextView) convertView  
                .findViewById(...);  
        // 将ViewHodler保存到Tag中  
        convertView.setTag(vHolder);  
    } else {  
        //当convertView不为空时,通过getTag()得到View  
        vHolder = (ViewHolder) convertView.getTag();  
    }  
    // 给对象赋值,修改显示的值  
    vHolder.img.setImageBitmap(...);  
    vHolder.tv.setText(...);  
    return convertView;  
}  

static class ViewHolder {  
    TextView tv;  
    ImageView img;  
}


未取消注册广播接收者

registerReceiver()
unregisterReceiver()
要成对出现,通常需要在
Activity
onDestory()
方法去取消注册广播接收者。

IO
流未关闭 注意用完后及时关闭

Bitmap
使用后未调用
recycle()


Context
泄漏 这是一个很隐晦的
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);  
}


这段代码效率很快,但同时又是极其错误的:

我们看一下
setBackgroundDrawable(Drawable background)
的源码:

public void setBackgroundDrawable(Drawable background) {
    ...
    background.setCallback(this);
 }


background.setCallback(this);
方法,也就是说
Drawable
拥有
TextView
的引用,而
TextView
又拥有
Activity
(Context类型)的引用,
因为
sBackground
static
的,即使
Activity
被销毁,但是
sBackground
的生命周期还没走完,所以内存仍然不会被释放。这样就会有内存泄露了。
对,这样想是对的,但是我们看一下
setCallback
的源码:

public final void setCallback(Callback cb) {
    mCallback = new WeakReference<Callback>(cb);
}


我们会发现里面使用了
WeakReference
,所以不会存在内存泄露了,但是官网当时提供的例子明明说有泄露,这是因为在3.0之后,
修复了这个内存泄露的问题,在3.0之前
setCallback
,方法是没有使用
WeakReference
的,所以这种泄露的情况在3.0之前会发生,3.0之后已经被修复。

线程 线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。

public class MyActivity extends Activity {     
    @Override     
    public void onCreate(Bundle savedInstanceState) {         
        super.onCreate(savedInstanceState);         
        setContentView(R.layout.main);         
        new MyThread().start();     
    }       
    private class MyThread extends Thread{         
    @Override         
        public void run() {             
        super.run();             
        //耗时的操作       
        }     
    } 
}


假设
MyThread
run
函数是一个很费时的操作,当调用
finish
的时候
Activity
会销毁掉吗?

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

尽量使用
ApplicationContext
Context
引用的生命周期超过它本身的生命周期,也会导致
Context
泄漏。
所以如果打算保存一个长时间的对象时尽量使用
Application
这种
Context
类型。
例如:

mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
改成:
mStorageManager = (StorageManager) getApplicationContext().getSystemService(Context.STORAGE_SERVICE);


按道理来说这种系统服务是不会有问题,但是有些厂商在修改的时候,可能会导致
Context
无法被及时释放。

Handler的使用,在Activity退出的时候注意移除(尤其是循环的时候)

public class ThreadDemo extends Activity {  
    private static final String TAG = "ThreadDemo";  
    private int count = 0;  
    private Handler mHandler =  new Handler();  

    private Runnable mRunnable = new Runnable() {  

        public void run() {  
            //为了方便 查看,我们用Log打印出来   
            Log.e(TAG, Thread.currentThread().getName() + " " +count);    
            //每2秒执行一次   
            mHandler.postDelayed(mRunnable, 2000);  
        }   
    };  
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);   
        //通过Handler启动线程   
        mHandler.post(mRunnable);  
    } 
}


这样在也会引发内存泄露。我们应该在
onDestory
方法中移除
Handler
,代码如下:

@Override  
protected void onDestroy() {  
    super.onDestroy();  
    mHandler.removeCallbacks(mRunnable);  
}


总结一下避免
Contex
t泄漏应该注意的问题:

使用
getApplicationContext()
类型。
注意对
Context
的引用不要超过它本身的生命周期。
慎重的使用
static
关键字。
Activity
里如果有线程或
Handler
时,一定要在
onDestroy()
里及时停掉。

邮箱 :charon.chui@gmail.com

Good Luck!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: