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); }
总结一下避免
Context泄漏应该注意的问题:
使用
getApplicationContext()类型。
注意对
Context的引用不要超过它本身的生命周期。
慎重的使用
static关键字。
Activity里如果有线程或
Handler时,一定要在
onDestroy()里及时停掉。
邮箱 :charon.chui@gmail.com
Good Luck!
相关文章推荐
- 某曲线的旋转版本及其它
- 过滤转型
- extjs研究学习笔记《一》
- minisat 安装指南
- UVA11997 K Smallest Sums(并归,优先队列)
- spring容器加载完毕做一件事情(利用ContextRefreshedEvent事件)
- iOS推送 (百度推送)
- C++中L和_T()之区别
- MyBatis知多少(16)MyBatis映射
- 部署Exchange Server 2016 Preview
- 部署Exchange Server 2016 Preview
- Linux kernel 分析之二十三:文件系统
- C++标志粘贴##怎么使用
- boost库常用功能
- 股票学习(技术分析05)
- 文件打包与解压缩
- java RMI
- Java Beans
- *HDU 5305 - Friends (DFS + 剪枝)
- 第一百一十二天 how can I 坚持