Android技术分享| Android 中部分内存泄漏示例及解决方案

Posted anyRTC

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android技术分享| Android 中部分内存泄漏示例及解决方案相关的知识,希望对你有一定的参考价值。

简单介绍内存泄漏&内存抖动

内存泄漏

举例:

请注意以下的例子是虚构的

内存抖动

源自android文档中的Memory churn一词,中文翻译为内存抖动。
指快速频繁的创建对象从而产生的性能问题。

引用Android文档原文:

内存泄漏(Memory leak)的产生和避免方式

Java内存泄漏的根本原因是长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏。

尽管短生命周期对象已经不再需要,但因为长生命周期依旧持有它的引用,故不能被回收而导致内存泄漏。

几种引起内存泄漏的问题:

静态集合类引起的内存泄漏

HashMapArrayList等集合以静态形式声明时,这些静态对象的生命周期与应用程序一致。他们所引用的对象也无法被释放,因为它们也被集合引用着。

private static HashMap<String, Object> a = new HashMap();
public static void main(String args[]) 
  for (int i = 0; i < 1000; i++) 
    Object tO = new Object();
    a.put("0", tO);
    tO = null;  
  

如果仅仅释放引用本身(tO = null),ArrayList依然在引用该对象,GC无法回收

监听器

在Java应用中,通常会用到很多监听器,一般通过addXXXXListener()实现。但释放对象时通常会忘记删除监听器,从而增加内存泄漏的风险。

各种连接

如数据库连接、网络连接(Socket)和I/O连接。忘记显式调用close()方法引起的内存泄漏

内部类和外部模块的引用

内部类的引用是很容易被遗忘的一种,一旦没有释放可能会导致一系列后续对象无法释放。此外还要小心外部模块不经意的引用,内部类是否提供相应的操作去除外部引用。

单例模式

由于单例的静态特性,使其生命周期与应用的生命周期一样长,一旦使用不恰当极易造成内存泄漏。如果单利持有外部引用,需要注意提供释放方式,否则当外部对象无法被正常回收时,会进而导致内存泄漏。

常见的内存泄漏处理方式:

集合类泄漏

如集合的使用范围超过逻辑代码的范围,需要格外注意删除机制是否完善可靠。比如由静态属性static指向的集合。

单利泄漏

以下为简单逻辑代码,只为举例说明内存泄漏问题,不保证单利模式的可靠性

public class AppManager 
  private static AppManager instance;
  private Context context;

  private AppManager(Context context) 
    this.context = context;
  

  public static AppManager getInstance(Context context) 
    if (instance == null) 
      instance = new AppManager(context);
    
    return instance;
  

AppManager创建时需要传入一个Context,这个Context的生命周期长短至关重要。

  1. 如果传入的是ApplicationContext,因为Application的生命周期等同于应用的生命周期,所以没有任何问题
  2. 如果传入的是ActivityContext,则需要考虑这个Activity是否在整个生命周期都不会被回收了,如果不是,则会造成内存泄漏

非静态内部类创建静态实例造成的内存泄漏

public class MyActivity extends AppCompatActivity 
  private static MyInnerClass mInnerClass = null;

  @Override
  protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    ...

    if (mInnerClass == null) 
      mInnerClass = new MyInnerClass();
    
  

  class MyInnerClass 
    ...
  

内部类持有外部类引用,而static声明的对象声明周期通常会比Activity长。即使关闭这个页面,由于mInnerClass为静态的,并且持有MyActivity的引用,导致无法回收此页面从而引起内存泄漏

应该将该内部类单独封装为一个单例来使用。

匿名内部类/异步线程

public class MyActivity extends AppCompatActivity 
  @Override
  protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    ...

    new Thread(new Runnable() 
      @Override
      public void run() 
        ...
      
    ).start();
  

Runnable都使用了匿名内部类,将持有MyActivity的引用。如果任务在Activity销毁前未完成,将导致Activity的内存无法被回收,从而造成内存泄漏

解决方法:将Runnable独立出来或使用静态内部类,可以避免因持有外部对象导致的内存泄漏

Handler造成的内存泄漏

public class SampleActivity extends AppCompatActivity 
  private final Handler mHandler = new Handler() 
    @Override
    public void handleMessage(Message msg) 
      ...
    
  

  @Override
  protected void onCreate(Bundle savedInstanceState) 
    ...

    mHandler.postDelayed(new Runnable() 
      @Override
      public void run() 
        ...
      
    , 300000);

    finish();
  

Handler属于TLS(Thread Local Storage)变量,生命周期与Activity是不一致的,容易导致持有的对象无法正确被释放

当Android应用程序启动时,该应用程序的主线程会自动创建一个Looper对象和与之关联的MessageQueue。

当主线程中实例化一个Handler对象后,它就会自动与主线程Looper的MessageQueue关联起来。所有发送到MessageQueue的Messag都会持有Handler的引用,所以Looper会据此回调Handle的handleMessage()方法来处理消息。只要MessageQueue中有未处理的Message,Looper就会不断的从中取出并交给Handler处理。

另外,主线程的Looper对象会伴随该应用程序的整个生命周期。

在Java中,非静态内部类和匿名类内部类都会潜在持有它们所属的外部类的引用,但是静态内部类却不会。

当该 Activity 被 finish() 掉时,延迟执行任务的 Message 还会继续存在于主线程中,它持有该 Activity 的 Handler 引用,所以此时 finish() 掉的 Activity 就不会被回收了从而造成内存泄漏(因 Handler 为非静态内部类,它会持有外部类的引用,在这里就是指 SampleActivity)。

解决方法:在 Activity 中避免使用非静态内部类,比如上面我们将 Handler 声明为静态的,则其存活期跟 Activity 的生命周期就无关了。同时通过弱引用的方式引入 Activity,避免直接将 Activity 作为 context 传进去,见如下代码:

public class SampleActivity extends AppCompatActivity 

  private static class MyHandler extends Handler 
    private final WeakReference<SampleActivity> mActivity;

    public MyHandler(SampleActivity activity) 
      mActivity = new WeakReference<SampleActivity>(activity);
    

    @Override
    public void handleMessage(Message msg) 
      SampleActivity activity = mActivity.get();
      if (activity != null) 
        ...
      
    
  
  private final MyHandler mHandler = new MyHandler(this);

  private static final Runnable mRunnable = new Runnable() 
    @Override
    public void run()  ... 
  

  @Override
  protected void onCreate(Bundle savedInstanceState) 
    ...

    mHandler.postDelayed(mRunnable, 300000);
    finish();
  
避免不必要的静态成员变量

对于BroadcastReceiver、ContentObserver、File、Cursor、Stream、Bitmap等资源的使用,应在Activity销毁前及时关闭或注销

不使用WebView对象时,应调用destroy()方法销毁

以上是关于Android技术分享| Android 中部分内存泄漏示例及解决方案的主要内容,如果未能解决你的问题,请参考以下文章

Android技术分享|Android 中部分内存泄漏示例及解决方案

android如何改变editText控件中部分文字的格式

Android文件下载之进度检测

android可滑动收起展开布局

Android技术分享收集

Android BLE 库分享