Android 应用程序在开发的过程中内存的准确控制是判断一个程序好坏的重要标准之一:
一、假如我们开发的程序内存溢出、泄漏了会引发那些实质性的问题呢?
1、程序卡顿、响应速度变慢。
2、开启其他程序的时候,内存泄漏的程序放在后台没有进行关闭,但是也可能会莫名其妙的消失(内存越大它在,在后台越有可能死掉,如果内存小可能在后台停留的时间越长)
3、甚至有的时候会直接崩溃。
二、面对内存溢出这些可能出现的问题,走入正题,先说一下Android的内存机制:
Android的程序是由java语言进行开发的,所以Android的内存管理与Java的内存管理相似。到底什么东西需要为之分配内存的?对象,所有对象在java堆内分配空间;然而对象的释放是由垃圾回收器来完成的。C/C++中的内存机制是“谁污染,谁治理”,java对于这一点进行了比较人性化的改善,专门设计了一个专门的清洁工GC。
那么GC怎么能够确认某一个对象是不是已经被废弃了呢?Java采用了有向图的原理。Java将引用关系考虑为图的有向边,有向边从引用者指向引用对象。线程对象可以作为有向图的起始顶点,该图就是从起始顶点开始的一棵树,根顶点可以到达的对象都是有效对象,GC不会回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。(本段之前也不是很理解,通过查询的具体GC怎么进行回收的。)
三、那到底什么是内存溢出(OOM)呢?它是怎么发生的呢?
我们先从原理上说一下内存溢出和它的发生: 相信读者都读过一些Android开发的书籍,书中都会讲到Android系统框架,其中有一个Dalvik讲解,没错Android的虚拟机是基于寄存器的Dalvik的,一般情况它的最大堆的大小就是16M。Android系统会为每一个应用程序分配这个16M的Dalvik,只有16M当然我们能利用的内存空间是相当有限的了,如果我们开发的内存空间超过了16M就会出现内存溢出现象OOM(out of Memory)。
原理上说完了,其实结合开发,概括起来内存不够用就两种情况:
(1)内存泄漏:由于在代码编写的过程中,长期的保留了某种资源的引用,让该资源得不到释放。
(2)保留了多个消耗大内存的对象,导致程序内存超过了16M的限制。
针对以上着两种情况,以及结合我在开发过程中遇到的和内存优化有关系的问题,在下面我会一一进行解释说明:
问题一:静态变量导致的内存泄漏,这是最简单的内存泄露
static是Java中的一个关键字,它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例,尤其 Context,因为它出现的内存泄漏错误很多,在开发过程中千万不要出现
public static Context mContext;
mContext=this;
如果将Activity赋值到了mContext,当退出这个Activity的时候,即使Activity已经Ondestory,但是仍然会有对象保存它的引用,因此这个Activity仍然得不到释放。
对于这种情况应该如何避免呢:(1)应该尽量避免static成员变量引用资源耗费过多的实例,就像Context。
(2)应该尽量使用Application中的Context,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题。
问题二:属性动画导致的内存泄漏
Android 3.0以后出现了属性动画,在属性动画中有一类无限循环的动画,如果我们在开发过程中播放了此类动画而且没有在onDestory中去停止动画,那么动画会一直播放下去,虽然在界面中无法看到该东湖效果了,最主要的是这个时候Activity中呢个要执行动画的view会被动画持有,而View又持有了Activity,最终导致Activity也无法得到释放。
以下面的代码为例:
mImageview=(Imageview)findViewById(R.id.a);
ObjectAnimator animator=ObjectAnimator.ofFloat(mImagview,"rotation",animator.setRepeatCount(ValueAnimator.INFINITE));
animator.start();
对于上面的这段代码是无限循环的动画,但是会泄漏当前的Activity,解决办法就是在onDestory中调用animator.cancel();来停止动画
问题三:静态内部类的使用防止Activity中的对象泄漏(用到弱引用)
首先说一下我们为什么要用静态内部类呢,因为静态类不持有外部类对象,这样的话Activity就可以随便进行回收了。但是有些时候,就像假如我们想通过Handler进行通信的时候,将Handler设为静态内部类就无法操作Activity中的内容了,这个时候我们还要用到弱引用。
以Handler声明为静态内部类为例子进行讲解:
在一个定义的线程中执行: ihandler.sendEmptyMessage(SUCCESS);
声明一个静态内部类:
private static class IHandler extends Handler {
private final WeakReference
public IHandler(Login login) {
mActivity = new WeakReference
}
@Override
public void handleMessage(Message msg) {
int flag = msg.what;
switch (flag) {
case 0:
String errorMsg = (String) msg.getData().getSerializable(
"ErrorMsg");
((Login) mActivity.get()).showTip(errorMsg);
break;
case SUCCESS:
((Login) mActivity.get()).finish();
break;
}
}
}
在线程开启后会通过Handler进行通信,但是要在静态内部类IHandler中操作Activity,这时候又要避免内存泄漏我们就用到了静态内部类,静态内部不持有外部类的对象,所以用到了弱引用,注意,一个弱引用指向的Activity,在回收的时候是没有影响的,在GC检查的时候直接将Activity回收掉,内存泄漏的问题也不会出现了。
问题四:在操作数据库的时候Cursor忘记关闭
Cursor是Android查询数据后得到的一个管理数据集合的类,正常情况下,如果查询得到的数据量较小时不会有内存问题,而且虚拟机能够保证Cusor最终会被释放掉。
然而如果Cursor的数据量特表大,特别是如果里面有Blob信息时,应该保证Cursor占用的内存被及时的释放掉,而不是等待GC来处理。并且Android明显是倾向于编程者手动的将Cursor close掉,因为在源代码中我们发现,如果等到垃圾回收器来回收时,会给用户以错误提示。
所以我们使用Cursor的方式一般如下:
Cursor cursor = null;
try {
cursor = mContext.getContentResolver().query(uri,null, null,null,null);
if(cursor != null) {
cursor.moveToFirst();
//do something
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
cursor.close();
}
}
有一种情况下,我们不能直接将Cursor关闭掉,这就是在CursorAdapter中应用的情况,但是注意,CursorAdapter在Acivity结束时并没有自动的将Cursor关闭掉,因此,你需要在onDestroy函数中,手动关闭。
@Override
protected void onDestroy() {
if (mAdapter != null && mAdapter.getCurosr() != null) {
mAdapter.getCursor().close();
}
super.onDestroy();
}
CursorAdapter中的changeCursor函数,会将原来的Cursor释放掉,并替换为新的Cursor,所以你不用担心原来的Cursor没有被关闭。
你可能会想到使用Activity的managedQuery来生成Cursor,这样Cursor就会与Acitivity的生命周期一致了,多么完美的解决方法!然而事实上managedQuery也有很大的局限性。 managedQuery生成的Cursor必须确保不会被替换,因为可能很多程序事实上查询条件都是不确定的,因此我们经常会用新查询的Cursor来替换掉原先的Cursor。因此这种方法适用范围也是很小。
问题5:IO、File等资源未释放掉
对于这种情况相信开发者再熟悉不过也不会范这种低级的错误,文件、流等是相当耗费内存资源的,在使用完成后一定要关闭,此乃大忌。。。。
问题6:在Android中Adapter使用十分广泛,特别是在list中。所以adapter是数据的 “集散地” ,所以对其进行内存优化是很有必要的。Adapter中ViewHoler与ContentView的使用也能节省内存消耗:一个内存溢出的重要点
VIewHoler是利用ListView的一种视图缓存机制,避免了每次调用getView()的时候通过fingViewById()实例控件(避免了每次都要寻找控件内存消耗),使用ViewHlder能够提高50%以上的效率,而使用ConvertView能够进行view的缓存,直接将子布局转换成View进行缓存,然后每次再进来的时候可以用过tag找到缓存的布局(避免了每次布局的转化寻找消耗的内存)。
实例代码如下:
@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中(Tag可以接收Object类型对象,所以任何东西都可以保存在其中)
convertView.setTag(vHolder);
} else {
//当convertView不为空时,通过getTag()得到View
vHolder = (ViewHolder) convertView.getTag();
}
// 给对象赋值,修改显示的值
vHolder.img.setImageBitmap(...);
vHolder.tv.setText(...);
return convertView;
}
//将显示的View 包装成类
static class ViewHolder {
TextView tv;
ImageView img;
}
由于明天要期末考试,我先去复习一会,还有两个重要的模块bitmap与线程池的使用明天考完试完成。。。