Android常见内存泄漏及解决方案

Android内存泄漏方式及解决方案

常见内存泄漏

1、非静态内部类/匿名类默认持有外部类的引用(例如:Handler、AsyncTask、Thread)
  • 解析(匿名类[Handler、Thread] 非静态内部类[AsyncTask])
public class TestActivity extends Activity{
    
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    };
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
         // ① 匿名线程持有 Activity 的引用,进行耗时操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(50000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        
        // ② 使用匿名 Handler 发送耗时消息
        Message message = Message.obtain();
        mHandler.sendMessageDelayed(message, 60000);
        
        // ③ 非静态内部类 MyAscnyTask 进行耗时操作
        new MyAscnyTask().execute();
        
    }  
    
    class MyAscnyTask extends AsyncTask<Void, Integer, String>{
        @Override
        protected String doInBackground(Void... params) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "";
        }
    }
}

上面通过在Activity中操作:

  • ① new 一个匿名线程进行耗时操作

  • ② new 一个匿名Handler发送一个延迟消息

  • ③ new 一个非静态MyAscnyTask进行耗时操作

当Activity被销毁后GC依然无法回收Activity,因为 ①匿名线程类、②匿名Handler(或者是非静态Handler)、③非静态MyAscnyTask 都会持有外部Activity的一个引用,生命周期较长的对象持有生命周期短的对象,导致GC无法回收造成内存泄漏。

而Handler为常见的线程间通信成员之一,主要用来发送和处理消息,其中在sendMessage发送消息时候最终会调用内部通过 enqueueMessage 方法将消息添加到消息队列 MessageQueue:

mHandler.sendMessage(msg);//发送消息,具体流程如下
  • sendMessage(Message msg)
  • endMessageDelayed(Message msg, long delayMillis)
  • sendMessageAtTime(Message msg, long uptimeMillis)
  • enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)

其中 enqueueMessage 源码如下

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
	// 1. 把 当前的Handler实例对象作为msg的target属性,这里可以看到Message持有了Handler的引用
	msg.target = this;
	// 2. 调用消息队列的enqueueMessage()
	// 即:Handler发送的消息,最终是保存到消息队列
	return queue.enqueueMessage(msg, uptimeMillis);
}
参考:https://www.jianshu.com/p/b4d745c7ff7a                                

可见在 enqueueMessage中Message持有了Handler的引用,而Handler持有TestActivity的引用,因此如果TestActivity被销毁,但是其引用一致存在于MessageQueue中的Message中,所以无法被GC回收,最终造成内存泄漏。

  • 解决方案

    • 匿名线程改为 继承 Thread 的静态内部类方式。

    • 匿名(非静态内部)类Handler改为继承 Handler的静态内部类方式(同时将外部类使用弱引用(WeakReference)方式提供给Handler),以及在 Activity 的 onDestroy() 方法中,移除所有的消息 mHandler.removeCallbacksAndMessages(null);

    • 非静态内部类改为静态内部类。

public class TestActivity extends Activity{
   
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
         // ① 线程更改为静态内部类线程
        new MyThread().start();
        
        // ② 匿名Handler更改为静态内部类Handler,并使用弱引用提供Context。
        Message message = Message.obtain();
        new MyHandler(this).sendMessageDelayed(message, 60000);
        
        // ③ 非静态内部类 MyAscnyTask 更改为静态内部类
        new MyAscnyTask().execute();  
    } 
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //Activity销毁时候清空Handler的消息和回调
        myHandler.removeCallbacksAndMessages(null);
    }
    
    //静态内部类线程
     static class MyThread extends Thread{
        @Override
        public void run() {
            super.run();
             try {
                    Thread.sleep(50000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        }
    }
    
    //静态内部类Handler
    static class MyHandler extends Handler {
        WeakReference<SplashActivity> weakReference;
        
        public MyHandler(SplashActivity activity) {
            weakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            }
        }
    } 

    //静态内部类 MyAscnyTask
    static class MyAscnyTask extends AsyncTask<Void, Integer, String>{
        @Override
        protected String doInBackground(Void... params) {
           try {
                Thread.sleep(50000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "";
         }
     }
}
2、单例模式持有外部类的引用
  • 解析
public class TestSingle {
    private static TestSingle testSingle = null;
    private Context context;
    
    private TestSingle(Context context) {
        this.context = context;
    }

    public static TestSingle newInstance(Context context) {
        if (testSingle == null) {
            synchronized (TestSingle.class) {
                if (testSingle == null) {
                    testSingle = new TestSingle(context);
                }
            }
        }
        return testSingle;
    }
}

上面为单例模式,其中需要传入Context作为构造单例实例的参数,如果Context是一个Activity,当Activity退出后,GC依然无法回收,因为单例持有此Activity的引用,直到进程退出,这样就造成内存泄漏。

  • 解决方案

    • 使用全局Application的Context取代(如果Context允许使用Application的Context的话)

      public class TestSingle {
      
          private static TestSingle testSingle = null;
          private Context context;
      
          private TestSingle(Context context) {
              //这里使用context.getApplicationContext()获取Application的Context
              this.context = context.getApplicationContext();
          }
      
          public static TestSingle newInstance(Context context) {
              if (testSingle == null) {
                  synchronized (TestSingle.class) {
                      if (testSingle == null) {
                          testSingle = new TestSingle(context);
                      }
                  }
              }
              return testSingle;
          }
      }
      
      
    • 如果Context不可使用Application的Context替代,那么使用弱引用方式来提供Context

      public class TestSingle {
      
          private static TestSingle testSingle = null;
          WeakReference<Context> weakReference;
      
          private TestSingle(Context context) {
              //使用弱引用方式来提供Context
              weakReference = new WeakReference<>(context);
          }
      
          public static TestSingle newInstance(Context context) {
              if (testSingle == null) {
                  synchronized (TestSingle.class) {
                      if (testSingle == null) {
                          testSingle = new TestSingle(context);
                      }
                  }
              }
              return testSingle;
          }
          
          public void test(){
              //weakReference.get() TODO...
          } 
      }
      
3、静态成员变量持有外部类的引用
  • 解析
public class B{
    private static Activity mActivity;
    public static b(Activity activity){
        this.mActivity = activity;
        //mActivity TODO...
    } 
}

如上:B类中的静态成员变量mActivity持有了外部类Activity的引用,因此这个静态成员变量会一致存在进程中,造成内存泄漏。

  • 解决方案

    • 取消类中静态成员变量引用,方法中直接使用外部类

      public class B{
          public static b(Activity activity){
               //activity TODO...
          } 
      }
      
    • 如果非要引用外部类,使用弱引用方式提供

      public class B{
          private WeakReference weakRefrence;
          
          public B(Activity activity){
              //使用弱引用方式提供Activity
              weakRefrence = new WeakReference<>(activity);
          }
          
          public void b(){
          	Activity activity = weakRefrence.get();
          	//activity TODO...  
          }  
      }
      
4、集合类
  • 解析

    static List<Object> objectList = new ArrayList<>();
       for (int i = 0; i < 10; i++) {
           Object obj = new Object();
           objectList.add(obj);
           obj = null;
        }
    // 虽释放了集合元素引用的本身:obj=null
    // 但集合List 仍然引用该对象,故垃圾回收器GC 依然不可回收该对象
    

    上面为一个循环每次new一个对象添加到静态集合List中,因为静态变量生命周期一直会维持到进程结束,因此他内部所引用的对象也不能释放,这样便造成了内存泄漏。

  • 解决方案

    在集合元素使用之后从集合中删除,等所有元素都使用完之后,将集合置空。

    objectList.clear();
    objectList = null;
    
5、资源对象使用后未关闭
  • 解析

    • 网络、文件等流忘记关闭
    • 手动注册广播时,退出时忘记 unregisterReceiver()
    • Service 执行完后忘记 stopSelf()
    • EventBus 等观察者模式的框架忘记手动解除注册
  • 解决方案

    • 对于 广播BraodcastReceiver

      注销注册 unregisterReceiver()

    • 对于 文件流File

      关闭流 InputStream / OutputStream.close()

    • 对于数据库游标cursor

      使用后关闭游标 cursor.close()

    • 对于 图片资源Bitmap

      当它不再被使用时,应调用recycle()回收此对象的像素所占用的内存;最后再赋为null

      Bitmap.recycle(); 
      Bitmap = null;
      
  • ListView 的 Item 泄露

  • WebView造成的内存泄漏

6、其他情况

除了上述常见情况,还有一些日常的使用会导致内存泄露

主要包括:ContextWebViewAdapter,具体介绍如下

img

总结

下面,我将用一张图总结Android中内存泄露的原因 & 解决方案

示意图

内存泄露的分析工具

哪怕完全了解 内存泄露的原因,但难免还是会出现内存泄露的现象

下面将简单介绍内存泄露的分析工具

Android Lint

Lint 是 Android Studio 提供的 代码扫描分析工具,它可以帮助我们发现代码机构 / 质量问题,同时提供一些解决方案,检测内存泄露当然也不在话下,使用也是非常的简单,可以参考下这篇文章:Android 性能优化:使用 Lint 优化代码、去除多余资源

Leakcanary

LeakCanary 是 Square 公司开源的「Android 和 Java 的内存泄漏检测库」,Square 出品,必属精品,功能很强大,使用也很简单。建议直接看 Github 上的说明:leakcanary,也可以参考这篇文章:Android内存优化(六)LeakCanary使用详解 或者 LeakCanary: 让内存泄露无所遁形

参考资料

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值