1、Android的开发工具是java,这能帮助我们解决很底层的问题 包括:内存管理,平台依赖。然而,有时候项目依然会报OOM错误,so垃圾收集器在哪?
2、我主要研究一种情况:内存中较大对象很长一段时间内不能被释放。这方面并不完全算作内存溢出,对象会在某一时间点上被收集,so我们不屌它。虽然有时候他也会导致oom,所以不建议这么干滴。(这话咋说的这么矛盾,作者精分了?)
3、简单例子:
public class NewActivity extends Activity {
private Handler mHandler = new Handler();
private TextView mTextview;
@Override
protected void onCreate(android.os.Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.inflate_textview);
mTextview = (TextView) findViewById(R.id.xxwj_newtv4);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mTextview.setText("Done");
}
}, 80000);
};
}
4、这是一个非常基本的Activity,注意到匿名类Runnable被handler延迟执行了好多次,我们运行并且旋转屏幕多次,然后dump memory 分析它
5、现在内存里有很多Activity,这tm很不好啊,为啥gc没收了他们。
6、获取所有内存的Activity查询语句是在OQL(Object Query Language)创建的,非常简单粗暴。
7、能够看出,其中一个Activity是被this$0引用了,这是一个来自内部匿名类指向其主类的间接引用(OMG) this$0是被callback方法引用的,callback又被一堆next()方法引用到主线程。
8、任何时候你创建一个非静态内部类在你自己的类中,java会自动为其创建一个间接引用指向其主类。
9、只要你的handler调用了Runnable或Message,它将被存储在LoopThread所引用的Message消息队列中,直到Message被执行。发送delayed消息是一个明显的内存泄漏,泄漏的时间大于等于延迟的时间。如果消息队列很长的话发送非延迟消息也可能引发一个临时泄漏。
10、解决方案1:Static Runnable
让我们试着克服这个把我们弄疯了的this$0,将匿名类转化为静态类
@Override
protected void onCreate(android.os.Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.inflate_textview);
mTextview = (TextView) findViewById(R.id.xxwj_newtv4);
mHandler.postDelayed(new DoneRunnable(mTextview), 80000);
};
private static final class DoneRunnable implements Runnable{
private final TextView mTV;
public DoneRunnable(TextView tv) {
this.mTV = tv;
}
@Override
public void run() {
mTV.setText("Done");
}
}
11、运行、旋转屏幕、释放内存堆:
12、啥?还tm这样,看看谁持有了Activities的引用
13、看这颗树的最底部,activity被DoneRunnable中的mTextView类的mContext引用,用静态内部类没办法解决内存泄漏,需要来点更狠的
14、解决方案2:弱引用的Static Runnable
我们继续用之前的修复办法并且弱引组织Activity被释放的TextView
@Override
protected void onCreate(android.os.Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.inflate_textview);
mTextview = (TextView) findViewById(R.id.xxwj_newtv4);
mHandler.postDelayed(new DoneRunnable(mTextview), 80000);
};
private static final class DoneRunnable implements Runnable{
private final WeakReference<TextView> mTV;
public DoneRunnable(TextView tv) {
this.mTV = new WeakReference<TextView>(tv);
}
@Override
public void run() {
final TextView tt = mTV.get();
if(tt != null){
tt.setText("Done");
}
}
}
15、重新运行,旋转,释放内存:
16、woops!只有一个Activity实例,解决了我们的内存泄漏问题
17、使用弱引用时要小心谨慎,他们能在任何时候被置为空,解决办法就是先赋值给一个局部变量(强引用),然后在使用前检查是否为空
18、解决handler内存泄漏我们需要做的:
1、用静态内部类或外部类
2、操纵Handler/Runnable时用弱引用
19、如果和一开始的代码进行对比,会发现在可读性和清晰度方面有很大的不同。一开始的代码很短和清晰,这么写太麻烦
20、解决方案3:在onDestroy中清空所有Message
Handler类中有个牛逼方法removeCallbacksAndMessages(),它可以接受空字段作为参数,它将移除所有特定Handler的Runnable和Message:
@Override
protected void onDestroy() {
mHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
21、完美!这比上一个解决办法要好很多。唯一难搞的是你需要在所有的Activity/Fragment中调用onDestory()方法来清除message,听着都恶心
22、解决方案4:用WeakHandler
隆重介绍一下Badoo团队整的WeakHeadler,Handler的替代方案,安全多了。
23、代码很像吧,经实测内存中也只有一个实例,简单吧,代码和内存都一样简洁。使用方法gradle一下即可
24、总结:
本文一共介绍了3种方案:
1、声明一个继承Runnable类的静态内部类,并弱引用要操作的控件
2、在onDestroy()中调用removeCallbacksAndMessages()
3、调用Badoo团队的WeakHandler来替代os.Handler
666、WeakHandler只能替代postDelay(New Runnable())方法造成的内存泄漏,没办法替代os.Handler中接收消息的操作,所以正常情况下需要调用方法(2)