本文的思维导图(这里使用的是MindNode)
目录
检测工具
单例引起的内存泄漏
[非静态内部类/匿名内部类
引起的内存泄露](#非静态内部类/匿名内部类
引起的内存泄露)其他内存问题和优化
小结
附录
检测工具
内存泄漏本来是一个很可怕的问题, 因为对于具有GC(Garbage Collection)机制的Java来说, 其开发者对内存的关注远没有C/C++开发者那么高, 内存泄漏听起来很遥远也很陌生
但是得益于Square出品的leakcanary让android下的内存泄漏检测变得轻松而简单
Square是干什么的? 我只想说它一不小心还出品过: okhttp retrofit picasso dagger, 其他就不解释了
安装和使用非常简单, 只需要以下两步(详细参考leakcanary主页)
- 安装依赖, 编辑build.gradle
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
- 启动服务, 编辑或新建MyApplication.java
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}
搞定! 下面和往常一样使用你的app即可, 如果发生内存泄漏, leakcanary会报警并给出具体原因
单例引起的内存泄漏
单例模式是一个很简单但又很容易写错的模式
要辩的话, 请移步这里设计模式 之 Singleton(Java实现)
不仅容易写错, 而且还容易引起内存泄漏, 不信我们来看下面的例子
首先, 定义一个单例如下
public class Singleton {
private volatile static Singleton instance;
private Context context;
private Singleton(Context context) {
this.context = context;
}
public static Singleton getInstance(Context c) {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(c);
}
}
}
return instance;
}
}
接着在Activity中使用这个单例
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SingletonMemoryLeak();
}
private void SingletonMemoryLeak() {
Singleton singleton = Singleton.getInstance(this);
}
}
代码准备好之后, 我们来做个测试:
不断地做横竖屏切换(这里请关闭系统的方向锁定)
稍等片刻, leakcanary就报警啦
为什么会出现内存泄漏呢? 以下几个要素值得注意
Singleton实例的生命周期是和app一样的, 因为它是static的
Singleton实例引用了Activity context
所以当横竖屏切换时, Activity并没有及时的释放
那么如何解决内存泄漏呢? 答案就是使用app context
Singleton singleton = Singleton.getInstance(getApplicationContext());
非静态内部类/匿名内部类
引起的内存泄露
内部类在android开发中非常普遍, 但是使用稍有不当便又会出现问题, 我们来看下面的这个例子
首先, 我们创建非静态和静态的成员对象如下
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "msg " + msg);
}
};
private static class MyHandler extends Handler {
private final WeakReference mActivity;
public MyHandler(MainActivity activity) {
mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = mActivity.get();
if (activity != null) {
Log.d(TAG, "msg " + msg);
}
}
}
private final MyHandler sHandler = new MyHandler(this);
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() {
Log.d(TAG, "delay runnable");
}
};
// ...
}
如果懒得看这么长的代码, 只需要记住:
其中handler是非静态, 而mHandler和mRunnable是静态的
接着我们来使用定义好的Handler
测试1: 使用非静态成员对象和匿名成员对象
public class MainActivity extends AppCompatActivity {
// ...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
HandlerMemoryLeak();
}
private void HandlerMemoryLeak() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
Log.d(TAG, "delay runnable");
}
}, 1000 * 10);
}
}
这里的非静态成员对象是handler, 匿名成员对象是new Runnable()生成的对象
代码准备好之后, 我们仍然做横竖屏切换的测试, 结果内存又一次"光荣"泄漏
这里暂不解释, 我们先接着再做几个相同的测试
测试2: 使用非静态成员对象和静态成员对象
这里的非静态成员对象指的是handler, 而静态成员对象指的是sRunnable
private void HandlerMemoryLeak() {
handler.postDelayed(sRunnable, 1000 * 10);
}
同样的测试条件下, 还是出现了内存泄漏(leakcanary表现的原因和测试1完全一样)
测试3: 使用静态成员对象和匿名成员对象
这里的静态成员对象指的是sHandler, 而匿名成员对象指的是new Runnable()生成的对象
private void HandlerMemoryLeak() {
sHandler.postDelayed(new Runnable() {
@Override
public void run() {
Log.d(TAG, "delay runnable");
}
}, 1000 * 10);
}
同样的测试条件下, 还是出现了内存泄漏(leakcanary表现的原因和测试1和测试2完全一样)
测试了半天, 怎么都是泄漏呢? 这是因为
Java中非静态(non-static)内部类(handler)和匿名内部类(new Runnable())都会隐式地持有其外部类的引用
如果想要彻底解决泄漏, 那么需要只使用静态内部类, 正确的姿势如下
private void HandlerMemoryLeak() {
sHandler.postDelayed(sRunnable, 1000 * 10);
}
非静态内部类和匿名内部类的使用, 不仅仅出现在上述Handler使用中, 异步操作中也同样需要特别注意此问题
其他内存问题和优化
除了上述的内存泄漏, 还有其他一些内存问题和优化, 例如:
资源(如: DB等)打开后, 没有及时释放
没有合理使用复用引起的内存占用过大, 例如列表控件推荐使用RecyclerView
Bitmap没有优化, 导致内存吃紧
WebView使用独立进程以减少主进程内存占用
打开android:largeHeap以增加最大堆内存占用的优化(详细参考探究android:largeHeap)
小结
在android开发中, 解决了内存泄漏问题, 只是内存问题的第一步
下一步也是更困难的是对app做内存优化, 当然这又是另一个复杂而庞大的话题了
后续会带来分享, 敬请期待, 如有讨论或纠正, 欢迎留言, 谢谢
附录
How to Leak a Context: Handlers & Inner Classes
内存泄露从入门到精通三部曲
更多文章, 请支持我的个人博客