Android内存优化——常见内存泄露及优化方案
单例导致内存泄露
单例模式在Android开发中会经常用到,但是如果使用不当就会导致内存泄露。因为单例的静态特性使得它的生命周期同应用的生命周期一样长,如果一个对象已经没有用处了,但是单例还持有它的引用,那么在整个应用程序的生命周期它都不能正常被回收,从而导致内存泄露
public class AppSettings {
private static AppSettings sInstance;
private Context mContext;
private AppSettings(Context context) {
this.mContext = context;
}
public static AppSettings getInstance(Context context) {
if (sInstance == null) {
sInstance = new AppSettings(context);
}
return sInstance;
}
}
像上面代码中这样的单例,如果我们在调用getInstance(Context context)
方法的时候传入的context
参数是Activity
、Service
等上下文,就会导致内存泄露。
以Activity
为例,当我们启动一个Activity
,并调用getInstance(Context context)
方法去获取AppSettings
的单例,传入Activity.this
作为context
,这样AppSettings
类的单例sInstance
就持有了Activity
的引用,当我们退出Activity
时,该Activity
就没有用了,但是因为sIntance
作为静态单例(在应用程序的整个生命周期中存在)会继续持有这个Activity
的引用,导致这个Activity
对象无法被回收释放,这就造成了内存泄露。
为了避免这样单例导致内存泄露,我们可以将context
参数改为全局的上下文:
private AppSettings(Context context) {
this.mContext = context.getApplicationContext();
}
全局的上下文Application Context
就是应用程序的上下文,和单例的生命周期一样长,这样就避免了内存泄漏。
单例模式对应应用程序的生命周期,所以我们在构造单例的时候尽量避免使用Activity
的上下文,而是使用Application
的上下文。
静态变量导致内存泄露
静态变量存储在方法区,它的生命周期从类加载开始,到整个进程结束。一旦静态变量初始化后,它所持有的引用只有等到进程结束才会释放。
比如下面这样的情况,在Activity
中为了避免重复的创建info
,将sInfo
作为静态变量:
public class MainActivity extends AppCompatActivity {
private static Info sInfo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (sInfo != null) {
sInfo = new Info(this);
}
}
}
class Info {
public Info(Activity activity) {
}
}
Info
作为Activity
的静态成员,并且持有Activity
的引用,但是sInfo
作为静态变量,生命周期肯定比Activity
长。所以当Activity
退出后,sInfo
仍然引用了Activity
,Activity
不能被回收,这就导致了内存泄露。
在Android开发中,静态持有很多时候都有可能因为其使用的生命周期不一致而导致内存泄露,所以我们在新建静态持有的变量的时候需要多考虑一下各个成员之间的引用关系,并且尽量少地使用静态持有的变量,以避免发生内存泄露。当然,我们也可以在适当的时候讲静态量重置为null,使其不再持有引用,这样也可以避免内存泄露。
非静态内部类导致内存泄露
非静态内部类(包括匿名内部类)默认就会持有外部类的引用,当非静态内部类对象的生命周期比外部类对象的生命周期长时,就会导致内存泄露。
非静态内部类导致的内存泄露在Android开发中有一种典型的场景就是使用Handler
,很多开发者在使用Handler
是这样写的:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start();
}
private void start() {
Message msg = Message.obtain();
msg.what = 1;
mHandler.sendMessage(msg);
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
// 做相应逻辑
}
}
};
}
也许有人会说,mHandler
并未作为静态变量持有Activity
引用,生命周期可能不会比Activity
长,应该不一定会导致内存泄露呢,显然不是这样的!
熟悉Handler
消息机制的都知道,mHandler
会作为成员变量保存在发送的消息msg
中,即msg
持有mHandler
的引用,而mHandler
是Activity
的非静态内部类实例,即mHandler
持有Activity
的引用,那么我们就可以理解为msg
间接持有Activity
的引用。msg
被发送后先放到消息队列MessageQueue
中,然后等待Looper
的轮询处理(MessageQueue
和Looper
都是与线程相关联的,MessageQueue
是Looper
引用的成员变量,而Looper
是保存在ThreadLocal
中的)。那么当Activity
退出后,msg
可能仍然存在于消息对列MessageQueue
中未处理或者正在处理,那么这样就会导致Activity
无法被回收,以致发生Activity
的内存泄露。
通常在Android开发中如果要使用内部类,但又要规避内存泄露,一般都会采用静态内部类+弱引用的方式。
public class MainActivity extends AppCompatActivity {
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new MyHandler(this);
start();
}
private void start() {
Message msg = Message.obtain();
msg.what = 1;
mHandler.sendMessage(msg);
}
private static class MyHandler extends Handler {
private WeakReference<MainActivity> activityWeakReference;
public MyHandler(MainActivity activity) {
activityWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = activityWeakReference.get();
if (activity != null) {
if (msg.what == 1) {
// 做相应逻辑
}
}
}
}
}
mHandler
通过弱引用的方式持有Activity
,当GC执行垃圾回收时,遇到Activity
就会回收并释放所占据的内存单元。这样就不会发生内存泄露了。
上面的做法确实避免了Activity
导致的内存泄露,发送的msg
不再已经没有持有Activity
的引用了,但是msg
还是有可能存在消息队列MessageQueue
中,所以更好的是在Activity
销毁时就将mHandler
的回调和发送的消息给移除掉。
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
非静态内部类造成内存泄露还有一种情况就是使用Thread
或者AsyncTask
。
比如在Activity中直接new
一个子线程Thread
:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
// 模拟相应耗时逻辑
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
或者直接新建AsyncTask
异步任务:public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
// 模拟相应耗时逻辑
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}.execute();
}
}
Thread
和AsyncTask
都是匿名内部类对象,默认就隐式的持有外部Activity
的引用,导致Activity
内存泄露。要避免内存泄露的话还是需要像上面Handler
一样使用静态内部类+弱应用的方式(代码就不列了,参考上面Hanlder
的正确写法)。总结
内存泄露在Android内存优化是一个比较重要的一个方面,很多时候程序中发生了内存泄露我们不一定就能注意到,所有在编码的过程要养成良好的习惯。总结下来只要做到以下这几点就能避免大多数情况的内存泄漏:
构造单例的时候尽量别用Activity
的引用;
静态引用时注意应用对象的置空或者少用静态引用;
使用静态内部类+软引用代替非静态内部类;
及时取消广播或者观察者注册;
耗时任务、属性动画在Activity
销毁时记得cancel
;
文件流、Cursor
等资源及时关闭;Activity
销毁时WebView
的移除和销毁。
上一篇: crontab定时任务管理器
下一篇: [MVC] 综合案例:用户管理系统