一、不同生命周期导致的内存泄漏
前面有分析了内存泄漏的原因,本该被回收的对象被占用,得不到回收便会内存泄漏。总归到底的原因还是对象引用在类之间传递,它们的生命周期不同,导致回收时发生问题。
举个简单的例子:
当单列模式中传入的Activity是,ToastRouter便持有了MainActivity的强引用,当MainActivity结束时,便得不到回收,这是内存泄漏发生了
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ToastRouter.getInstance(this);
}
}
...
public class ToastRouter {
private static final ToastRouter ourInstance = new ToastRouter();
private Context mContext;
public static ToastRouter getInstance(Context context) {
this.mContext=context;
return ourInstance;
}
private ToastRouter() {
}
}
解决办法
- 尽量避免生命周期不对等的对象引用传递,如果避免不了,应用对等生命周期的对象替代。如上Activity传入,可以用ApplicationContext传递代替,因为ApplicationContext的生命周期与APP对等。
- 与对象生命周期绑定释放,如上例子,应该在MainActivity onDestary()方法中释放传入对象引用
二、非静态内部类持有对象导致的内存泄漏
非静态内部类持有对象导致的内存泄漏也很好理解,就是内部类持有了外部类的对象,导致的外部类回收失败造成的内存泄漏
1. 非静态内部类调用外部类的方法的
- 看一个简单的JAVA代码案例
- 通过匿名内部类,内部类分别打印一个延时log日志。
- 其中分别用的AsyncTask和Handler举例
/**
* 通过匿名内部类,打印一个1.5s延时log
*/
public void doAnonymousInnerClassTask() {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
//睡眠1.5s
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
logMsg(" AsyncTask 匿名内部类 doInBackground");
return null;
}
}.execute();
}
/**
* 通过内部类,打印一个1.5s延时log
*/
public void doNormalInnerTask() {
TestAsyncTask testAsyncTask = new TestAsyncTask();
testAsyncTask.execute();
}
private class TestAsyncTask extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... voids) {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
logMsg(" TestAsyncTask内部类 doInBackground");
return null;
}
}
private void logMsg(String msg) {
Log.d(TAG, msg);
}
-
我们通过.class文件,查看它的对象引用发现,在匿名内部类和内部类中,调用MainActivity的logMsg方法时,已持有了MainActivity.this的引用,因此当非静态内部类生命周期比MainActivity长时,即发生了内存泄漏。
public void doAnonymousInnerClassTask() { (new AsyncTask<Void, Void, Void>() { protected Void doInBackground(Void... voids) { try { Thread.sleep(1500L); } catch (InterruptedException var3) { var3.printStackTrace(); } MainActivity.this.logMsg(" AsyncTask 匿名内部类 doInBackground"); return null; } }).execute(new Void[0]); } private class TestAsyncTask extends AsyncTask<Void, Integer, Void> { private TestAsyncTask() { } protected Void doInBackground(Void... voids) { try { Thread.sleep(1500L); } catch (InterruptedException var3) { var3.printStackTrace(); } MainActivity.this.logMsg(" TestAsyncTask内部类 doInBackground"); return null; } }
2. 内部类是如何持有外部类对象?
上述通过.class文件可以看到内部类在调用外部类的方法时,通过持有的外部类对象去调用。那么外部类对象的引用是什么时候传入的呢。通过
参考文章:深入理解Java中为什么内部类可以访问外部类的成员得到结论:
- 编译器自动为内部类添加一个成员变量, 这个成员变量的类型和外部类的类型相同, 这个成员变量就是指向外部类对象的引用;
- 编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型, 在构造方法内部使用这个参数为1中添加的成员变量赋值;
- 在调用内部类的构造函数初始化内部类对象时, 会默认传入外部类的引用。
由此,即使内部类并没有调用外部类的方法或者变量,也一样会持有外部类的引用。
3. 如何处理非静态内部类内存泄漏问题
可以使用,但是前提是确保非静态内部类的生命周期超过外部类
-
使用静态内部类,在静态内部类需要持有外部类引用时,通过关联外部类的弱引用去调用。
/** * 通过静态内部类打印一个1.5s延时log */ public void doStaticInnerClassTask() { mHandler.sendEmptyMessageDelayed(0, 1500); } private final StaticHandler mHandler = new StaticHandler(this); private static class StaticHandler<T> extends Handler { protected WeakReference<T> ref; public StaticHandler(T cls) { ref = new WeakReference<T>(cls); } public T getRef() { return ref != null ? ref.get() : null; } @Override public void handleMessage(Message msg) { super.handleMessage(msg); MainActivity _Activity = (MainActivity) ref.get(); _Activity.logMsg("StaticHandler 静态内部类log "); } }
在外部类生命周期结束前自主释放外部类的引用