【Android性能优化】(一) Android内存泄露分析

来源:互联网 发布:javascript if 编辑:程序博客网 时间:2024/05/17 07:05

注:转载请注明来自Nemo, http://blog.csdn.net/nemo__
 
 

一、Android Studio内存泄露查找方法

1. 在Android Studio内,按Alt+6,跳转到Android Monitor编辑框。
2. 关注Memory项内容,操作怀疑内存泄露的步骤。
3. 点击Memory右边Initiate GC后,Dump Java Heap,会生成一个hprof文件。
4. 可以分析内存占用较大的对象,同时在hprof编辑框内有一个Analyzer Tasks页,右上角点击开始按钮,会在下方列出Detect Leaked Activities
5. 进一步分析Reference Tree页中,最靠前的几项,基本能定位到泄露的地方。

Activity内存泄露问题,也可以通过adb shell dumpsys meminfo xxx.package.xxx后,观察下方ViewsActivities数目,是否一直在增加。

 

二、内存泄露原因分析

1. 慎用static关键字,注意static Context.
public class MainActivity extends Activity{    public static Context mContext;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mContext = this;    }}

        Context对象为静态的,那么Activity就无法正常销毁,会常驻内存。

【解决方法】1.使用Application的Context;2.慎用static关键字。
 

2. 单例模式导致内存的泄漏

        静态变量导致的内存泄漏太过明显,而单例模式带来的内存的泄漏容易被忽略。

public class SingleInstance {    private static volatile SingleInstance instance;    private Context context;    private SingleInstance(Context context) {         this.context = context;    }    public static SingleInstance getInstance(Context context) {        if(instance == null) {            synchronized(SingleInstance.class) {                if(instance == null) {                    instance = new SingleInstance(context);                }            }        }        return instance;     }}

        上例中,传入给单例对象的context如果是Activity的context,而单例对象是一个static对象,其生命周期与应用程序是一致的(也就是说,只有应用程序进程被杀掉了,static对象才会被销),该SingleInstance单例静态对象持有当前Activity的context,当MainActivity退出时,由于instance还继续只有其context引用,对造成系统无法销毁该Activity,从而造成内存泄漏。

【解决方法】1.使用应用程序的getApplicationContext(),静态对象的生命周期与应用程序的生命周期一致,故此不会导致内存泄漏;2.持有传入的context的弱引用。

public class SingleInstance {    private WeakReference<Context> weakContext;    private SingleInstance(Context context)    {         weakContext = new WeakReference<Context>(context);    }    ....}

 

3. 属性动画导致的内存泄漏

        从Android3.0开始,Google提供了属性动画,属性动画中有一类无限循环的动画,例:

mAnimator = ValueAnimator.ofFloat(DEGREE_START, DEGREE_END);mAnimator.setDuration(DURATION);mAnimator.setStartDelay(60);mAnimator.setRepeatCount(Animation.INFINITE);mAnimator.setRepeatMode(Animation.RESTART);mAnimator.start();

        如果在Activity中播放此类动画并且在onDestroy()方法中没有停止该动画,那么动画会一直循环下去,尽管在界面上已经无法看不到动画了,但这个时候Activity的View会被动画持有,而View又持有Activity,最终Activity无法释放。由于动画是无限循环的,会泄露当前的Activity。

【解决方法】适当的时机(如onDestroy()方法中)调用Animator.cancel()方法。

if (mAnimator != null) {    mAnimator.cancel();}

 

4. 非静态内部类持有外部类实例
public class MainActivity extends Activity  {      static Demo sInstance = null;      @Override      public void onCreate(BundlesavedInstanceState)      {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);          if (sInstance == null)          {             sInstance= new Demo();          }      }    class Demo      {          voiddoSomething()          {              System.out.print("doth.");          }      }  }  

        第一个MainActivity activity1实例创建时,sInstance会获得并一直持有activity1的引用。

【解决方法】WeakReference引用外部实例。

public class OutterClass  {      ......      ......      static class InnerClass      {          private final WeakReference<OutterClass> mOutterClassInstance;        ......          ......          final OutterClass outer = mOutterClassInstance.get();    }  }

 

5. 注册某个对象后未反注册

【解决方法】注册广播接收器、注册观察者等,在退出时需要进行反注册。
 

6. 资源对象没关闭造成的内存泄露

        资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于Java虚拟机内,还存在于Java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。

【解决方法】对于资源性对象在不使用的时候,应该立即调用它的close()函数,将其关闭掉,然后再置为null.在我们的程序退出时一定要确保我们的资源性对象已经关闭。
 

7. Handler类、HandlerThread类使用不当

        Handler类,Handler通过发送Message与其他线程交互,Message发出之后是存储在目标线程的MessageQueue中的,而有时候Message也不是马上就被处理的,可能会驻留比较久的时间。
        在Message类中存在一个成员变量target,它强引用了handler实例,如果Message在Queue中一直存在,就会导致handler实例无法被回收,如果handler对应的类是非静态内部类,则会导致外部类实例(Activity或者Service)不会被回收,这就造成了外部类实例的泄露。

        HandlerThread实现的run方法是一个无限循环,它不会自己结束,线程的生命周期超过了activity生命周期,当横竖屏切换,HandlerThread线程的数量会随着activity重建次数的增加而增加。

public classMainActivity extends Activity  {      @Override      public void onCreate(BundlesavedInstanceState)    {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Thread mThread = newHandlerThread("demo", Process.THREAD_PRIORITY_BACKGROUND);        mThread.start();        MyHandler mHandler = new MyHandler(mThread.getLooper());        ....    }    @Override    public void onDestroy()      {          super.onDestroy();      }  }  

【解决方法】应该在onDestroy时将线程停止掉:mThread.getLooper().quit()。对于不是HandlerThread的线程,也应该确保activity消耗后,线程已经终止,可以这样做:在onDestroy时调用mThread.join()。
 

8. 线程、AsyncTask类造成内存泄露

        Runnable和AsyncTask都可以是一个匿名内部类,它们对当前的Activity都有一个隐式引用。如果Activity在销毁前,任务未完成,将导致Activity的内存资源无法回收。

【解决方法】同样是使用静态内部类来解决。
 

9. 构造Adapter时,没有使用缓存的convertView

public View getView(intposition, View convertView, ViewGroup parent)

        getView()为ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的listitem。这个构造过程就是由getView()方法完成的,getView()的第二个形参convertView就是被缓存起来的listitem的view对象(初始化时缓存中没有view对象则convertView是null)。

public View getView(intposition, View convertView, ViewGroup parent) {      View view = null;      if (convertView != null){          view = convertView;          populate(view, getItem(position));      } else {          view = new Xxx(...);      }      return view;  }  

 

10. Bitmap使用不当

(1) 及时的销毁
        系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过Java堆的限制。因此,在用完Bitmap时,要及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉。

(2) 设置一定的采样率

BitmapFactory.Options options = newBitmapFactory.Options();    options.inSampleSize = 2;

(3) 运用软引用(SoftRefrence)
        我们使用Bitmap后没有保留对它的引用,因此就无法调用Recycle函数。这时候巧妙的运用软引用,可以使Bitmap在内存快不足时得到有效的释放。

SoftReference<Bitmap> bitmap_ref = new SoftReference<Bitmap>(BitmapFactory.decodeStream(inputstream));....if (bitmap_ref.get() != null) {    bitmap_ref.get().recycle();  }

 

11. 及时消除集合类引用

        通常会把一些对象的引用加入到了集合中,当我们不需要该对象时,如果没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,可能更严重。长时间地使用手机才能出现OOM。

【解决方法】集合类对普通对象的引用,在不需要时要及时从集合类中消除引用。
 
 

参考:
http://blog.csdn.net/gemmem/article/details/13017999
http://www.cnblogs.com/yejiurui/archive/2013/02/23/2923418.html
http://www.kancloud.cn/digest/itfootballprefermanc/100913

0 0
原创粉丝点击