android安全(十二)Android 内存泄露总结

  • A+
所属分类:移动安全

Android中常见的内存泄露及解决方案


集合类泄露

如果某个集合是全局性的变量(比如 static 修饰),集合内直接存放一些占用大量内存的对象(而不是通过弱引用存放),那么随着集合 size 的增大,会导致内存占用不断上升,而在 Activity 等销毁时,集合中的这些对象无法被回收,导致内存泄露。比如我们喜欢通过静态 HashMap 做一些缓存之类的事,这种情况要小心,集合内对象建议采用弱引用的方式存取,并考虑在不需要的时候手动释放。


单例造成的内存泄露

单例的静态特性导致其生命周期同应用一样长。

有时创建单例时如果我们需要Context对象,如果传入的是Application的Context那么不会有问题。如果传入的是Activity的Context对象,那么当Activity生命周期结束时,该Activity的引用依然被单例持有,所以不会被回收,而单例的生命周期又是跟应用一样长,所以这就造成了内存泄露。

解决办法一:在创建单例的构造中不直接用传进来的context,而是通过这个context获取Application的Context。代码如下:

public class AppManager {    private static AppManager instance;    private Context context;    private AppManager(Context context) {        this.context = context.getApplicationContext();// 使用Application 的context
   }    public static AppManager getInstance(Context context) {        if (instance != null) {
           instance = new AppManager(context);
       }        return instance;
   }
}

第二种解决方案:在构造单例时不需要传入 context,直接在我们的 Application 中写一个静态方法,方法内通过 getApplicationContext 返回 context,然后在单例中直接调用这个静态方法获取 context。


非静态内部类造成的内存泄露

在 Java 中,非静态内部类(包括匿名内部类,比如 Handler, Runnable匿名内部类最容易导致内存泄露)会持有外部类对象的强引用(如 Activity),而静态的内部类则不会引用外部类对象。

非静态内部类或匿名类因为持有外部类的引用,所以可以访问外部类的资源属性成员变量等;静态内部类不行。

因为普通内部类或匿名类依赖外部类,所以必须先创建外部类,再创建普通内部类或匿名类;而静态内部类随时都可以在其他外部类中创建。


WebView 的泄漏

Android 中的 WebView 存在很大的兼容性问题,有些 WebView 甚至存在内存泄露的问题。所以通常根治这个问题的办法是为 WebView 开启另外一个进程,通过 AIDL 与主进程进行通信, WebView 所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。


AlertDialog 造成的内存泄露

new AlertDialog.Builder(this)
       .setPositiveButton("Baguette", new DialogInterface.OnClickListener() {            @Override
           public void onClick(DialogInterface dialog, int which) {
               MainActivity.this.makeBread();
           }
       }).show();

DialogInterface.OnClickListener 的匿名实现类持有了 MainActivity 的引用;

而在 AlertDialog 的实现中,OnClickListener 类将被包装在一个 Message 对象中(具体可以看 AlertController 类的 setButton 方法),而且这个 Message 会在其内部被复制一份(AlertController 类的 mButtonHandler 中可以看到),两份 Message 中只有一个被 recycle,另一个(OnClickListener 的成员变量引用的 Message 对象)将会泄露!


解决办法:

  • Android 5.0 以上不存在此问题;

  • Message 对象的泄漏无法避免,但是如果仅仅是一个空的 Message 对象,将被放入对象池作为后用,是没有问题的;

  • 让 DialogInterface.OnClickListener 对象不持有外部类的强引用,如用 static 类实现;

  • 在 Activity 退出前 dismiss dialog

Drawable 引起的内存泄露

Android 在 4.0 以后已经解决了这个问题。这里可以跳过。

当我们屏幕旋转时,默认会销毁掉当前的 Activity,然后创建一个新的 Activity 并保持之前的状态。在这个过程中,Android 系统会重新加载程序的UI视图和资源。假设我们有一个程序用到了一个很大的 Bitmap 图像,我们不想每次屏幕旋转时都重新加载这个 Bitmap 对象,最简单的办法就是将这个 Bitmap 对象使用 static 修饰。

private static Drawable sBackground;@Overrideprotected 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);
}

但是上面的方法在屏幕旋转时有可能引起内存泄露,因为,当一个 Drawable 绑定到了 View 上,实际上这个 View 对象就会成为这个 Drawable 的一个 callback 成员变量,上面的例子中静态的 sBackground 持有 TextView 对象的引用,而 TextView 持有 Activity 的引用。当屏幕旋转时,Activity 无法被销毁,这样就产生了内存泄露问题。

该问题主要产生在 4.0 以前,因为在 2.3.7 及以下版本 Drawable 的 setCallback 方法的实现是直接赋值,而从 4.0.1 开始,setCallback 采用了弱引用处理这个问题,避免了内存泄露问题。


资源未关闭造成的内存泄露

  • BroadcastReceiver,ContentObserver 之类的没有解除注册

  • Cursor,Stream 之类的没有 close

  • 无限循环的动画在 Activity 退出前没有停止

  • 一些其他的该 release 的没有 release,该 recycle 的没有 recycle… 等等。

总结

我们不难发现,大多数问题都是 static 造成的!

  • 在使用 static 时一定要小心,关注该 static 变量持有的引用情况。在必要情况下使用弱引用的方式来持有一些引用

  • 在使用非静态内部类时也要注意,毕竟它们持有外部类的引用。(使用 RxJava 的同学在 subscribe 时也要注意 unSubscribe)

  • 注意在生命周期结束时释放资源

  • 使用属性动画时,不用的时候请停止(尤其是循环播放的动画),不然会产生内存泄露(Activity 无法释放)(View 动画不会)

几种内存检测工具的介绍

  • Memory Monitor

  • Allocation Tracker

  • Heap Viewer

  • LeakCanary

Memory Monitor

位于 Android Monitor 中,该工具可以:

  • 方便的显示内存使用和 GC 情况

  • 快速定位卡顿是否和 GC 有关

  • 快速定位 Crash 是否和内存占用过高有关

  • 快速定位潜在的内存泄露问题(内存占用一直在增长)

  • 但是不能准确的定位问题


Allocation Tracker

该工具用途:

  • 可以定位代码中分配的对象类型、大小、时间、线程、堆栈等信息

  • 可以定位内存抖动问题

  • 配合 Heap Viewer 定位内存泄露问题(可以找出来泄露的对象是在哪创建的等等)

使用方法:在 Memory Monitor 中有个 Start Allocation Tracking 按钮即可开始跟踪 在点击停止跟踪后会显示统计结果。


Heap Viewer

该工具用于:

  • 显示内存快照信息

  • 每次 GC 后收集一次信息

  • 查找内存泄露的利器

使用方法: 在 Memory Monitor 中有个 Dump Java Heap 按钮,点击即可,在统计报告左上角选按 package 分类。配合 Memory Monitor 的 initiate GC(执行 GC)按钮,可检测内存泄露等情况。


LeakCanary

重要的事情说三遍:

       for (int i = 0; i < 3; i++) {
           Log.e(TAG, "检测内存泄露的神器!");
       }

LeakCanary 具体使用不再赘述,自行 Google。



一如既往的学习,一如既往的整理,一如即往的分享。感谢支持android安全(十二)Android 内存泄露总结

android安全(十二)Android 内存泄露总结


android安全(十二)Android 内存泄露总结

2020hw系列文章整理(中秋快乐、国庆快乐、双节快乐)

HW中如何检测和阻止DNS隧道

app安全之反编译(一)

Android安全(二)—-攻击框架drozer全功能介绍

Android安全(三)—so注入(inject)

Android安全(四)--数据库 之 SQLite数据库

Android安全(五)--查看APK的签名的方法

Android安全(六)--apk加固原理



扫描关注LemonSec

android安全(十二)Android 内存泄露总结


android安全(十二)Android 内存泄露总结


本文始发于微信公众号(LemonSec):android安全(十二)Android 内存泄露总结

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: