Android性能优化(1)—内存泄漏

2018-06-28  本文已影响0人  leey111

注:本文将会使用 Memory Profiler 进行内存检测

接下来将按以下四个方面来记录和总结一下内存泄漏:

  • 什么是内存泄漏
  • 内存泄漏会导致什么后果
  • 三个栗子
  • 总结

什么是内存泄漏

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费。


内存泄漏会导致什么后果

1.频繁 GC

2. OOM


三个栗子

1. Handler 内存泄漏

public class MemoryLeakageOneActivity extends AppCompatActivity {
    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //模拟界面销毁还存在未处理的消息
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {

            }
        }, 1000000000);
    }
}
Handler内存泄漏.png

根据上述『App操作流程』来看『MemoryLeakageOneActivity』 已经销毁了本应该不出现在堆内存里面。但是根据上图『Handler内存泄漏.png』来看『MemoryLeakageOneActivity』依然存在于堆内存中,因此对于本应用来说『MemoryLeakageOneActivity』 就已经产生了 内存泄漏

产生的原因:由于非静态内部类和匿名类内部类都会潜在持有它们所属的外部类的引用,但是静态内部类却不会。所以当『MemoryLeakageOneActivity』销毁时,未处理的消息持有 Handler 的引用,而 Handler 又持有它所属的外部类也就是『MemoryLeakageOneActivity』的引用。引用关系会一直保持直到消息得到处理,这样阻止了『MemoryLeakageOneActivity』被垃圾回收器回收,从而造成了内存泄漏。

如何解决 Handler 内存泄漏

public class MemoryLeakageOneActivity extends AppCompatActivity {
     @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //模拟界面销毁还存在未处理的消息
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {

            }
        }, 1000000000);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //移除Handler中所有消息
        mHandler.removeCallbacksAndMessages(null);
        mHandler = null;
    }
}

在『onDestroy』方法中移除 Handler 所有未处理的消息并将 Handler 置 null 。

接下来我们再次按照上述『App操作流程』『Memory Profiler操作流程』操作一遍后会发现堆内存中已经看不到『MemoryLeakageOneActivity』了。

解决Handler内存泄漏.png

根据上图『解决Handler内存泄漏.png』来看『MemoryLeakageOneActivity』已经从堆内存中释放掉了,由此也可以看出已经解决了之前『MemoryLeakageOneActivity』所产生的内存泄漏问题。

小结:

    1. 在界面销毁时应该将 Handler 未处理的消息进行移除。
    1. 在界面销毁时若 Handler 为匿名内部类,将 Handler 置 null ,因为匿名内部类会潜在持有它所属的外部类的引用。

2. 本类实例被其他类持有所导致内存泄漏

public class MemoryLeakageTwoActivity extends AppCompatActivity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //将本类的实例传给 MemoryLeakageMainActivity 类
        MemoryLeakageMainActivity.setActivity(this);
    }
}
public class MemoryLeakageMainActivity extends AppCompatActivity {
    private static Activity mActivity;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.memoryleakage_activity_main);
        startActivity(new Intent(this, MemoryLeakageTwoActivity.class));
    }

    public static void setActivity(Activity activity) {
        mActivity = activity;
    }
}

按照 『Handler内存泄漏中的「App操作流程」「Memory Profiler操作流程」』操作一遍后。

本类实例被其他类持有所导致内存泄漏.png

根据上图『本类实例被其他类持有所导致内存泄漏.png』来看『MemoryLeakageTwoActivity 』依然存在于堆内存中,因此对于本应用来说『MemoryLeakageTwoActivity 』 就已经产生了 内存泄漏

产生的原因:当『MemoryLeakageTwoActivity 』销毁时,由于『MemoryLeakageTwoActivity 』对象实例被『MemoryLeakageMainActivity』对象所引用,所以阻止了『MemoryLeakageTwoActivity 』被垃圾回收器回收,从而导致内存泄漏。

注:此处还有一个的地方需要注意,那就是 private static Activity mActivity; 由于被 static 修饰了所以导致 mActivity 所引用的实例生命周期和应用的生命周期一样长。

如何解决本类实例被其他类持有所导致内存泄漏

public class MemoryLeakageMainActivity extends AppCompatActivity {
    private static SoftReference<Activity> mActivity;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.memoryleakage_activity_main);
        startActivity(new Intent(this, MemoryLeakageTwoActivity.class));
    }
 
    public static void setActivity(Activity activity) {
         //使用软引用方式来引用外部类的实例对象
        mActivity = new SoftReference<>(activity);
    }
}

使用 软引用 方式来引用『MemoryLeakageTwoActivity』类的实例对象。( 软引用: GC 触发时会回收软引用指向的对象)

接下来继续按照 『Handler内存泄漏中的「App操作流程」「Memory Profiler操作流程」』操作一遍后。

解决本类实例被其他类持有所导致内存泄漏.png

根据上图『解决本类实例被其他类持有所导致内存泄漏.png』来看『MemoryLeakageTwoActivity』已经从堆内存中释放掉了,由此也可以看出已经解决了之前『MemoryLeakageTwoActivity』所产生的内存泄漏问题。

小结:

    1. 使用软引用方式来引用外部类的实例对象。

注:这里需要注意的地方是使用软引用时所引用的对象如果没有存在于堆内存当中那么通过软引用对象的 get() 方法进行获得软引用所引用的对象时将会为 null


3. 非静态内部类创建静态实例所导致内存泄漏

在频繁的启动Activity中,为了避免重复创建相同的数据资源,我们可能会创建一个静态的数据资源的实例,来解决重复创建数据资源的问题。

public class MemoryLeakageThreeActivity extends AppCompatActivity {
    private static MemoryLeakageResource mResource = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (mResource == null) {
            mResource = new MemoryLeakageResource();
        }
    }

    private class MemoryLeakageResource {

    }
}

接下来继续按照 『Handler内存泄漏中的「App操作流程」「Memory Profiler操作流程」』操作一遍后。

非静态内部类创建静态实例所导致内存泄漏.png

根据上图『非静态内部类创建静态实例所导致内存泄漏.png』来看『MemoryLeakageThreeActivity 』和『MemoryLeakageResource 』都存在于堆内存中。对于我们的 应用场景 来看应当只有『MemoryLeakageResource 』存在于堆内存中,因此对于本应用来说『MemoryLeakageThreeActivity 』 就已经产生了 内存泄漏

产生的原因:当『MemoryLeakageThreeActivity 』创建『MemoryLeakageResource 』对象实例时,由于『MemoryLeakageResource 』对象是非静态内部类因此就潜在的持有『MemoryLeakageThreeActivity 』对象的引用。又由于静态变量的生命周期和应用的生命周期一样长,所以当我们再将『MemoryLeakageResource 』对象引用赋值给一个静态变量时从而就导致了『MemoryLeakageResource 』对象无法被回收,而『MemoryLeakageResource 』对象又持有着『MemoryLeakageThreeActivity 』对象的引用。从而也就导致『MemoryLeakageThreeActivity 』对象无法被回收。

如何解决非静态内部类创建静态实例所导致内存泄漏

public class MemoryLeakageThreeActivity extends AppCompatActivity {
    private static MemoryLeakageResource mResource = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (mResource == null) {
            mResource = new MemoryLeakageResource();
        }

    }
    //将内部类改为静态内部类
    private static class MemoryLeakageResource {

    }
}

将『MemoryLeakageResource』内部类改为静态内部类。
接下来继续按照 『Handler内存泄漏中的「App操作流程」「Memory Profiler操作流程」』操作一遍后。

解决非静态内部类创建静态实例所导致内存泄漏.png

根据上图『解决非静态内部类创建静态实例所导致内存泄漏.png』来看『MemoryLeakageThreeActivity 』已经从堆内存中释放掉了而『MemoryLeakageResource 』对象依然存在于堆内存,从而既满足了之前 应用场景中 提到的复用『MemoryLeakageResource 』对象又解决了之前『MemoryLeakageThreeActivity 』所产生的内存泄漏问题。

小结:

    1. 非静态内部类会潜在持有它们所属的外部类的引用,但是静态内部类却不会。
    1. 静态对象的实例和应用的生命周期一样长。

总结

注:本文只举了一部分内存泄漏的栗子以及解决方式,还有其他很多种导致内存泄漏情况例如使用了 BraodcastReceiverContentObserverFileCursorStreamBitmap 等资源应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,从而造成内存泄漏等等。简单点来说内存泄漏就是由于外部的引用从而阻止了本该被回收器回收的对象。


感谢

常见的内存泄漏原因及解决方法

上一篇下一篇

猜你喜欢

热点阅读