作为Android开发人员,我们或多或少都听说过内存泄漏。那么何为内存泄漏,Android中的内存泄漏又是什么样子的呢,本文将简单概括的进行一些总结。
关于内存泄露的定义,我可以理解成这样
没有用的对象无法回收的现象就是内存泄露
如果程序发生了内存泄露,则会带来如下的问题
在正式介绍内存泄露之前,我们有必要介绍一些必要的预备知识。
new
指令生成对象时,堆内存将会为此开辟一份空间存放该对象以上图为例,我们可以知道
上面的垃圾回收中,我们提到的两个概念,一个是GC根节点,另一个是强引用
在Java中,可以作为GC 根节点的有
提到强引用,有必要系统说一下Java中的引用类型。Java中的引用类型可以分为一下四种:
StringBuffer buffer = new StringBuffer();
就是buffer变量持有的为StringBuilder的强引用类型。补充了预备知识,我们就需要具体讲一讲Android中的内存泄漏了。
归纳而言,Android中的内存泄漏有以下几个特点:
在Android中的内存泄漏场景有很多,按照类型划分可以归纳为
此外,如果按照泄漏的程度,可以分为
在Android中,Activity是我们常用的组件,通常情况下,一个Activity会包含了一些复杂的UI视图,而视图中如果含有ImageView,则有可能会使用比较大的Bitmap对象。因而一个Activity持有的内存会相对很多,如果造成了Activity的泄漏,势必造成一大块内存无法回收,发生泄漏。
这里举个简单的例子,说明Activity的内存泄漏,比如我们有一个叫做AppSettings的类,它是一个单例模式的应用。
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class AppSettings { private Context mAppContext; private static AppSettings sInstance = new AppSettings(); //some other codes public static AppSettings getInstance() { return sInstance; } public final void setup(Context context) { mAppContext = context; } } |
---|
当我们传入Activity作为Context参数时,则AppSettings实例会持有这个Activity的实例。
当我们旋转设备时,Android系统会销毁当前的Activity,创建新的Activity来加载合适的布局。如果出现Activity被单例实例持有,那么旋转过程中的旧Activity无法被销毁掉。就发生了我们所说的内存泄漏。
想要解决这个问题也不难,那就是使用Application的Context对象,因为它和AppSettings实例具有相同的生命周期。这里是通过使用Context.getApplicationContext()
方法来实现。所以修改如下
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class AppSettings { private Context mAppContext; private static AppSettings sInstance = new AppSettings(); //some other codes public static AppSettings getInstance() { return sInstance; } public final void setup(Context context) { mAppContext = context.getApplicationContext(); } } |
---|
在Android中我们会使用很多listener,observer。这些都是作为观察者模式的实现。当我们注册一个listener时,这个listener的实例会被主题所引用。如果主题的生命周期要明显大于listener,那么就有可能发生内存泄漏。
以下面的代码为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class MainActivity extends AppCompatActivity implements OnNetworkChangedListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); NetworkManager.getInstance().registerListener(this); } @Override public void onNetworkUp() { } @Override public void onNetworkDown() { } } |
---|
上述代码处理的业务,可以理解为
又是单例模式,可知NetworkManager会持有MainActivity的实例引用,因而屏幕旋转时,MainActivity同样无法被回收,进而造成了内存泄漏。
对于这种类型的内存泄漏,解决方法是这样的。即在MainActivity的onDestroy方法中加入反注销的方法调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public class MainActivity extends AppCompatActivity implements OnNetworkChangedListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); NetworkManager.getInstance().registerListener(this); } @Override public void onNetworkUp() { } @Override public void onNetworkDown() { } @Override protected void onDestroy() { super.onDestroy(); NetworkManager.getInstance().unregisterListener(this); } } |
---|
在Java中,非静态内部类会隐式持有外部类的实例引用。想要了解更多,可以参考这篇文章细话Java:”失效”的private修饰符
通常情况下,我们会书写类似这样的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class SensorListenerActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SensorManager sensorManager = (SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE); sensorManager.registerListener(new SensorListener() { @Override public void onSensorChanged(int sensor, float[] values) { } @Override public void onAccuracyChanged(int sensor, int accuracy) { } }, SensorManager.SENSOR_ALL); } } |
---|
其中上面的SensorListner实例是一个匿名内部类的实例,也是非静态内部类的一种。因此SensorListner也会持有外部SensorListenerActivity的实例引用。
而SensorManager作为单例模式实现,其生命周期与Application相同,和SensorListner对象生命周期不同,有可能间接导致SensorListenerActivity发生内存泄漏。
解决这种问题的方法可以是
除了上面的三种场景外,Android的内存泄漏还有可能出现在以下情况
Activity.getSystemService()
使用不当,也会导致内存泄漏。想要解决内存泄漏无非如下两种方法
下面会简单介绍一些内存泄漏检测和解决的工具
detectAll()
或者detectActivityLeaks()
可以检测Activity的内存泄漏setClassInstanceLimit()
可以限定类的实例个数,可以辅助判断某些类是否发生了内存泄漏Android Memory Monitor内置于Android Studio中,用于展示应用内存的使用和释放情况。它大致长成这个样子
当你的App占用的内存持续增加,而且你同时出发GC,也没有进行释放,那么你的App很有可能发生了内存泄漏问题。
以上图做个例子,进行分析
上述的Retained Heap的大小获取是基于假设的,而现实在进行分析中不可能基于这种方法,那么实际上计算泄漏内存的大小的方法其实是这样的。
这里我们需要一个概念,就是Dominator Tree(统治者树)。
上图中
内存泄漏在App中很常见,需要我们花时间去解决。
处理内存泄漏问题,不仅要解决掉,更应该善于整理总结,做到后续编码中主动避免。
本文是我在droidcon beijing 2016和 GDG Beijing Devfest所做分享的文章总结版。如有问题,欢迎指出。